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.
- package/README.md +79 -55
- package/cli/init.ts +14 -3
- package/examples/social-app/.mcp.json +10 -0
- package/examples/social-app/AGENTS.md +105 -0
- package/examples/social-app/CLAUDE.md +105 -0
- package/examples/social-app/README.md +19 -0
- package/examples/social-app/backend/.gitkeep +1 -0
- package/examples/social-app/generated/android/social-app/app/build.gradle.kts +92 -0
- package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +26 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +20 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +35 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +13 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +98 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +19 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +68 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +15 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +34 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +390 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +234 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +641 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +113 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +212 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +113 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +137 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +180 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +157 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +85 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +74 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +293 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +116 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +161 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +162 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +95 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +123 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +33 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +41 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +20 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +82 -0
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +60 -0
- package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +9 -0
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +91 -0
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +10 -0
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +79 -0
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +79 -0
- package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +23 -0
- package/examples/social-app/generated/android/social-app/build.gradle.kts +6 -0
- package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +48 -0
- package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/examples/social-app/generated/android/social-app/gradle.properties +11 -0
- package/examples/social-app/generated/android/social-app/gradlew +25 -0
- package/examples/social-app/generated/android/social-app/settings.gradle.kts +23 -0
- package/examples/social-app/generated/web/social-app/index.html +12 -0
- package/examples/social-app/generated/web/social-app/package-lock.json +2517 -0
- package/examples/social-app/generated/web/social-app/package.json +27 -0
- package/examples/social-app/generated/web/social-app/src/app/App.tsx +58 -0
- package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +247 -0
- package/examples/social-app/generated/web/social-app/src/components/cards.tsx +317 -0
- package/examples/social-app/generated/web/social-app/src/components/ui.tsx +328 -0
- package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +86 -0
- package/examples/social-app/generated/web/social-app/src/i18n.tsx +59 -0
- package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +85 -0
- package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +70 -0
- package/examples/social-app/generated/web/social-app/src/lib/utils.ts +97 -0
- package/examples/social-app/generated/web/social-app/src/locales/en.json +67 -0
- package/examples/social-app/generated/web/social-app/src/locales/ru.json +67 -0
- package/examples/social-app/generated/web/social-app/src/locales/uz.json +67 -0
- package/examples/social-app/generated/web/social-app/src/main.tsx +16 -0
- package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +90 -0
- package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +86 -0
- package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +57 -0
- package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +113 -0
- package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +52 -0
- package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +41 -0
- package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +115 -0
- package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +57 -0
- package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +76 -0
- package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +96 -0
- package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +77 -0
- package/examples/social-app/generated/web/social-app/src/state/store.ts +592 -0
- package/examples/social-app/generated/web/social-app/src/styles.css +124 -0
- package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +1 -0
- package/examples/social-app/generated/web/social-app/tsconfig.json +22 -0
- package/examples/social-app/generated/web/social-app/tsconfig.node.json +13 -0
- package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +1 -0
- package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +1 -0
- package/examples/social-app/generated/web/social-app/vite.config.d.ts +2 -0
- package/examples/social-app/generated/web/social-app/vite.config.js +6 -0
- package/examples/social-app/generated/web/social-app/vite.config.ts +7 -0
- package/examples/social-app/openuispec/README.md +56 -0
- package/examples/social-app/openuispec/contracts/.gitkeep +0 -0
- package/examples/social-app/openuispec/contracts/action_trigger.yaml +73 -0
- package/examples/social-app/openuispec/contracts/collection.yaml +43 -0
- package/examples/social-app/openuispec/contracts/data_display.yaml +47 -0
- package/examples/social-app/openuispec/contracts/feedback.yaml +49 -0
- package/examples/social-app/openuispec/contracts/input_field.yaml +41 -0
- package/examples/social-app/openuispec/contracts/nav_container.yaml +34 -0
- package/examples/social-app/openuispec/contracts/surface.yaml +41 -0
- package/examples/social-app/openuispec/flows/.gitkeep +0 -0
- package/examples/social-app/openuispec/flows/create_post.yaml +66 -0
- package/examples/social-app/openuispec/locales/.gitkeep +0 -0
- package/examples/social-app/openuispec/locales/en.json +67 -0
- package/examples/social-app/openuispec/locales/ru.json +67 -0
- package/examples/social-app/openuispec/locales/uz.json +67 -0
- package/examples/social-app/openuispec/openuispec.yaml +214 -0
- package/examples/social-app/openuispec/platform/.gitkeep +0 -0
- package/examples/social-app/openuispec/platform/android.yaml +30 -0
- package/examples/social-app/openuispec/platform/ios.yaml +19 -0
- package/examples/social-app/openuispec/platform/web.yaml +23 -0
- package/examples/social-app/openuispec/screens/.gitkeep +0 -0
- package/examples/social-app/openuispec/screens/chat_detail.yaml +53 -0
- package/examples/social-app/openuispec/screens/discover.yaml +78 -0
- package/examples/social-app/openuispec/screens/edit_profile.yaml +78 -0
- package/examples/social-app/openuispec/screens/home_feed.yaml +123 -0
- package/examples/social-app/openuispec/screens/messages_inbox.yaml +43 -0
- package/examples/social-app/openuispec/screens/notifications.yaml +29 -0
- package/examples/social-app/openuispec/screens/post_detail.yaml +86 -0
- package/examples/social-app/openuispec/screens/profile_self.yaml +53 -0
- package/examples/social-app/openuispec/screens/profile_user.yaml +60 -0
- package/examples/social-app/openuispec/screens/search_results.yaml +62 -0
- package/examples/social-app/openuispec/screens/settings.yaml +94 -0
- package/examples/social-app/openuispec/tokens/.gitkeep +0 -0
- package/examples/social-app/openuispec/tokens/color.yaml +76 -0
- package/examples/social-app/openuispec/tokens/elevation.yaml +31 -0
- package/examples/social-app/openuispec/tokens/icons.yaml +147 -0
- package/examples/social-app/openuispec/tokens/layout.yaml +37 -0
- package/examples/social-app/openuispec/tokens/motion.yaml +28 -0
- package/examples/social-app/openuispec/tokens/spacing.yaml +19 -0
- package/examples/social-app/openuispec/tokens/themes.yaml +31 -0
- package/examples/social-app/openuispec/tokens/typography.yaml +50 -0
- package/examples/social-app/package.json +12 -0
- package/mcp-server/index.ts +69 -0
- 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>
|