openuispec 0.1.44 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +79 -55
  2. package/cli/init.ts +14 -3
  3. package/examples/social-app/.mcp.json +10 -0
  4. package/examples/social-app/AGENTS.md +105 -0
  5. package/examples/social-app/CLAUDE.md +105 -0
  6. package/examples/social-app/README.md +19 -0
  7. package/examples/social-app/backend/.gitkeep +1 -0
  8. package/examples/social-app/generated/android/social-app/app/build.gradle.kts +92 -0
  9. package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +26 -0
  10. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +20 -0
  11. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +35 -0
  12. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +13 -0
  13. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +98 -0
  14. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +19 -0
  15. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +68 -0
  16. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +15 -0
  17. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +34 -0
  18. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +390 -0
  19. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +234 -0
  20. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +641 -0
  21. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +113 -0
  22. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +212 -0
  23. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +113 -0
  24. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +137 -0
  25. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +180 -0
  26. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +157 -0
  27. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +85 -0
  28. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +74 -0
  29. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +293 -0
  30. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +116 -0
  31. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +161 -0
  32. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +162 -0
  33. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +95 -0
  34. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +123 -0
  35. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +33 -0
  36. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +41 -0
  37. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +20 -0
  38. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +82 -0
  39. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +60 -0
  40. package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +9 -0
  41. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  42. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  43. package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +91 -0
  44. package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +10 -0
  45. package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +79 -0
  46. package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +79 -0
  47. package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +23 -0
  48. package/examples/social-app/generated/android/social-app/build.gradle.kts +6 -0
  49. package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +48 -0
  50. package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +8 -0
  51. package/examples/social-app/generated/android/social-app/gradle.properties +11 -0
  52. package/examples/social-app/generated/android/social-app/gradlew +25 -0
  53. package/examples/social-app/generated/android/social-app/settings.gradle.kts +23 -0
  54. package/examples/social-app/generated/web/social-app/index.html +12 -0
  55. package/examples/social-app/generated/web/social-app/package-lock.json +2517 -0
  56. package/examples/social-app/generated/web/social-app/package.json +27 -0
  57. package/examples/social-app/generated/web/social-app/src/app/App.tsx +58 -0
  58. package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +247 -0
  59. package/examples/social-app/generated/web/social-app/src/components/cards.tsx +317 -0
  60. package/examples/social-app/generated/web/social-app/src/components/ui.tsx +328 -0
  61. package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +86 -0
  62. package/examples/social-app/generated/web/social-app/src/i18n.tsx +59 -0
  63. package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +85 -0
  64. package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +70 -0
  65. package/examples/social-app/generated/web/social-app/src/lib/utils.ts +97 -0
  66. package/examples/social-app/generated/web/social-app/src/locales/en.json +67 -0
  67. package/examples/social-app/generated/web/social-app/src/locales/ru.json +67 -0
  68. package/examples/social-app/generated/web/social-app/src/locales/uz.json +67 -0
  69. package/examples/social-app/generated/web/social-app/src/main.tsx +16 -0
  70. package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +90 -0
  71. package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +86 -0
  72. package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +57 -0
  73. package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +113 -0
  74. package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +52 -0
  75. package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +41 -0
  76. package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +115 -0
  77. package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +57 -0
  78. package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +76 -0
  79. package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +96 -0
  80. package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +77 -0
  81. package/examples/social-app/generated/web/social-app/src/state/store.ts +592 -0
  82. package/examples/social-app/generated/web/social-app/src/styles.css +124 -0
  83. package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +1 -0
  84. package/examples/social-app/generated/web/social-app/tsconfig.json +22 -0
  85. package/examples/social-app/generated/web/social-app/tsconfig.node.json +13 -0
  86. package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +1 -0
  87. package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +1 -0
  88. package/examples/social-app/generated/web/social-app/vite.config.d.ts +2 -0
  89. package/examples/social-app/generated/web/social-app/vite.config.js +6 -0
  90. package/examples/social-app/generated/web/social-app/vite.config.ts +7 -0
  91. package/examples/social-app/openuispec/README.md +56 -0
  92. package/examples/social-app/openuispec/contracts/.gitkeep +0 -0
  93. package/examples/social-app/openuispec/contracts/action_trigger.yaml +73 -0
  94. package/examples/social-app/openuispec/contracts/collection.yaml +43 -0
  95. package/examples/social-app/openuispec/contracts/data_display.yaml +47 -0
  96. package/examples/social-app/openuispec/contracts/feedback.yaml +49 -0
  97. package/examples/social-app/openuispec/contracts/input_field.yaml +41 -0
  98. package/examples/social-app/openuispec/contracts/nav_container.yaml +34 -0
  99. package/examples/social-app/openuispec/contracts/surface.yaml +41 -0
  100. package/examples/social-app/openuispec/flows/.gitkeep +0 -0
  101. package/examples/social-app/openuispec/flows/create_post.yaml +66 -0
  102. package/examples/social-app/openuispec/locales/.gitkeep +0 -0
  103. package/examples/social-app/openuispec/locales/en.json +67 -0
  104. package/examples/social-app/openuispec/locales/ru.json +67 -0
  105. package/examples/social-app/openuispec/locales/uz.json +67 -0
  106. package/examples/social-app/openuispec/openuispec.yaml +214 -0
  107. package/examples/social-app/openuispec/platform/.gitkeep +0 -0
  108. package/examples/social-app/openuispec/platform/android.yaml +30 -0
  109. package/examples/social-app/openuispec/platform/ios.yaml +19 -0
  110. package/examples/social-app/openuispec/platform/web.yaml +23 -0
  111. package/examples/social-app/openuispec/screens/.gitkeep +0 -0
  112. package/examples/social-app/openuispec/screens/chat_detail.yaml +53 -0
  113. package/examples/social-app/openuispec/screens/discover.yaml +78 -0
  114. package/examples/social-app/openuispec/screens/edit_profile.yaml +78 -0
  115. package/examples/social-app/openuispec/screens/home_feed.yaml +123 -0
  116. package/examples/social-app/openuispec/screens/messages_inbox.yaml +43 -0
  117. package/examples/social-app/openuispec/screens/notifications.yaml +29 -0
  118. package/examples/social-app/openuispec/screens/post_detail.yaml +86 -0
  119. package/examples/social-app/openuispec/screens/profile_self.yaml +53 -0
  120. package/examples/social-app/openuispec/screens/profile_user.yaml +60 -0
  121. package/examples/social-app/openuispec/screens/search_results.yaml +62 -0
  122. package/examples/social-app/openuispec/screens/settings.yaml +94 -0
  123. package/examples/social-app/openuispec/tokens/.gitkeep +0 -0
  124. package/examples/social-app/openuispec/tokens/color.yaml +76 -0
  125. package/examples/social-app/openuispec/tokens/elevation.yaml +31 -0
  126. package/examples/social-app/openuispec/tokens/icons.yaml +147 -0
  127. package/examples/social-app/openuispec/tokens/layout.yaml +37 -0
  128. package/examples/social-app/openuispec/tokens/motion.yaml +28 -0
  129. package/examples/social-app/openuispec/tokens/spacing.yaml +19 -0
  130. package/examples/social-app/openuispec/tokens/themes.yaml +31 -0
  131. package/examples/social-app/openuispec/tokens/typography.yaml +50 -0
  132. package/examples/social-app/package.json +12 -0
  133. package/mcp-server/index.ts +69 -0
  134. package/package.json +1 -1
