feat: change drawer

This commit is contained in:
2026-06-12 21:16:51 +03:30
parent 675123309a
commit fd8eb0a373
5 changed files with 200 additions and 15 deletions
@@ -21,8 +21,10 @@ import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
@@ -33,6 +35,7 @@ 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 com.approagency.pharmacy.presentation.common.ConfirmBottomSheet
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
@@ -52,6 +55,7 @@ fun MainContainer(
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
var showLogoutConfirm by remember { mutableStateOf(false) }
val context = LocalContext.current
val appVersion = remember {
@@ -68,12 +72,7 @@ fun MainContainer(
appVersion = appVersion,
themeMode = themeMode,
onThemeModeChange = { session.setThemeMode(it) },
onLogout = {
scope.launch {
authRepository.logout()
drawerState.close()
}
}
onLogout = { showLogoutConfirm = true }
)
}
) {
@@ -96,6 +95,22 @@ fun MainContainer(
if (sheetVisible) {
AccountSheet(onDismiss = { sheetController.hide() })
}
if (showLogoutConfirm) {
ConfirmBottomSheet(
title = "خروج از حساب",
message = "آیا می‌خواهید از حساب کاربری خود خارج شوید؟",
confirmText = "خروج",
onConfirm = {
showLogoutConfirm = false
scope.launch {
authRepository.logout()
drawerState.close()
}
},
onDismiss = { showLogoutConfirm = false }
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -11,6 +11,7 @@ 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.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Logout
@@ -61,7 +62,10 @@ fun AccountDrawer(
onThemeModeChange: (ThemeMode) -> Unit,
onLogout: () -> Unit
) {
ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surface) {
ModalDrawerSheet(
modifier = Modifier.width(280.dp),
drawerContainerColor = MaterialTheme.colorScheme.surface
) {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Column(
modifier = Modifier
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -37,9 +38,9 @@ import androidx.compose.ui.unit.dp
import com.approagency.pharmacy.MainActivity
import com.approagency.pharmacy.domain.model.SubscriptionProduct
import com.approagency.pharmacy.presentation.common.CustomModalBottomSheet
import com.approagency.pharmacy.presentation.common.Loading
import com.approagency.pharmacy.presentation.common.OtpTextField
import com.approagency.pharmacy.presentation.common.PrimaryButton
import com.approagency.pharmacy.presentation.common.shimmer
import com.approagency.pharmacy.presentation.viewModel.AccountPhase
import com.approagency.pharmacy.presentation.viewModel.AccountViewModel
import com.vada.caller.ui.theme.dime
@@ -147,11 +148,11 @@ fun AccountSheet(
)
Spacer(Modifier.height(MaterialTheme.dime.lg))
when {
ui.productsLoading -> Loading(
modifier = Modifier
.fillMaxWidth()
.height(160.dp)
)
ui.productsLoading -> Column(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md)
) {
repeat(2) { ProductCardSkeleton() }
}
else -> Column(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md)
) {
@@ -182,8 +183,6 @@ fun AccountSheet(
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.height(MaterialTheme.dime.lg))
TextButton(onClick = { viewModel.logout() }) { Text("خروج از حساب") }
}
}
@@ -241,6 +240,39 @@ private fun ProductCard(
}
}
/** جای‌گیرنده‌ی shimmer برای کارت محصول هنگام بارگذاری. */
@Composable
private fun ProductCardSkeleton() {
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(MaterialTheme.dime.lg)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Box(
Modifier
.width(120.dp)
.height(18.dp)
.shimmer()
)
Box(
Modifier
.width(72.dp)
.height(18.dp)
.shimmer()
)
}
Spacer(Modifier.height(MaterialTheme.dime.md))
Box(
Modifier
.fillMaxWidth()
.height(44.dp)
.shimmer(shape = MaterialTheme.shapes.medium)
)
}
}
}
/** قالب‌بندی قیمت با جداکننده‌ی هزارگان. */
private fun formatPrice(price: Long): String =
price.toString().reversed().chunked(3).joinToString(",").reversed()
@@ -0,0 +1,81 @@
package com.approagency.pharmacy.presentation.common
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.vada.caller.ui.theme.dime
/**
* شیتِ تأییدِ عمومی برای کنشهای حساس (مثل خروج از حساب).
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConfirmBottomSheet(
title: String,
message: String,
confirmText: String,
onConfirm: () -> Unit,
onDismiss: () -> Unit,
dismissText: String = "انصراف",
confirmColor: Color = MaterialTheme.colorScheme.error
) {
CustomModalBottomSheet(onDismiss = onDismiss) {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(MaterialTheme.dime.lg)
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
Spacer(Modifier.height(MaterialTheme.dime.sm))
Text(
text = message,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.height(MaterialTheme.dime.xl))
Row(horizontalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md)) {
OutlinedButton(
onClick = onDismiss,
modifier = Modifier
.weight(1f)
.height(48.dp)
) { Text(dismissText) }
Button(
onClick = onConfirm,
modifier = Modifier
.weight(1f)
.height(48.dp),
colors = ButtonDefaults.buttonColors(
containerColor = confirmColor,
contentColor = MaterialTheme.colorScheme.onError
)
) { Text(confirmText) }
}
Spacer(Modifier.height(MaterialTheme.dime.sm))
}
}
}
}
@@ -0,0 +1,53 @@
package com.approagency.pharmacy.presentation.common
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.material3.MaterialTheme
/**
* افکت درخششِ متحرک (shimmer) برای جایگیرندههای لودینگ.
* رنگها از تم گرفته میشوند تا در حالت تاریک/روشن هماهنگ باشد.
*/
fun Modifier.shimmer(shape: Shape = RoundedCornerShape(8.dp)): Modifier = composed {
var size by remember { mutableStateOf(IntSize.Zero) }
val transition = rememberInfiniteTransition(label = "shimmer")
val startX by transition.animateFloat(
initialValue = -2f * size.width.coerceAtLeast(1),
targetValue = 2f * size.width.coerceAtLeast(1),
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1200),
repeatMode = RepeatMode.Restart
),
label = "shimmerX"
)
val base = MaterialTheme.colorScheme.surfaceVariant
val highlight = MaterialTheme.colorScheme.surfaceContainerHighest
clip(shape)
.background(
brush = Brush.linearGradient(
colors = listOf(base, highlight, base),
start = Offset(startX, 0f),
end = Offset(startX + size.width.toFloat(), size.height.toFloat())
)
)
.onGloballyPositioned { size = it.size }
}