feat: change drawer
This commit is contained in:
@@ -21,8 +21,10 @@ import androidx.compose.material3.rememberDrawerState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.AccountDrawer
|
||||||
import com.approagency.pharmacy.presentation.account.AccountSheet
|
import com.approagency.pharmacy.presentation.account.AccountSheet
|
||||||
import com.approagency.pharmacy.presentation.account.AccountSheetController
|
import com.approagency.pharmacy.presentation.account.AccountSheetController
|
||||||
|
import com.approagency.pharmacy.presentation.common.ConfirmBottomSheet
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ fun MainContainer(
|
|||||||
|
|
||||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
var showLogoutConfirm by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val appVersion = remember {
|
val appVersion = remember {
|
||||||
@@ -68,12 +72,7 @@ fun MainContainer(
|
|||||||
appVersion = appVersion,
|
appVersion = appVersion,
|
||||||
themeMode = themeMode,
|
themeMode = themeMode,
|
||||||
onThemeModeChange = { session.setThemeMode(it) },
|
onThemeModeChange = { session.setThemeMode(it) },
|
||||||
onLogout = {
|
onLogout = { showLogoutConfirm = true }
|
||||||
scope.launch {
|
|
||||||
authRepository.logout()
|
|
||||||
drawerState.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -96,6 +95,22 @@ fun MainContainer(
|
|||||||
if (sheetVisible) {
|
if (sheetVisible) {
|
||||||
AccountSheet(onDismiss = { sheetController.hide() })
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.Logout
|
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||||
@@ -61,7 +62,10 @@ fun AccountDrawer(
|
|||||||
onThemeModeChange: (ThemeMode) -> Unit,
|
onThemeModeChange: (ThemeMode) -> Unit,
|
||||||
onLogout: () -> Unit
|
onLogout: () -> Unit
|
||||||
) {
|
) {
|
||||||
ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surface) {
|
ModalDrawerSheet(
|
||||||
|
modifier = Modifier.width(280.dp),
|
||||||
|
drawerContainerColor = MaterialTheme.colorScheme.surface
|
||||||
|
) {
|
||||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@@ -37,9 +38,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.approagency.pharmacy.MainActivity
|
import com.approagency.pharmacy.MainActivity
|
||||||
import com.approagency.pharmacy.domain.model.SubscriptionProduct
|
import com.approagency.pharmacy.domain.model.SubscriptionProduct
|
||||||
import com.approagency.pharmacy.presentation.common.CustomModalBottomSheet
|
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.OtpTextField
|
||||||
import com.approagency.pharmacy.presentation.common.PrimaryButton
|
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.AccountPhase
|
||||||
import com.approagency.pharmacy.presentation.viewModel.AccountViewModel
|
import com.approagency.pharmacy.presentation.viewModel.AccountViewModel
|
||||||
import com.vada.caller.ui.theme.dime
|
import com.vada.caller.ui.theme.dime
|
||||||
@@ -147,11 +148,11 @@ fun AccountSheet(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.height(MaterialTheme.dime.lg))
|
Spacer(Modifier.height(MaterialTheme.dime.lg))
|
||||||
when {
|
when {
|
||||||
ui.productsLoading -> Loading(
|
ui.productsLoading -> Column(
|
||||||
modifier = Modifier
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md)
|
||||||
.fillMaxWidth()
|
) {
|
||||||
.height(160.dp)
|
repeat(2) { ProductCardSkeleton() }
|
||||||
)
|
}
|
||||||
else -> Column(
|
else -> Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md)
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md)
|
||||||
) {
|
) {
|
||||||
@@ -182,8 +183,6 @@ fun AccountSheet(
|
|||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
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 =
|
private fun formatPrice(price: Long): String =
|
||||||
price.toString().reversed().chunked(3).joinToString(",").reversed()
|
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 }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user