diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/animations/ShineOverlay.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/animations/ShineOverlay.kt new file mode 100644 index 00000000..411609e3 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/animations/ShineOverlay.kt @@ -0,0 +1,76 @@ +package network.bisq.mobile.presentation.ui.components.atoms.animations + +import androidx.compose.animation.core.* +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.* +import kotlin.random.Random + +const val INITIAL_SHINE = -1.0f +const val TARGET_SHINE = 3f +const val ANIMATION_INTERVAL = 5000 +const val ANIMATION_MAX_INTERVAL = 10001 +const val GRADIENT_OFFSET_FACTOR = 300f + +fun nextDuration(): Int = Random.nextInt(ANIMATION_INTERVAL, ANIMATION_MAX_INTERVAL) + +@Composable +fun ShineOverlay( + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit +) { + + val randomDuration = remember { mutableStateOf(nextDuration()) } + + val infiniteTransition = rememberInfiniteTransition() + val gradientOffset by infiniteTransition.animateFloat( + initialValue = INITIAL_SHINE, + targetValue = TARGET_SHINE, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = randomDuration.value, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ) + ) + + LaunchedEffect(gradientOffset) { + if (gradientOffset == TARGET_SHINE) { + randomDuration.value = nextDuration() + } + } + + // Gradient brush that moves across the composable + val gradientBrush = Brush.linearGradient( + colors = listOf( + Color.Transparent, + Color.White.copy(alpha = 0.3f), // Shine effect + Color.Transparent + ), + start = Offset(gradientOffset * GRADIENT_OFFSET_FACTOR, gradientOffset * GRADIENT_OFFSET_FACTOR), + end = Offset((gradientOffset + 1) * GRADIENT_OFFSET_FACTOR, (gradientOffset + 1) * GRADIENT_OFFSET_FACTOR) + ) + + // Layer composable with shine overlay + Box( + modifier = modifier + .clip(CircleShape), + contentAlignment = Alignment.Center + ) { + // UserIcon composable + content() + + // Canvas for the moving shine gradient + Canvas( + modifier = Modifier + .matchParentSize() + .clip(CircleShape)) { + drawRect(brush = gradientBrush) + } + } +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt index a214d9a8..825f2c65 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt @@ -1,5 +1,6 @@ package network.bisq.mobile.presentation.ui.components.molecules +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -17,9 +18,12 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults -import network.bisq.mobile.presentation.ui.components.atoms.icons.BellIcon +import androidx.compose.ui.draw.alpha +import androidx.navigation.compose.currentBackStackEntryAsState +import network.bisq.mobile.presentation.ui.components.atoms.animations.ShineOverlay import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogoSmall import network.bisq.mobile.presentation.ui.components.atoms.icons.UserIcon +import network.bisq.mobile.presentation.ui.navigation.Routes import network.bisq.mobile.presentation.ui.theme.BisqTheme import org.koin.compose.koinInject import org.koin.core.qualifier.named @@ -39,6 +43,9 @@ fun TopBar( ) { val presenter: ITopBarPresenter = koinInject() val navController: NavHostController = presenter.getRootNavController() + val tabNavController: NavHostController = presenter.getRootTabNavController() + + val currentTab = tabNavController.currentBackStackEntryAsState().value?.destination?.route val showBackButton = customBackButton == null && navController.previousBackStackEntry != null @@ -100,7 +107,17 @@ fun TopBar( // TODO implement full feature after MVP // BellIcon() Spacer(modifier = Modifier.width(12.dp)) - UserIcon(presenter.uniqueAvatar.value, modifier = Modifier.size(30.dp)) + ShineOverlay { + UserIcon(presenter.uniqueAvatar.value, + modifier = Modifier.size(30.dp) +// .fillMaxSize() + .alpha(if (currentTab == Routes.TabSettings.name) 0.5f else 1.0f) + .clickable { + if (currentTab != Routes.TabSettings.name) { + navController.navigate(Routes.UserProfileSettings.name) + } + }) + } } }, ) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt index f5333990..4120d17e 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt @@ -16,6 +16,7 @@ enum class Routes(val title: String) { TabCurrencies(title = "tab_currencies"), TabMyTrades(title = "tab_my_trades"), TabSettings(title = "tab_settings"), + UserProfileSettings(title = "user_profile_settings"), OfferList(title = "offer_list"), TakeOfferTradeAmount(title = "take_offer_trade_amount"), TakeOfferPaymentMethod(title = "take_offer_payment_method"), diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt index 7c36d588..4789c9bc 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt @@ -19,6 +19,7 @@ import network.bisq.mobile.presentation.ui.uicases.offers.OffersListScreen import network.bisq.mobile.presentation.ui.uicases.offers.createOffer.* import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.TakeOfferReviewTradeScreen import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.TakeOfferTradeAmountScreen +import network.bisq.mobile.presentation.ui.uicases.settings.UserProfileSettingsScreen import network.bisq.mobile.presentation.ui.uicases.startup.CreateProfileScreen import network.bisq.mobile.presentation.ui.uicases.startup.OnBoardingScreen import network.bisq.mobile.presentation.ui.uicases.startup.SplashScreen @@ -96,6 +97,9 @@ fun RootNavGraph(rootNavController: NavHostController) { CreateOfferReviewOfferScreen() } + composable(Routes.UserProfileSettings.name) { + UserProfileSettingsScreen(showBackNavigation = true) + } } } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/UserProfileSettingsScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/UserProfileSettingsScreen.kt index 2089211d..f26dbd0d 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/UserProfileSettingsScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/UserProfileSettingsScreen.kt @@ -3,9 +3,10 @@ package network.bisq.mobile.presentation.ui.uicases.settings import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment @@ -13,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController import bisqapps.shared.presentation.generated.resources.Res import bisqapps.shared.presentation.generated.resources.img_bitcoin_payment_waiting import kotlinx.coroutines.flow.StateFlow @@ -48,7 +50,7 @@ interface IUserProfileSettingsPresenter: ViewPresenter { } @Composable -fun UserProfileSettingsScreen() { +fun UserProfileSettingsScreen(showBackNavigation: Boolean = false) { val presenter: IUserProfileSettingsPresenter = koinInject() @@ -71,7 +73,7 @@ fun UserProfileSettingsScreen() { Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { - UserProfileScreenHeader(presenter) + UserProfileScreenHeader(presenter, showBackNavigation) Spacer(modifier = Modifier.height(16.dp)) BisqScrollLayout(onModifier = { modifier -> modifier.weight(1f) }) { @@ -124,20 +126,44 @@ fun UserProfileSettingsScreen() { } @Composable -private fun UserProfileScreenHeader(presenter: IUserProfileSettingsPresenter) { +private fun UserProfileScreenHeader(presenter: IUserProfileSettingsPresenter, showBackNavigation: Boolean) { Box( modifier = Modifier - .size(80.dp) - .clip(CircleShape) - .padding(8.dp) .fillMaxWidth() + .padding(8.dp) .background(BisqTheme.colors.dark1), contentAlignment = Alignment.Center ) { - UserIcon( - presenter.uniqueAvatar.value, - modifier = Modifier.size(72.dp) - ) + // Back Button if showBackNavigation is true + if (showBackNavigation) { + IconButton( + onClick = { presenter.getRootNavController().popBackStack() }, + modifier = Modifier + .size(48.dp) + .align(Alignment.CenterStart) // Align to the top-left + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = BisqTheme.colors.light1 + ) + } + } + + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .padding(8.dp) + .fillMaxWidth() + .background(BisqTheme.colors.dark1), + contentAlignment = Alignment.Center + ) { + UserIcon( + presenter.uniqueAvatar.value, + modifier = Modifier.size(72.dp) + ) + } } }