@@ -0,0 +1,162 @@
1
+ package com.social.app.ui.screens
2
+
3
+ import androidx.compose.foundation.layout.Arrangement
4
+ import androidx.compose.foundation.layout.Column
5
+ import androidx.compose.foundation.layout.fillMaxSize
6
+ import androidx.compose.foundation.layout.padding
7
+ import androidx.compose.foundation.rememberScrollState
8
+ import androidx.compose.foundation.verticalScroll
9
+ import androidx.compose.material.icons.Icons
10
+ import androidx.compose.material.icons.automirrored.filled.ArrowBack
11
+ import androidx.compose.material.icons.filled.Edit
12
+ import androidx.compose.material3.ExperimentalMaterial3Api
13
+ import androidx.compose.material3.Icon
14
+ import androidx.compose.material3.IconButton
15
+ import androidx.compose.material3.MaterialTheme
16
+ import androidx.compose.material3.Scaffold
17
+ import androidx.compose.material3.Text
18
+ import androidx.compose.material3.TopAppBar
19
+ import androidx.compose.material3.TopAppBarDefaults
20
+ import androidx.compose.runtime.Composable
21
+ import androidx.compose.runtime.DisposableEffect
22
+ import androidx.compose.runtime.getValue
23
+ import androidx.compose.runtime.mutableStateOf
24
+ import androidx.compose.runtime.remember
25
+ import androidx.compose.runtime.rememberCoroutineScope
26
+ import androidx.compose.runtime.setValue
27
+ import androidx.compose.ui.Modifier
28
+ import androidx.compose.ui.res.stringResource
29
+ import com.arkivanov.mvikotlin.core.store.StoreFactory
30
+ import com.arkivanov.mvikotlin.extensions.coroutines.stateFlow
31
+ import com.social.app.R
32
+ import com.social.app.data.preferences.PreferencesRepository
33
+ import com.social.app.data.preferences.ThemeMode
34
+ import com.social.app.ui.components.ActionTriggerButton
35
+ import com.social.app.ui.components.ActionTriggerVariant
36
+ import com.social.app.ui.components.ChipOption
37
+ import com.social.app.ui.components.ContractConfirmationDialog
38
+ import com.social.app.ui.components.ContractSectionHeader
39
+ import com.social.app.ui.components.ContractSelectField
40
+ import com.social.app.ui.components.ContractToggleField
41
+ import com.social.app.ui.theme.Spacing
42
+ import androidx.compose.runtime.collectAsState
43
+
44
+ @OptIn(ExperimentalMaterial3Api::class)
45
+ @Composable
46
+ fun SettingsScreen(
47
+ onBackClick: () -> Unit,
48
+ onEditProfileClick: () -> Unit,
49
+ preferencesRepository: PreferencesRepository,
50
+ storeFactory: StoreFactory,
51
+ ) {
52
+ val storeScope = rememberCoroutineScope()
53
+ val store =
54
+ remember(preferencesRepository, storeFactory) {
55
+ SettingsStoreFactory(
56
+ storeFactory = storeFactory,
57
+ preferencesRepository = preferencesRepository,
58
+ ).create()
59
+ }
60
+ DisposableEffect(store) {
61
+ onDispose(store::dispose)
62
+ }
63
+ val stateFlow = remember(store, storeScope) { store.stateFlow(storeScope) }
64
+ val state by stateFlow.collectAsState()
65
+ val preferences = state.preferences
66
+ var themeExpanded by remember { mutableStateOf(false) }
67
+ var showLogoutDialog by remember { mutableStateOf(false) }
68
+
69
+ val themeOptions =
70
+ listOf(
71
+ ChipOption("system", stringResource(R.string.settings_theme_system)),
72
+ ChipOption("light", stringResource(R.string.settings_theme_light)),
73
+ ChipOption("dark", stringResource(R.string.settings_theme_dark)),
74
+ )
75
+
76
+ Scaffold(
77
+ topBar = {
78
+ TopAppBar(
79
+ title = { Text(stringResource(R.string.nav_settings)) },
80
+ navigationIcon = {
81
+ IconButton(onClick = onBackClick) {
82
+ Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
83
+ }
84
+ },
85
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.surface)
86
+ )
87
+ }
88
+ ) { padding ->
89
+ Column(
90
+ modifier = Modifier
91
+ .fillMaxSize()
92
+ .padding(padding)
93
+ .padding(horizontal = Spacing.MD)
94
+ .verticalScroll(rememberScrollState())
95
+ .padding(bottom = Spacing.XL),
96
+ verticalArrangement = Arrangement.spacedBy(Spacing.SM),
97
+ ) {
98
+ ContractSectionHeader(stringResource(R.string.settings_appearance))
99
+ ContractSelectField(
100
+ label = stringResource(R.string.settings_theme),
101
+ selectedLabel = themeOptions.first { it.value == preferences.themeMode.storageValue }.label,
102
+ expanded = themeExpanded,
103
+ onExpandedChange = { themeExpanded = it },
104
+ options = themeOptions,
105
+ onOptionSelected = {
106
+ store.accept(SettingsStore.Intent.SelectTheme(ThemeMode.fromStorageValue(it.value)))
107
+ },
108
+ )
109
+
110
+ ContractSectionHeader(stringResource(R.string.settings_notifications))
111
+ ContractToggleField(
112
+ label = stringResource(R.string.settings_push_notifications),
113
+ checked = preferences.pushNotifications,
114
+ onCheckedChange = {
115
+ store.accept(SettingsStore.Intent.SetPushNotifications(it))
116
+ },
117
+ )
118
+ ContractToggleField(
119
+ label = stringResource(R.string.settings_message_previews),
120
+ checked = preferences.messagePreviews,
121
+ onCheckedChange = {
122
+ store.accept(SettingsStore.Intent.SetMessagePreviews(it))
123
+ },
124
+ )
125
+
126
+ ContractSectionHeader(stringResource(R.string.settings_language))
127
+ ContractToggleField(
128
+ label = stringResource(R.string.settings_auto_translate),
129
+ checked = preferences.autoTranslate,
130
+ onCheckedChange = {
131
+ store.accept(SettingsStore.Intent.SetAutoTranslate(it))
132
+ },
133
+ )
134
+
135
+ ContractSectionHeader(stringResource(R.string.settings_account))
136
+ ActionTriggerButton(
137
+ text = stringResource(R.string.settings_edit_profile),
138
+ onClick = onEditProfileClick,
139
+ variant = ActionTriggerVariant.Secondary,
140
+ fullWidth = true,
141
+ icon = { Icon(Icons.Default.Edit, contentDescription = null) },
142
+ )
143
+ ActionTriggerButton(
144
+ text = stringResource(R.string.settings_logout),
145
+ onClick = { showLogoutDialog = true },
146
+ variant = ActionTriggerVariant.Destructive,
147
+ fullWidth = true,
148
+ )
149
+ }
150
+ }
151
+
152
+ if (showLogoutDialog) {
153
+ ContractConfirmationDialog(
154
+ title = stringResource(R.string.settings_logout),
155
+ message = stringResource(R.string.settings_logout_confirm),
156
+ confirmLabel = stringResource(R.string.settings_logout),
157
+ dismissLabel = stringResource(R.string.common_cancel),
158
+ onConfirm = { showLogoutDialog = false },
159
+ onDismiss = { showLogoutDialog = false },
160
+ )
161
+ }
162
+ }
@@ -0,0 +1,95 @@
1
+ package com.social.app.ui.screens
2
+
3
+ import com.arkivanov.mvikotlin.core.store.Reducer
4
+ import com.arkivanov.mvikotlin.core.store.Store
5
+ import com.arkivanov.mvikotlin.core.store.StoreFactory
6
+ import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
7
+ import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor
8
+ import com.social.app.data.preferences.AppPreferences
9
+ import com.social.app.data.preferences.PreferencesRepository
10
+ import com.social.app.data.preferences.ThemeMode
11
+ import kotlinx.coroutines.launch
12
+
13
+ interface SettingsStore : Store<SettingsStore.Intent, SettingsStore.State, Nothing> {
14
+ sealed interface Intent {
15
+ data class SelectTheme(val themeMode: ThemeMode) : Intent
16
+
17
+ data class SetPushNotifications(val enabled: Boolean) : Intent
18
+
19
+ data class SetMessagePreviews(val enabled: Boolean) : Intent
20
+
21
+ data class SetAutoTranslate(val enabled: Boolean) : Intent
22
+ }
23
+
24
+ data class State(
25
+ val preferences: AppPreferences = AppPreferences(),
26
+ )
27
+ }
28
+
29
+ class SettingsStoreFactory(
30
+ private val storeFactory: StoreFactory,
31
+ private val preferencesRepository: PreferencesRepository,
32
+ ) {
33
+ fun create(): SettingsStore =
34
+ object : SettingsStore,
35
+ Store<SettingsStore.Intent, SettingsStore.State, Nothing> by storeFactory.create(
36
+ name = "SettingsStore",
37
+ initialState = SettingsStore.State(),
38
+ bootstrapper = SimpleBootstrapper(Action.ObservePreferences),
39
+ executorFactory = ::ExecutorImpl,
40
+ reducer = ReducerImpl,
41
+ ) {}
42
+
43
+ private sealed interface Action {
44
+ data object ObservePreferences : Action
45
+ }
46
+
47
+ private sealed interface Message {
48
+ data class PreferencesLoaded(val preferences: AppPreferences) : Message
49
+ }
50
+
51
+ private inner class ExecutorImpl :
52
+ CoroutineExecutor<SettingsStore.Intent, Action, SettingsStore.State, Message, Nothing>() {
53
+ override fun executeAction(action: Action) {
54
+ when (action) {
55
+ Action.ObservePreferences ->
56
+ scope.launch {
57
+ preferencesRepository.preferences.collect { preferences ->
58
+ dispatch(Message.PreferencesLoaded(preferences))
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ override fun executeIntent(intent: SettingsStore.Intent) {
65
+ when (intent) {
66
+ is SettingsStore.Intent.SelectTheme ->
67
+ scope.launch {
68
+ preferencesRepository.updateThemeMode(intent.themeMode)
69
+ }
70
+
71
+ is SettingsStore.Intent.SetPushNotifications ->
72
+ scope.launch {
73
+ preferencesRepository.updatePushNotifications(intent.enabled)
74
+ }
75
+
76
+ is SettingsStore.Intent.SetMessagePreviews ->
77
+ scope.launch {
78
+ preferencesRepository.updateMessagePreviews(intent.enabled)
79
+ }
80
+
81
+ is SettingsStore.Intent.SetAutoTranslate ->
82
+ scope.launch {
83
+ preferencesRepository.updateAutoTranslate(intent.enabled)
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ private object ReducerImpl : Reducer<SettingsStore.State, Message> {
90
+ override fun SettingsStore.State.reduce(msg: Message): SettingsStore.State =
91
+ when (msg) {
92
+ is Message.PreferencesLoaded -> copy(preferences = msg.preferences)
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,123 @@
1
+ package com.social.app.ui.screens
2
+
3
+ import androidx.compose.foundation.layout.Box
4
+ import androidx.compose.foundation.layout.fillMaxSize
5
+ import androidx.compose.foundation.layout.fillMaxWidth
6
+ import androidx.compose.foundation.layout.padding
7
+ import androidx.compose.foundation.lazy.LazyColumn
8
+ import androidx.compose.foundation.lazy.items
9
+ import androidx.compose.material.icons.Icons
10
+ import androidx.compose.material.icons.automirrored.filled.ArrowBack
11
+ import androidx.compose.material3.ExperimentalMaterial3Api
12
+ import androidx.compose.material3.Icon
13
+ import androidx.compose.material3.IconButton
14
+ import androidx.compose.material3.MaterialTheme
15
+ import androidx.compose.material3.Scaffold
16
+ import androidx.compose.material3.Text
17
+ import androidx.compose.material3.TopAppBar
18
+ import androidx.compose.material3.TopAppBarDefaults
19
+ import androidx.compose.runtime.Composable
20
+ import androidx.compose.runtime.getValue
21
+ import androidx.compose.runtime.mutableStateOf
22
+ import androidx.compose.runtime.remember
23
+ import androidx.compose.runtime.setValue
24
+ import androidx.compose.ui.Alignment
25
+ import androidx.compose.ui.Modifier
26
+ import androidx.compose.ui.res.stringResource
27
+ import com.social.app.R
28
+ import com.social.app.data.MockData
29
+ import com.social.app.ui.components.ActionTriggerVariant
30
+ import com.social.app.ui.components.ContractListCard
31
+ import com.social.app.ui.components.ContractSectionHeader
32
+ import com.social.app.ui.components.PostItem
33
+ import com.social.app.ui.components.ProfileHeroCard
34
+ import com.social.app.ui.theme.Spacing
35
+
36
+ @OptIn(ExperimentalMaterial3Api::class)
37
+ @Composable
38
+ fun UserProfileScreen(
39
+ userId: String,
40
+ onBackClick: () -> Unit,
41
+ onPostClick: (String) -> Unit
42
+ ) {
43
+ val user = MockData.users.find { it.id == userId } ?: MockData.users[1]
44
+ val userPosts = MockData.posts.filter { it.authorId == user.id }
45
+ var isFollowing by remember(user.id) { mutableStateOf(user.isFollowed) }
46
+
47
+ Scaffold(
48
+ modifier = Modifier.fillMaxSize(),
49
+ topBar = {
50
+ TopAppBar(
51
+ title = { Text(user.displayName) },
52
+ navigationIcon = {
53
+ IconButton(onClick = onBackClick) {
54
+ Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
55
+ }
56
+ },
57
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.surface)
58
+ )
59
+ }
60
+ ) { padding ->
61
+ LazyColumn(
62
+ modifier = Modifier
63
+ .fillMaxSize()
64
+ .padding(padding)
65
+ ) {
66
+ item {
67
+ ProfileHeroCard(
68
+ title = user.displayName,
69
+ subtitle = "@${user.handle}",
70
+ body = user.bio,
71
+ avatarUrl = user.avatarUrl,
72
+ modifier = Modifier
73
+ .fillMaxWidth()
74
+ .padding(Spacing.MD),
75
+ actionLabel =
76
+ if (isFollowing) {
77
+ stringResource(R.string.profile_following)
78
+ } else {
79
+ stringResource(R.string.profile_follow_button)
80
+ },
81
+ actionVariant = if (isFollowing) ActionTriggerVariant.Secondary else ActionTriggerVariant.Primary,
82
+ onActionClick = { isFollowing = !isFollowing },
83
+ )
84
+ }
85
+
86
+ item {
87
+ ContractSectionHeader(stringResource(R.string.profile_posts_header))
88
+ }
89
+
90
+ if (userPosts.isEmpty()) {
91
+ item {
92
+ Box(
93
+ modifier =
94
+ Modifier
95
+ .fillMaxWidth()
96
+ .padding(horizontal = Spacing.MD, vertical = Spacing.SM),
97
+ contentAlignment = Alignment.Center,
98
+ ) {
99
+ ContractListCard(
100
+ title = stringResource(R.string.profile_no_posts_user),
101
+ )
102
+ }
103
+ }
104
+ } else {
105
+ items(userPosts) { post ->
106
+ PostItem(
107
+ authorName = post.authorName,
108
+ authorHandle = post.authorHandle,
109
+ authorAvatar = post.authorAvatar,
110
+ body = post.body,
111
+ likeCount = post.likeCount,
112
+ commentCount = post.commentCount,
113
+ timestamp = post.timestamp,
114
+ mediaUrl = post.mediaUrl,
115
+ onLikeClick = {},
116
+ onAuthorClick = {},
117
+ onCommentClick = { onPostClick(post.id) },
118
+ )
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,33 @@
1
+ package com.social.app.ui.theme
2
+
3
+ import androidx.compose.ui.graphics.Color
4
+
5
+ val BrandPrimary = Color(0xFF1C1B1A)
6
+ val OnBrandPrimary = Color(0xFFFFFFFF)
7
+ val BrandAccent = Color(0xFF5B52A3)
8
+ val OnBrandAccent = Color(0xFFFFFFFF)
9
+
10
+ val SurfacePrimary = Color(0xFFFAF8F5)
11
+ val SurfaceSecondary = Color(0xFFF3F0EB)
12
+ val SurfaceTertiary = Color(0xFFEBE7E0)
13
+
14
+ val TextPrimary = Color(0xFF1C1B1A)
15
+ val TextSecondary = Color(0xFF6B6966)
16
+ val TextTertiary = Color(0xFF9E9A95)
17
+
18
+ val SemanticSuccess = Color(0xFF2D9D5E)
19
+ val SemanticWarning = Color(0xFFD4920E)
20
+ val SemanticDanger = Color(0xFFD43B3B)
21
+ val SemanticInfo = Color(0xFF3B82D4)
22
+
23
+ val BorderDefault = Color(0xFFE0DCD6)
24
+ val BorderStrong = Color(0xFFC5C0B8)
25
+
26
+ val DarkSurfacePrimary = Color(0xFF1A1A1A)
27
+ val DarkSurfaceSecondary = Color(0xFF262626)
28
+ val DarkSurfaceTertiary = Color(0xFF363636)
29
+ val DarkTextPrimary = Color(0xFFFAFAFA)
30
+ val DarkTextSecondary = Color(0xFFA6A6A6)
31
+ val DarkTextTertiary = Color(0xFF6B6B6B)
32
+ val DarkBorderDefault = Color(0xFF4A4A4A)
33
+ val DarkBorderStrong = Color(0xFF666666)
@@ -0,0 +1,41 @@
1
+ package com.social.app.ui.theme
2
+
3
+ import androidx.compose.foundation.shape.RoundedCornerShape
4
+ import androidx.compose.ui.unit.dp
5
+
6
+ object Shapes {
7
+ val RoundedCapPrimary = RoundedCornerShape(
8
+ topStart = 2.dp,
9
+ topEnd = 24.dp,
10
+ bottomEnd = 2.dp,
11
+ bottomStart = 24.dp
12
+ )
13
+
14
+ val RoundedCapSecondary = RoundedCornerShape(
15
+ topStart = 24.dp,
16
+ topEnd = 2.dp,
17
+ bottomEnd = 24.dp,
18
+ bottomStart = 2.dp
19
+ )
20
+
21
+ val CardShape = RoundedCornerShape(
22
+ topStart = 3.dp,
23
+ topEnd = 20.dp,
24
+ bottomEnd = 3.dp,
25
+ bottomStart = 20.dp
26
+ )
27
+
28
+ val HeroShape = RoundedCornerShape(
29
+ topStart = 3.dp,
30
+ topEnd = 24.dp,
31
+ bottomEnd = 3.dp,
32
+ bottomStart = 24.dp
33
+ )
34
+
35
+ val SheetShape = RoundedCornerShape(
36
+ topStart = 24.dp,
37
+ topEnd = 24.dp,
38
+ bottomStart = 0.dp,
39
+ bottomEnd = 0.dp
40
+ )
41
+ }
@@ -0,0 +1,20 @@
1
+ package com.social.app.ui.theme
2
+
3
+ import androidx.compose.ui.unit.dp
4
+
5
+ object Spacing {
6
+ val None = 0.dp
7
+ val XXS = 2.dp
8
+ val XS = 4.dp
9
+ val SM = 8.dp
10
+ val MD = 16.dp
11
+ val LG = 24.dp
12
+ val XL = 32.dp
13
+ val XXL = 48.dp
14
+
15
+ val PageMarginHorizontal = MD
16
+ val PageMarginVertical = MD
17
+ val CardPadding = MD
18
+ val SectionGap = LG
19
+ val ListItemGap = SM
20
+ }
@@ -0,0 +1,82 @@
1
+ package com.social.app.ui.theme
2
+
3
+ import android.app.Activity
4
+ import android.os.Build
5
+ import androidx.compose.foundation.isSystemInDarkTheme
6
+ import androidx.compose.material3.MaterialTheme
7
+ import androidx.compose.material3.darkColorScheme
8
+ import androidx.compose.material3.dynamicDarkColorScheme
9
+ import androidx.compose.material3.dynamicLightColorScheme
10
+ import androidx.compose.material3.lightColorScheme
11
+ import androidx.compose.runtime.Composable
12
+ import androidx.compose.runtime.SideEffect
13
+ import androidx.compose.ui.graphics.toArgb
14
+ import androidx.compose.ui.platform.LocalContext
15
+ import androidx.compose.ui.platform.LocalView
16
+ import androidx.core.view.WindowCompat
17
+ import com.social.app.data.preferences.ThemeMode
18
+
19
+ private val DarkColorScheme = darkColorScheme(
20
+ primary = BrandPrimary,
21
+ onPrimary = OnBrandPrimary,
22
+ secondary = BrandAccent,
23
+ onSecondary = OnBrandAccent,
24
+ background = DarkSurfacePrimary,
25
+ surface = DarkSurfacePrimary,
26
+ onSurface = DarkTextPrimary,
27
+ onBackground = DarkTextPrimary,
28
+ surfaceVariant = DarkSurfaceSecondary,
29
+ onSurfaceVariant = DarkTextSecondary,
30
+ outline = DarkBorderDefault,
31
+ )
32
+
33
+ private val LightColorScheme = lightColorScheme(
34
+ primary = BrandPrimary,
35
+ onPrimary = OnBrandPrimary,
36
+ secondary = BrandAccent,
37
+ onSecondary = OnBrandAccent,
38
+ background = SurfacePrimary,
39
+ surface = SurfacePrimary,
40
+ onSurface = TextPrimary,
41
+ onBackground = TextPrimary,
42
+ surfaceVariant = SurfaceSecondary,
43
+ onSurfaceVariant = TextSecondary,
44
+ outline = BorderDefault,
45
+ )
46
+
47
+ @Composable
48
+ fun SocialAppTheme(
49
+ themeMode: ThemeMode = ThemeMode.System,
50
+ // Dynamic color is available on Android 12+
51
+ dynamicColor: Boolean = false,
52
+ content: @Composable () -> Unit
53
+ ) {
54
+ val systemDarkTheme = isSystemInDarkTheme()
55
+ val darkTheme =
56
+ when (themeMode) {
57
+ ThemeMode.System -> systemDarkTheme
58
+ ThemeMode.Light -> false
59
+ ThemeMode.Dark -> true
60
+ }
61
+ val colorScheme = when {
62
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
63
+ val context = LocalContext.current
64
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
65
+ }
66
+ darkTheme -> DarkColorScheme
67
+ else -> LightColorScheme
68
+ }
69
+ val view = LocalView.current
70
+ if (!view.isInEditMode) {
71
+ SideEffect {
72
+ val window = (view.context as Activity).window
73
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
74
+ }
75
+ }
76
+
77
+ MaterialTheme(
78
+ colorScheme = colorScheme,
79
+ typography = Typography,
80
+ content = content
81
+ )
82
+ }
@@ -0,0 +1,60 @@
1
+ package com.social.app.ui.theme
2
+
3
+ import androidx.compose.material3.Typography
4
+ import androidx.compose.ui.text.TextStyle
5
+ import androidx.compose.ui.text.font.FontFamily
6
+ import androidx.compose.ui.text.font.FontWeight
7
+ import androidx.compose.ui.unit.sp
8
+
9
+ private val AppFontFamily = FontFamily.SansSerif
10
+
11
+ val Typography = Typography(
12
+ displayLarge = TextStyle(
13
+ fontFamily = AppFontFamily,
14
+ fontWeight = FontWeight.Bold,
15
+ fontSize = 32.sp,
16
+ lineHeight = 38.4.sp
17
+ ),
18
+ headlineLarge = TextStyle(
19
+ fontFamily = AppFontFamily,
20
+ fontWeight = FontWeight.SemiBold,
21
+ fontSize = 24.sp,
22
+ lineHeight = 31.2.sp
23
+ ),
24
+ headlineMedium = TextStyle(
25
+ fontFamily = AppFontFamily,
26
+ fontWeight = FontWeight.SemiBold,
27
+ fontSize = 20.sp,
28
+ lineHeight = 26.sp
29
+ ),
30
+ headlineSmall = TextStyle(
31
+ fontFamily = AppFontFamily,
32
+ fontWeight = FontWeight.SemiBold,
33
+ fontSize = 16.sp,
34
+ lineHeight = 22.4.sp
35
+ ),
36
+ bodyLarge = TextStyle(
37
+ fontFamily = AppFontFamily,
38
+ fontWeight = FontWeight.Normal,
39
+ fontSize = 16.sp,
40
+ lineHeight = 24.sp
41
+ ),
42
+ bodyMedium = TextStyle(
43
+ fontFamily = AppFontFamily,
44
+ fontWeight = FontWeight.Normal,
45
+ fontSize = 14.sp,
46
+ lineHeight = 21.sp
47
+ ),
48
+ labelSmall = TextStyle(
49
+ fontFamily = AppFontFamily,
50
+ fontWeight = FontWeight.Normal,
51
+ fontSize = 12.sp,
52
+ lineHeight = 16.8.sp
53
+ ),
54
+ labelLarge = TextStyle(
55
+ fontFamily = AppFontFamily,
56
+ fontWeight = FontWeight.SemiBold,
57
+ fontSize = 16.sp,
58
+ lineHeight = 16.sp
59
+ )
60
+ )
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="108dp"
3
+ android:height="108dp"
4
+ android:viewportWidth="108"
5
+ android:viewportHeight="108">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M31,31h46v46h-46z" />
9
+ </vector>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <background android:drawable="@color/brand_primary"/>
4
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
5
+ </adaptive-icon>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <background android:drawable="@color/brand_primary"/>
4
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
5
+ </adaptive-icon>