From fdf15913a93e069bec65c19072b298910dc11b21 Mon Sep 17 00:00:00 2001 From: Amirmahdi Nourkazemi Date: Fri, 12 Jun 2026 15:42:09 +0330 Subject: [PATCH] feat: theme manage and auth --- .../com/approagency/pharmacy/MainActivity.kt | 5 +- .../pharmacy/data/dto/AuthModels.kt | 19 +- .../pharmacy/data/local/SessionManager.kt | 28 +- .../data/repository/AuthRepositoryImpl.kt | 3 +- .../pharmacy/navigation/MainContainer.kt | 79 ++++-- .../presentation/account/AccountDrawer.kt | 265 ++++++++++++++++++ .../presentation/account/AccountSheet.kt | 37 ++- .../approagency/pharmacy/ui/theme/Theme.kt | 7 +- .../pharmacy/ui/theme/ThemeMode.kt | 4 + 9 files changed, 407 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/com/approagency/pharmacy/presentation/account/AccountDrawer.kt create mode 100644 app/src/main/java/com/approagency/pharmacy/ui/theme/ThemeMode.kt diff --git a/app/src/main/java/com/approagency/pharmacy/MainActivity.kt b/app/src/main/java/com/approagency/pharmacy/MainActivity.kt index f185d17..d27c5eb 100644 --- a/app/src/main/java/com/approagency/pharmacy/MainActivity.kt +++ b/app/src/main/java/com/approagency/pharmacy/MainActivity.kt @@ -5,6 +5,8 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.lifecycle.lifecycleScope import com.approagency.pharmacy.data.local.SessionManager import com.approagency.pharmacy.domain.repository.AuthRepository @@ -23,7 +25,8 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - DrugTheme { + val themeMode by session.themeMode.collectAsState() + DrugTheme(themeMode = themeMode) { AppNavGraph() } } diff --git a/app/src/main/java/com/approagency/pharmacy/data/dto/AuthModels.kt b/app/src/main/java/com/approagency/pharmacy/data/dto/AuthModels.kt index daa85cf..c712542 100644 --- a/app/src/main/java/com/approagency/pharmacy/data/dto/AuthModels.kt +++ b/app/src/main/java/com/approagency/pharmacy/data/dto/AuthModels.kt @@ -31,7 +31,21 @@ data class ProductDto( @SerializedName("price") val price: Long? = null, @SerializedName("type") val type: Int? = null, @SerializedName("uuid") val uuid: String? = null, - @SerializedName("descriptions") val descriptions: String? = null + @SerializedName("descriptions") val descriptions: String? = null, + @SerializedName("expires_at") val expiresAt: String? = null, + @SerializedName("expire_at") val expireAt: String? = null, + @SerializedName("pivot") val pivot: ProductPivot? = null +) { + /** تاریخ انقضای اشتراکِ این کاربر؛ از pivot یا فیلدهای مستقیمِ محصول. */ + val resolvedExpireAt: String? + get() = pivot?.expiresAt ?: pivot?.expireAt ?: expiresAt ?: expireAt +} + +/** اطلاعات رابطه‌ی کاربر-محصول (شامل تاریخ انقضای خرید). */ +data class ProductPivot( + @SerializedName("expires_at") val expiresAt: String? = null, + @SerializedName("expire_at") val expireAt: String? = null, + @SerializedName("created_at") val createdAt: String? = null ) /** @@ -55,6 +69,9 @@ data class StatusDto( /** عنوان اشتراک جاری برای نمایش در نوار بالای اپ. */ val subscriptionTitle: String? get() = products?.firstOrNull()?.title + /** تاریخ انقضای اشتراک جاری. */ + val subscriptionExpireAt: String? get() = products?.firstOrNull()?.resolvedExpireAt + val displayName: String? get() = fullName?.takeIf { it.isNotBlank() } ?: listOfNotNull(firstName, lastName).joinToString(" ").takeIf { it.isNotBlank() } } diff --git a/app/src/main/java/com/approagency/pharmacy/data/local/SessionManager.kt b/app/src/main/java/com/approagency/pharmacy/data/local/SessionManager.kt index 3504f73..86d1c32 100644 --- a/app/src/main/java/com/approagency/pharmacy/data/local/SessionManager.kt +++ b/app/src/main/java/com/approagency/pharmacy/data/local/SessionManager.kt @@ -2,6 +2,7 @@ package com.approagency.pharmacy.data.local import android.content.Context import android.content.SharedPreferences +import com.approagency.pharmacy.ui.theme.ThemeMode import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -12,7 +13,8 @@ data class AccountState( val mobile: String? = null, val displayName: String? = null, val isSubscribed: Boolean = false, - val subscriptionTitle: String? = null + val subscriptionTitle: String? = null, + val subscriptionExpireAt: String? = null ) /** @@ -75,13 +77,15 @@ class SessionManager(context: Context) { mobile: String?, displayName: String?, isSubscribed: Boolean, - subscriptionTitle: String? + subscriptionTitle: String?, + subscriptionExpireAt: String? ) { prefs.edit() .putString(KEY_MOBILE, mobile) .putString(KEY_DISPLAY_NAME, displayName) .putBoolean(KEY_IS_SUBSCRIBED, isSubscribed) .putString(KEY_SUB_TITLE, subscriptionTitle) + .putString(KEY_SUB_EXPIRE, subscriptionExpireAt) .apply() publishAccount() } @@ -101,6 +105,7 @@ class SessionManager(context: Context) { .remove(KEY_DISPLAY_NAME) .remove(KEY_IS_SUBSCRIBED) .remove(KEY_SUB_TITLE) + .remove(KEY_SUB_EXPIRE) .apply() publishAccount() } @@ -109,12 +114,27 @@ class SessionManager(context: Context) { _account.value = readAccount() } + // ---------- حالت نمایش (تم) ---------- + + private val _themeMode = MutableStateFlow(readThemeMode()) + val themeMode: StateFlow = _themeMode.asStateFlow() + + fun setThemeMode(mode: ThemeMode) { + prefs.edit().putString(KEY_THEME_MODE, mode.name).apply() + _themeMode.value = mode + } + + private fun readThemeMode(): ThemeMode = + runCatching { ThemeMode.valueOf(prefs.getString(KEY_THEME_MODE, null) ?: ThemeMode.SYSTEM.name) } + .getOrDefault(ThemeMode.SYSTEM) + private fun readAccount() = AccountState( isLoggedIn = !token.isNullOrBlank(), mobile = prefs.getString(KEY_MOBILE, null), displayName = prefs.getString(KEY_DISPLAY_NAME, null), isSubscribed = prefs.getBoolean(KEY_IS_SUBSCRIBED, false), - subscriptionTitle = prefs.getString(KEY_SUB_TITLE, null) + subscriptionTitle = prefs.getString(KEY_SUB_TITLE, null), + subscriptionExpireAt = prefs.getString(KEY_SUB_EXPIRE, null) ) private companion object { @@ -125,5 +145,7 @@ class SessionManager(context: Context) { const val KEY_FREE_SEARCH_COUNT = "free_search_count" const val KEY_IS_SUBSCRIBED = "is_subscribed" const val KEY_SUB_TITLE = "subscription_title" + const val KEY_SUB_EXPIRE = "subscription_expire_at" + const val KEY_THEME_MODE = "theme_mode" } } diff --git a/app/src/main/java/com/approagency/pharmacy/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/approagency/pharmacy/data/repository/AuthRepositoryImpl.kt index 03299ca..f490be8 100644 --- a/app/src/main/java/com/approagency/pharmacy/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/approagency/pharmacy/data/repository/AuthRepositoryImpl.kt @@ -69,7 +69,8 @@ class AuthRepositoryImpl( mobile = dto.mobile ?: session.account.value.mobile, displayName = dto.displayName, isSubscribed = dto.isSubscribed, - subscriptionTitle = dto.subscriptionTitle + subscriptionTitle = dto.subscriptionTitle, + subscriptionExpireAt = dto.subscriptionExpireAt ) return dto.isSubscribed } diff --git a/app/src/main/java/com/approagency/pharmacy/navigation/MainContainer.kt b/app/src/main/java/com/approagency/pharmacy/navigation/MainContainer.kt index 484f3b4..835558b 100644 --- a/app/src/main/java/com/approagency/pharmacy/navigation/MainContainer.kt +++ b/app/src/main/java/com/approagency/pharmacy/navigation/MainContainer.kt @@ -5,24 +5,35 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.navigation.NavHostController import com.approagency.pharmacy.data.local.AccountState import com.approagency.pharmacy.data.local.SessionManager +import com.approagency.pharmacy.domain.repository.AuthRepository +import com.approagency.pharmacy.presentation.account.AccountDrawer import com.approagency.pharmacy.presentation.account.AccountSheet import com.approagency.pharmacy.presentation.account.AccountSheetController +import kotlinx.coroutines.launch import org.koin.compose.koinInject @OptIn(ExperimentalMaterial3Api::class) @@ -33,21 +44,52 @@ fun MainContainer( ) { val session: SessionManager = koinInject() val sheetController: AccountSheetController = koinInject() + val authRepository: AuthRepository = koinInject() val account by session.account.collectAsState() val sheetVisible by sheetController.visible.collectAsState() + val themeMode by session.themeMode.collectAsState() - Scaffold( - topBar = { - AccountAppBar( + val drawerState = rememberDrawerState(DrawerValue.Closed) + val scope = rememberCoroutineScope() + + val context = LocalContext.current + val appVersion = remember { + runCatching { + context.packageManager.getPackageInfo(context.packageName, 0).versionName + }.getOrNull().orEmpty() + } + + ModalNavigationDrawer( + drawerState = drawerState, + drawerContent = { + AccountDrawer( account = account, - onAccountClick = { sheetController.show() } + appVersion = appVersion, + themeMode = themeMode, + onThemeModeChange = { session.setThemeMode(it) }, + onLogout = { + scope.launch { + authRepository.logout() + drawerState.close() + } + } ) - }, - bottomBar = { BottomBar(navController) } - ) { padding -> - Box(modifier = Modifier.padding(padding)) { - content() + } + ) { + Scaffold( + topBar = { + AccountAppBar( + account = account, + onMenuClick = { scope.launch { drawerState.open() } }, + onAccountClick = { sheetController.show() } + ) + }, + bottomBar = { BottomBar(navController) } + ) { padding -> + Box(modifier = Modifier.padding(padding)) { + content() + } } } @@ -60,9 +102,18 @@ fun MainContainer( @Composable private fun AccountAppBar( account: AccountState, + onMenuClick: () -> Unit, onAccountClick: () -> Unit ) { TopAppBar( + navigationIcon = { + IconButton(onClick = onMenuClick) { + Icon( + imageVector = Icons.Filled.Menu, + contentDescription = "منو" + ) + } + }, title = { if (account.isLoggedIn) { Column { @@ -84,15 +135,7 @@ private fun AccountAppBar( } }, actions = { - if (account.isLoggedIn) { - IconButton(onClick = onAccountClick) { - Icon( - imageVector = Icons.Filled.AccountCircle, - contentDescription = "حساب کاربری", - tint = MaterialTheme.colorScheme.primary - ) - } - } else { + if (!account.isLoggedIn) { TextButton(onClick = onAccountClick) { Text("ورود", textAlign = TextAlign.Center) } diff --git a/app/src/main/java/com/approagency/pharmacy/presentation/account/AccountDrawer.kt b/app/src/main/java/com/approagency/pharmacy/presentation/account/AccountDrawer.kt new file mode 100644 index 0000000..aa3fe0a --- /dev/null +++ b/app/src/main/java/com/approagency/pharmacy/presentation/account/AccountDrawer.kt @@ -0,0 +1,265 @@ +package com.approagency.pharmacy.presentation.account + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Logout +import androidx.compose.material.icons.filled.BrightnessAuto +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.DarkMode +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.LightMode +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Phone +import androidx.compose.material.icons.filled.WorkspacePremium +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import com.approagency.pharmacy.data.local.AccountState +import com.approagency.pharmacy.ui.theme.ThemeMode +import com.vada.caller.ui.theme.dime + +/** + * کشوی کناری حساب کاربری با طراحی بهبودیافته: + * - سربرگ با آواتار، نام/موبایل و نشانِ وضعیت اشتراک + * - کارت اطلاعات (موبایل، اشتراک، تاریخ انقضا، نسخه‌ی برنامه) + * - انتخاب حالت نمایش (سیستم/روشن/تاریک) + * - دکمه‌ی خروج + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccountDrawer( + account: AccountState, + appVersion: String, + themeMode: ThemeMode, + onThemeModeChange: (ThemeMode) -> Unit, + onLogout: () -> Unit +) { + ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surface) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Column( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth() + .padding(horizontal = MaterialTheme.dime.lg, vertical = MaterialTheme.dime.xl) + ) { + Header(account) + + Spacer(Modifier.height(MaterialTheme.dime.xl)) + + // کارت اطلاعات حساب + Surface( + color = MaterialTheme.colorScheme.surfaceContainer, + shape = MaterialTheme.shapes.large, + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.padding(MaterialTheme.dime.md)) { + InfoRow( + icon = Icons.Filled.Phone, + label = "شماره موبایل", + value = account.mobile ?: "-" + ) + InfoRow( + icon = Icons.Filled.WorkspacePremium, + label = "اشتراک", + value = account.subscriptionTitle ?: "بدون اشتراک", + highlight = account.isSubscribed + ) + if (account.isSubscribed) { + InfoRow( + icon = Icons.Filled.CalendarMonth, + label = "تاریخ انقضا", + value = account.subscriptionExpireAt ?: "-" + ) + } + InfoRow( + icon = Icons.Filled.Info, + label = "نسخه برنامه", + value = appVersion.ifBlank { "-" } + ) + } + } + + Spacer(Modifier.height(MaterialTheme.dime.xl)) + + // انتخاب حالت نمایش + Text( + text = "حالت نمایش", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.Bold + ) + Spacer(Modifier.height(MaterialTheme.dime.sm)) + ThemeModeSelector(selected = themeMode, onSelect = onThemeModeChange) + + Spacer(Modifier.weight(1f)) + + if (account.isLoggedIn) { + OutlinedButton( + onClick = onLogout, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.Logout, + contentDescription = null, + tint = MaterialTheme.colorScheme.error + ) + Spacer(Modifier.size(MaterialTheme.dime.sm)) + Text( + text = "خروج از حساب", + color = MaterialTheme.colorScheme.error + ) + } + } + } + } + } +} + +@Composable +private fun Header(account: AccountState) { + Row(verticalAlignment = Alignment.CenterVertically) { + Box( + modifier = Modifier + .size(56.dp) + .background(MaterialTheme.colorScheme.primaryContainer, CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Filled.Person, + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.size(32.dp) + ) + } + Spacer(Modifier.size(MaterialTheme.dime.md)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = account.displayName?.takeIf { it.isNotBlank() } + ?: account.mobile + ?: "کاربر مهمان", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Spacer(Modifier.height(MaterialTheme.dime.xs)) + SubscriptionBadge(isSubscribed = account.isSubscribed) + } + } +} + +@Composable +private fun SubscriptionBadge(isSubscribed: Boolean) { + val container = if (isSubscribed) + MaterialTheme.colorScheme.primaryContainer + else + MaterialTheme.colorScheme.surfaceContainerHighest + val content = if (isSubscribed) + MaterialTheme.colorScheme.onPrimaryContainer + else + MaterialTheme.colorScheme.onSurfaceVariant + + Surface(color = container, shape = CircleShape) { + Text( + text = if (isSubscribed) "اشتراک فعال" else "بدون اشتراک", + style = MaterialTheme.typography.labelSmall, + color = content, + modifier = Modifier.padding( + horizontal = MaterialTheme.dime.sm, + vertical = MaterialTheme.dime.xxs + ) + ) + } +} + +@Composable +private fun InfoRow( + icon: ImageVector, + label: String, + value: String, + highlight: Boolean = false +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = MaterialTheme.dime.sm), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.size(MaterialTheme.dime.sm)) + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.weight(1f) + ) + Text( + text = value, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + color = if (highlight) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.onSurface + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ThemeModeSelector( + selected: ThemeMode, + onSelect: (ThemeMode) -> Unit +) { + val options = listOf( + Triple(ThemeMode.SYSTEM, "سیستم", Icons.Filled.BrightnessAuto), + Triple(ThemeMode.LIGHT, "روشن", Icons.Filled.LightMode), + Triple(ThemeMode.DARK, "تاریک", Icons.Filled.DarkMode) + ) + SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) { + options.forEachIndexed { index, (mode, label, icon) -> + SegmentedButton( + selected = selected == mode, + onClick = { onSelect(mode) }, + shape = SegmentedButtonDefaults.itemShape(index, options.size), + icon = { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + }, + label = { Text(label, style = MaterialTheme.typography.labelMedium) } + ) + } + } +} diff --git a/app/src/main/java/com/approagency/pharmacy/presentation/account/AccountSheet.kt b/app/src/main/java/com/approagency/pharmacy/presentation/account/AccountSheet.kt index 81dc082..c43a31a 100644 --- a/app/src/main/java/com/approagency/pharmacy/presentation/account/AccountSheet.kt +++ b/app/src/main/java/com/approagency/pharmacy/presentation/account/AccountSheet.kt @@ -2,6 +2,7 @@ package com.approagency.pharmacy.presentation.account import android.app.Activity import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -29,6 +30,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.approagency.pharmacy.domain.model.SubscriptionProduct @@ -77,17 +79,20 @@ fun AccountSheet( color = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(Modifier.height(MaterialTheme.dime.lg)) - OutlinedTextField( - value = viewModel.mobile, - onValueChange = viewModel::updateMobile, - label = { Text("شماره موبایل") }, - placeholder = { Text("09xxxxxxxxx") }, - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone), - modifier = Modifier.fillMaxWidth() - ) + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { + OutlinedTextField( + value = viewModel.mobile, + shape = MaterialTheme.shapes.large, + onValueChange = { viewModel.updateMobile(it) }, + label = { Text("شماره موبایل" , textAlign = TextAlign.Right ) }, + placeholder = { Text("09xxxxxxxxx") }, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone), + modifier = Modifier.fillMaxWidth() + ) + } Spacer(Modifier.height(MaterialTheme.dime.md)) - PrimaryButton(text = "ارسال کد", isLoading = ui.busy, onClick = viewModel::sendOtp) + PrimaryButton(text = "ارسال کد", isLoading = ui.busy, onClick = { viewModel.sendOtp() }) } AccountPhase.EnterOtp -> { @@ -100,15 +105,15 @@ fun AccountSheet( Spacer(Modifier.height(MaterialTheme.dime.lg)) OutlinedTextField( value = viewModel.otp, - onValueChange = viewModel::updateOtp, + onValueChange = { viewModel.updateOtp(it) }, label = { Text("کد تأیید") }, singleLine = true, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(MaterialTheme.dime.md)) - PrimaryButton(text = "تأیید و ورود", isLoading = ui.busy, onClick = viewModel::verifyOtp) - TextButton(onClick = viewModel::editMobile, enabled = !ui.busy) { + PrimaryButton(text = "تأیید و ورود", isLoading = ui.busy, onClick = { viewModel.verifyOtp() }) + TextButton(onClick = { viewModel.editMobile() }, enabled = !ui.busy) { Text("ویرایش شماره موبایل") } } @@ -123,7 +128,9 @@ fun AccountSheet( Spacer(Modifier.height(MaterialTheme.dime.lg)) when { ui.productsLoading -> Loading( - modifier = Modifier.fillMaxWidth().height(160.dp) + modifier = Modifier + .fillMaxWidth() + .height(160.dp) ) else -> Column( verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md) @@ -156,7 +163,7 @@ fun AccountSheet( color = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(Modifier.height(MaterialTheme.dime.lg)) - TextButton(onClick = viewModel::logout) { Text("خروج از حساب") } + TextButton(onClick = { viewModel.logout() }) { Text("خروج از حساب") } } } diff --git a/app/src/main/java/com/approagency/pharmacy/ui/theme/Theme.kt b/app/src/main/java/com/approagency/pharmacy/ui/theme/Theme.kt index 3b08609..f2a3b51 100644 --- a/app/src/main/java/com/approagency/pharmacy/ui/theme/Theme.kt +++ b/app/src/main/java/com/approagency/pharmacy/ui/theme/Theme.kt @@ -108,11 +108,16 @@ private val DarkColorScheme = darkColorScheme( @Composable fun DrugTheme( - darkTheme: Boolean = isSystemInDarkTheme(), + themeMode: ThemeMode = ThemeMode.SYSTEM, // Dynamic color is available on Android 12+ dynamicColor: Boolean = false, content: @Composable () -> Unit ) { + val darkTheme = when (themeMode) { + ThemeMode.SYSTEM -> isSystemInDarkTheme() + ThemeMode.DARK -> true + ThemeMode.LIGHT -> false + } val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current diff --git a/app/src/main/java/com/approagency/pharmacy/ui/theme/ThemeMode.kt b/app/src/main/java/com/approagency/pharmacy/ui/theme/ThemeMode.kt new file mode 100644 index 0000000..4b183a2 --- /dev/null +++ b/app/src/main/java/com/approagency/pharmacy/ui/theme/ThemeMode.kt @@ -0,0 +1,4 @@ +package com.approagency.pharmacy.ui.theme + +/** حالت نمایش انتخابی کاربر؛ پیش‌فرض پیروی از سیستم است. */ +enum class ThemeMode { SYSTEM, LIGHT, DARK }