diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 81b164d5..13e845e6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -69,7 +69,7 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", ) - signingConfig = signingConfigs.getByName(Constants.RELEASE) + signingConfig = signingConfigs.getByName("debug") resValue("string", "provider", "\"${Constants.APP_NAME}.provider\"") } @@ -161,6 +161,7 @@ dependencies { implementation(libs.material) implementation(libs.androidx.storage) + implementation("androidx.compose.animation:animation-graphics:1.9.2") implementation("androidx.navigation3:navigation3-runtime:1.0.0-alpha10") implementation("androidx.navigation3:navigation3-ui:1.0.0-alpha10") implementation(libs.androidx.lifecycle.viewmodel.navigation3) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/animations/AnimatedFloatIcon.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/animations/AnimatedFloatIcon.kt new file mode 100644 index 00000000..3e9f1815 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/animations/AnimatedFloatIcon.kt @@ -0,0 +1,44 @@ +package com.zaneschepke.wireguardautotunnel.ui.common.animations + +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.vector.ImageVector + +@Composable +fun AnimatedFloatIcon( + inactiveIcon: ImageVector, + activeIcon: ImageVector, + isSelected: Boolean, + modifier: Modifier, +) { + + val transition = updateTransition(isSelected, label = null) + val scale by + transition.animateFloat(transitionSpec = { tween(250, easing = FastOutSlowInEasing) }) { + selected -> + if (selected) 1.2f else 1f + } + + val rotation by + animateFloatAsState(targetValue = 0f, animationSpec = tween(400, easing = LinearEasing)) + + Crossfade(targetState = isSelected, animationSpec = tween(250, easing = FastOutSlowInEasing)) { + selected -> + Icon( + imageVector = if (selected) activeIcon else inactiveIcon, + contentDescription = null, + modifier = modifier.scale(scale).rotate(rotation), + ) + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt index c1dce2e2..d8d514b7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt @@ -8,6 +8,7 @@ import androidx.compose.material.icons.filled.QuestionMark import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.Bolt import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.QuestionMark import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.rounded.Bolt import androidx.compose.material.icons.rounded.Home @@ -80,13 +81,20 @@ sealed class Route : NavKey { enum class Tab( val startRoute: Route, val titleRes: Int, - val icon: androidx.compose.ui.graphics.vector.ImageVector, + val inactiveIcon: androidx.compose.ui.graphics.vector.ImageVector, + val activeIcon: androidx.compose.ui.graphics.vector.ImageVector, val index: Int, ) { - TUNNELS(Route.Tunnels, R.string.tunnels, Icons.Rounded.Home, 0), - AUTOTUNNEL(Route.AutoTunnel, R.string.auto_tunnel, Icons.Rounded.Bolt, 1), - SETTINGS(Route.Settings, R.string.settings, Icons.Rounded.Settings, 2), - SUPPORT(Route.Support, R.string.support, Icons.Rounded.QuestionMark, 3); + TUNNELS(Route.Tunnels, R.string.tunnels, Icons.Outlined.Home, Icons.Filled.Home, 0), + AUTOTUNNEL(Route.AutoTunnel, R.string.auto_tunnel, Icons.Outlined.Bolt, Icons.Filled.Bolt, 1), + SETTINGS(Route.Settings, R.string.settings, Icons.Outlined.Settings, Icons.Filled.Settings, 2), + SUPPORT( + Route.Support, + R.string.support, + Icons.Outlined.QuestionMark, + Icons.Filled.QuestionMark, + 3, + ); companion object { fun fromRoute(route: Route): Tab = diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt index fe5f9821..37b76f09 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt @@ -1,5 +1,7 @@ package com.zaneschepke.wireguardautotunnel.ui.navigation.components +import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -10,8 +12,8 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.zaneschepke.wireguardautotunnel.ui.common.animations.AnimatedFloatIcon import com.zaneschepke.wireguardautotunnel.ui.navigation.Tab import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree @@ -28,6 +30,7 @@ fun BottomNavbar(isAutoTunnelActive: Boolean, currentTab: Tab, onTabSelected: (T val interactionSource = remember { MutableInteractionSource() } val isSelected = currentTab == tab val hasBadge = tab == Tab.AUTOTUNNEL && isAutoTunnelActive + IconButton( onClick = { onTabSelected(tab) }, colors = @@ -54,15 +57,19 @@ fun BottomNavbar(isAutoTunnelActive: Boolean, currentTab: Tab, onTabSelected: (T ) } ) { - Icon( - imageVector = tab.icon, - contentDescription = stringResource(tab.titleRes), + AnimatedFloatIcon( + activeIcon = tab.activeIcon, + inactiveIcon = tab.inactiveIcon, + isSelected = isSelected, + modifier = Modifier.size(24.dp), ) } } else { - Icon( - imageVector = tab.icon, - contentDescription = stringResource(tab.titleRes), + AnimatedFloatIcon( + activeIcon = tab.activeIcon, + inactiveIcon = tab.inactiveIcon, + isSelected = isSelected, + modifier = Modifier.size(24.dp), ) } }