diff --git a/app/src/main/java/me/rezapour/intervaltimer/MainActivity.kt b/app/src/main/java/me/rezapour/intervaltimer/MainActivity.kt
index e5b8be7..a7cbdd4 100644
--- a/app/src/main/java/me/rezapour/intervaltimer/MainActivity.kt
+++ b/app/src/main/java/me/rezapour/intervaltimer/MainActivity.kt
@@ -4,7 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import me.rezapour.designsystem.theme.IntervalTimerTheme
+import me.rezapour.designsystem.theme.IniTheme
import me.rezapour.intervaltimer.compose.AppRoot
class MainActivity : ComponentActivity() {
@@ -12,7 +12,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
- IntervalTimerTheme {
+ IniTheme {
AppRoot()
}
}
diff --git a/app/src/main/java/me/rezapour/intervaltimer/compose/AppRoot.kt b/app/src/main/java/me/rezapour/intervaltimer/compose/AppRoot.kt
index 121f9e5..4e5406c 100644
--- a/app/src/main/java/me/rezapour/intervaltimer/compose/AppRoot.kt
+++ b/app/src/main/java/me/rezapour/intervaltimer/compose/AppRoot.kt
@@ -22,6 +22,6 @@ fun AppRoot() {
backStack.add(AddTimerScreen)
})
}
- addTimerScreen()
+ addTimerScreen(backStack)
})
}
\ No newline at end of file
diff --git a/app/src/main/java/me/rezapour/intervaltimer/compose/MainScreen.kt b/app/src/main/java/me/rezapour/intervaltimer/compose/MainScreen.kt
index 7d8047a..4a8329e 100644
--- a/app/src/main/java/me/rezapour/intervaltimer/compose/MainScreen.kt
+++ b/app/src/main/java/me/rezapour/intervaltimer/compose/MainScreen.kt
@@ -3,11 +3,10 @@ package me.rezapour.intervaltimer.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import me.rezapour.designsystem.components.button.IniButtonPicker
@Composable
@@ -17,9 +16,9 @@ fun MainScreen(
Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.align(Alignment.Center)) {
- Button(onClick = {
+ IniButtonPicker (onClick = {
onAddTimerClicked()
- }) { Text(text = "AddTimer") }
+ })
}
}
diff --git a/core/common/src/main/java/me/rezapour/common/extentionfunctions/StringExtensionFunctions.kt b/core/common/src/main/java/me/rezapour/common/extentionfunctions/StringExtensionFunctions.kt
deleted file mode 100644
index a6ba7c6..0000000
--- a/core/common/src/main/java/me/rezapour/common/extentionfunctions/StringExtensionFunctions.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package me.rezapour.common.extentionfunctions
-
-fun String.toIntOrZero(): Int = this.toIntOrNull() ?: 0
-
-fun String.toLongOrZero(): Long = this.toLongOrNull() ?: 0L
-
-fun String.digitOnly(maxLen: Int? = null): String {
- val digits = this.filter { it.isDigit() }
- return if (maxLen != null) digits.take(maxLen) else digits
-}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/me/rezapour/designsystem/components/button/IniButton.kt b/core/designsystem/src/main/java/me/rezapour/designsystem/components/button/IniButton.kt
new file mode 100644
index 0000000..c9e18fe
--- /dev/null
+++ b/core/designsystem/src/main/java/me/rezapour/designsystem/components/button/IniButton.kt
@@ -0,0 +1,54 @@
+package me.rezapour.designsystem.components.button
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import me.rezapour.designsystem.theme.IniTheme
+import me.rezapour.designsystem.util.IniPreview
+import me.rezapour.designsystem.R as res
+
+
+@Composable
+fun IniButtonPicker(
+ modifier: Modifier = Modifier,
+ increaseMode: Boolean = true,
+ onClick: () -> Unit
+) {
+
+ IconButton(
+ modifier = modifier
+ .size(54.dp)
+ .clip(RoundedCornerShape(IniTheme.appShapes.medium))
+ .background(
+ color = IniTheme.colors.primaryContainer,
+ ),
+ onClick = onClick
+ ) {
+ Icon(
+ painter = if (increaseMode)
+ painterResource(res.drawable.ic_plus)
+ else
+ painterResource(res.drawable.ic_minus),
+ contentDescription = null,
+ tint = IniTheme.colors.primary,
+ )
+ }
+}
+
+@IniPreview
+@Composable
+fun IniButtonPreview() {
+ IniTheme() {
+ IniButtonPicker(increaseMode = true) {
+
+ }
+ }
+
+}
diff --git a/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Color.kt b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Color.kt
index 6df8e9e..3ebb546 100644
--- a/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Color.kt
+++ b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Color.kt
@@ -2,10 +2,46 @@ package me.rezapour.designsystem.theme
import androidx.compose.ui.graphics.Color
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFFEFB8C8)
+// Brand / Accent palette
+val GreenPrimary = Color(0xFF22C55E)
+val GreenPrimaryDark = Color(0xFF15803D)
+val GreenContainerDark = Color(0xFF163320)
+val GreenContainerLight = Color(0xFFDCFCE7)
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
+val OrangeSecondary = Color(0xFFF97316)
+val OrangeSecondaryDark = Color(0xFFC2410C)
+val OrangeContainerDark = Color(0xFF3A2314)
+val OrangeContainerLight = Color(0xFFFFE0CC)
+
+val SkyTertiary = Color(0xFF0EA5E9)
+val SkyTertiaryDark = Color(0xFF0369A1)
+val SkyContainerDark = Color(0xFF132C3A)
+val SkyContainerLight = Color(0xFFE0F2FE)
+
+// Neutral dark surfaces
+val BackgroundDark = Color(0xFF0F172A)
+val SurfaceDark = Color(0xFF1E293B)
+val SurfaceVariantDark = Color(0xFF1A2236)
+val OutlineDark = Color(0xFF334155)
+val OutlineVariantDark = Color(0xFF475569)
+
+// Neutral light surfaces
+val BackgroundLight = Color(0xFFF8FAFC)
+val SurfaceLight = Color(0xFFFFFFFF)
+val SurfaceVariantLight = Color(0xFFE2E8F0)
+val OutlineLight = Color(0xFF94A3B8)
+val OutlineVariantLight = Color(0xFFCBD5E1)
+
+// Text / content
+val OnDark = Color(0xFFF8FAFC)
+val OnLight = Color(0xFF0F172A)
+val MutedDark = Color(0xFF94A3B8)
+val MutedLight = Color(0xFF64748B)
+
+// Error
+val ErrorLight = Color(0xFFB3261E)
+val ErrorDark = Color(0xFFFFB4AB)
+val ErrorContainerLight = Color(0xFFF9DEDC)
+val ErrorContainerDark = Color(0xFF8C1D18)
+val OnErrorLight = Color(0xFFFFFFFF)
+val OnErrorDark = Color(0xFF690005)
diff --git a/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Theme.kt b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Theme.kt
index 897174f..04c4e8a 100644
--- a/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Theme.kt
+++ b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Theme.kt
@@ -1,42 +1,93 @@
package me.rezapour.designsystem.theme
import android.os.Build
-import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Shapes
+import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
+ primary = GreenPrimary,
+ onPrimary = OnLight,
+ primaryContainer = GreenContainerDark,
+ onPrimaryContainer = GreenContainerLight,
+
+ secondary = OrangeSecondary,
+ onSecondary = OnLight,
+ secondaryContainer = OrangeContainerDark,
+ onSecondaryContainer = Color(0xFFFFDCC2),
+
+ tertiary = SkyTertiary,
+ onTertiary = OnLight,
+ tertiaryContainer = SkyContainerDark,
+ onTertiaryContainer = Color(0xFFBFE9FF),
+
+ background = BackgroundDark,
+ onBackground = OnDark,
+
+ surface = SurfaceDark,
+ onSurface = OnDark,
+
+ surfaceVariant = SurfaceVariantDark,
+ onSurfaceVariant = MutedDark,
+
+ outline = OutlineDark,
+ outlineVariant = OutlineVariantDark,
+
+ error = ErrorDark,
+ onError = OnErrorDark,
+ errorContainer = ErrorContainerDark,
+ onErrorContainer = Color(0xFFFFDAD6),
)
private val LightColorScheme = lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40
-
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
+ primary = GreenPrimaryDark,
onPrimary = Color.White,
+ primaryContainer = GreenContainerLight,
+ onPrimaryContainer = Color(0xFF052E16),
+
+ secondary = OrangeSecondaryDark,
onSecondary = Color.White,
+ secondaryContainer = OrangeContainerLight,
+ onSecondaryContainer = Color(0xFF431407),
+
+ tertiary = SkyTertiaryDark,
onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
+ tertiaryContainer = SkyContainerLight,
+ onTertiaryContainer = Color(0xFF082F49),
+
+ background = BackgroundLight,
+ onBackground = OnLight,
+
+ surface = SurfaceLight,
+ onSurface = OnLight,
+
+ surfaceVariant = SurfaceVariantLight,
+ onSurfaceVariant = MutedLight,
+
+ outline = OutlineLight,
+ outlineVariant = OutlineVariantLight,
+
+ error = ErrorLight,
+ onError = OnErrorLight,
+ errorContainer = ErrorContainerLight,
+ onErrorContainer = Color(0xFF410E0B),
)
@Composable
-fun IntervalTimerTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
+fun IniTheme(
+ darkTheme: Boolean = true,
// Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
+ dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
@@ -49,9 +100,38 @@ fun IntervalTimerTheme(
else -> LightColorScheme
}
- MaterialTheme(
- colorScheme = colorScheme,
- typography = Typography,
- content = content
- )
-}
\ No newline at end of file
+ val spacing = Spacing()
+ val appShapes = AppShapes()
+ val sizes = Sizes()
+
+ CompositionLocalProvider(
+ LocalSpacing provides spacing,
+ LocalShapes provides appShapes,
+ LocalSizes provides sizes
+ ) {
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+ }
+}
+
+object IniTheme {
+ val spacing: Spacing
+ @Composable @ReadOnlyComposable get() = LocalSpacing.current
+ val sizes: Sizes
+ @Composable @ReadOnlyComposable get() = LocalSizes.current
+
+ val appShapes: AppShapes
+ @Composable @ReadOnlyComposable get() = LocalShapes.current
+
+ val colors: ColorScheme
+ @Composable @ReadOnlyComposable get() = MaterialTheme.colorScheme
+
+ val typography: Typography
+ @Composable @ReadOnlyComposable get() = MaterialTheme.typography
+
+ val shapes: Shapes
+ @Composable @ReadOnlyComposable get() = MaterialTheme.shapes
+}
diff --git a/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Tokens.kt b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Tokens.kt
new file mode 100644
index 0000000..165dc85
--- /dev/null
+++ b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Tokens.kt
@@ -0,0 +1,34 @@
+package me.rezapour.designsystem.theme
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+val LocalSpacing = staticCompositionLocalOf { Spacing() }
+val LocalShapes = staticCompositionLocalOf { AppShapes() }
+val LocalSizes = staticCompositionLocalOf { Sizes() }
+
+@Immutable
+data class Spacing(
+ val xs: Dp = 4.dp,
+ val s: Dp = 8.dp,
+ val m: Dp = 16.dp,
+ val l: Dp = 24.dp,
+ val xl: Dp = 32.dp
+)
+
+@Immutable
+data class AppShapes(
+ val extraLarge: Dp = 24.dp,
+ val large: Dp = 16.dp,
+ val medium: Dp = 12.dp,
+ val circle: Float = 0.5f // 50%
+)
+
+@Immutable
+data class Sizes(
+ val touchTarget: Dp = 48.dp,
+ val primaryFab: Dp = 80.dp,
+ val secondaryFab: Dp = 64.dp
+)
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Type.kt b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Type.kt
index 0dc411c..56ebc2b 100644
--- a/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Type.kt
+++ b/core/designsystem/src/main/java/me/rezapour/designsystem/theme/Type.kt
@@ -2,33 +2,59 @@ package me.rezapour.designsystem.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
+import me.rezapour.designsystem.R
// Set of Material typography styles to start with
+
+val InterFontFamily = FontFamily(
+ Font(R.font.inter, FontWeight.Normal),
+ Font(R.font.inter_semibold, FontWeight.SemiBold),
+ Font(R.font.inter_bold, FontWeight.Bold),
+ Font(R.font.inter_extrabold, FontWeight.ExtraBold)
+)
+
+val JetBrainsMonoFontFamily = FontFamily(
+ Font(R.font.jetbrains_mono_extrabold, FontWeight.ExtraBold)
+)
val Typography = Typography(
- bodyLarge = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 16.sp,
- lineHeight = 24.sp,
- letterSpacing = 0.5.sp
- )
- /* Other default text styles to override
+ displayLarge = TextStyle(
+ fontFamily = JetBrainsMonoFontFamily,
+ fontWeight = FontWeight.ExtraBold,
+ fontSize = 72.sp
+ ),
+ displaySmall = TextStyle(
+ fontFamily = JetBrainsMonoFontFamily,
+ fontWeight = FontWeight.ExtraBold,
+ fontSize = 42.sp
+ ),
+ headlineLarge = TextStyle(
+ fontFamily = InterFontFamily,
+ fontWeight = FontWeight.ExtraBold,
+ fontSize = 32.sp
+ ),
+ headlineMedium = TextStyle(
+ fontFamily = InterFontFamily,
+ fontWeight = FontWeight.Bold,
+ fontSize = 24.sp
+ ),
titleLarge = TextStyle(
- fontFamily = FontFamily.Default,
+ fontFamily = InterFontFamily,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 20.sp
+ ),
+ bodyMedium = TextStyle(
+ fontFamily = InterFontFamily,
fontWeight = FontWeight.Normal,
- fontSize = 22.sp,
- lineHeight = 28.sp,
- letterSpacing = 0.sp
+ fontSize = 14.sp
),
labelSmall = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Medium,
+ fontFamily = InterFontFamily,
+ fontWeight = FontWeight.SemiBold,
fontSize = 11.sp,
- lineHeight = 16.sp,
- letterSpacing = 0.5.sp
+ letterSpacing = 0.3.sp
)
- */
)
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/me/rezapour/designsystem/util/IniPreview.kt b/core/designsystem/src/main/java/me/rezapour/designsystem/util/IniPreview.kt
new file mode 100644
index 0000000..10da7fa
--- /dev/null
+++ b/core/designsystem/src/main/java/me/rezapour/designsystem/util/IniPreview.kt
@@ -0,0 +1,26 @@
+package me.rezapour.designsystem.util
+
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import androidx.compose.ui.tooling.preview.Preview
+
+@Preview(
+ name = "LightMode",
+ group = "LightMode",
+ showBackground = true,
+ uiMode = UI_MODE_NIGHT_NO
+)
+internal annotation class LightMode
+
+@Preview(
+ name = "DarkMode",
+ group = "LightMode",
+ showBackground = true,
+ uiMode = UI_MODE_NIGHT_YES
+)
+internal annotation class DarkMode
+
+
+@LightMode
+@DarkMode
+annotation class IniPreview
\ No newline at end of file
diff --git a/core/designsystem/src/main/res/drawable/ic_minus.xml b/core/designsystem/src/main/res/drawable/ic_minus.xml
new file mode 100644
index 0000000..6c40605
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_minus.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_plus.xml b/core/designsystem/src/main/res/drawable/ic_plus.xml
new file mode 100644
index 0000000..6d98b23
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_plus.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/font/inter.ttf b/core/designsystem/src/main/res/font/inter.ttf
new file mode 100644
index 0000000..ce0f305
Binary files /dev/null and b/core/designsystem/src/main/res/font/inter.ttf differ
diff --git a/core/designsystem/src/main/res/font/inter_bold.ttf b/core/designsystem/src/main/res/font/inter_bold.ttf
new file mode 100644
index 0000000..713c476
Binary files /dev/null and b/core/designsystem/src/main/res/font/inter_bold.ttf differ
diff --git a/core/designsystem/src/main/res/font/inter_extrabold.ttf b/core/designsystem/src/main/res/font/inter_extrabold.ttf
new file mode 100644
index 0000000..2c0298b
Binary files /dev/null and b/core/designsystem/src/main/res/font/inter_extrabold.ttf differ
diff --git a/core/designsystem/src/main/res/font/inter_semibold.ttf b/core/designsystem/src/main/res/font/inter_semibold.ttf
new file mode 100644
index 0000000..60e9041
Binary files /dev/null and b/core/designsystem/src/main/res/font/inter_semibold.ttf differ
diff --git a/core/designsystem/src/main/res/font/jetbrains_mono_extrabold.ttf b/core/designsystem/src/main/res/font/jetbrains_mono_extrabold.ttf
new file mode 100644
index 0000000..31d2ed2
Binary files /dev/null and b/core/designsystem/src/main/res/font/jetbrains_mono_extrabold.ttf differ
diff --git a/core/ui/.gitignore b/core/ui/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/ui/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
new file mode 100644
index 0000000..00070d8
--- /dev/null
+++ b/core/ui/build.gradle.kts
@@ -0,0 +1,42 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "me.rezapour.ui"
+ compileSdk = 36
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+}
\ No newline at end of file
diff --git a/core/ui/consumer-rules.pro b/core/ui/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/ui/proguard-rules.pro b/core/ui/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/ui/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/ui/src/androidTest/java/me/rezapour/ui/ExampleInstrumentedTest.kt b/core/ui/src/androidTest/java/me/rezapour/ui/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..0b653c4
--- /dev/null
+++ b/core/ui/src/androidTest/java/me/rezapour/ui/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package me.rezapour.ui
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("me.rezapour.ui.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/AndroidManifest.xml b/core/ui/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/core/ui/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/ui/src/main/java/me/rezapour/ui/formatter/TimerDurationFormatter.kt b/core/ui/src/main/java/me/rezapour/ui/formatter/TimerDurationFormatter.kt
new file mode 100644
index 0000000..af6ca73
--- /dev/null
+++ b/core/ui/src/main/java/me/rezapour/ui/formatter/TimerDurationFormatter.kt
@@ -0,0 +1,14 @@
+package me.rezapour.ui.formatter
+
+object TimerDurationFormatter {
+ fun formatForPicker(seconds:Long):String {
+ val mins = seconds / 60
+ val seconds = seconds % 60
+
+ return when {
+ mins != 0L && seconds != 0L -> "${mins}min\n${seconds}s"
+ mins != 0L -> "${mins}min"
+ else -> "${seconds}s"
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/test/java/me/rezapour/ui/ExampleUnitTest.kt b/core/ui/src/test/java/me/rezapour/ui/ExampleUnitTest.kt
new file mode 100644
index 0000000..218c9f3
--- /dev/null
+++ b/core/ui/src/test/java/me/rezapour/ui/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package me.rezapour.ui
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/feature/add-timer/build.gradle.kts b/feature/add-timer/build.gradle.kts
index 53e4370..0ba5f04 100644
--- a/feature/add-timer/build.gradle.kts
+++ b/feature/add-timer/build.gradle.kts
@@ -42,6 +42,7 @@ dependencies {
implementation(project(":core:domain"))
implementation(project(":core:designsystem"))
implementation(project(":core:common"))
+ implementation(project(":core:ui"))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
diff --git a/feature/add-timer/src/main/java/me/rezapour/add_timer/compose/AddTimerScreen.kt b/feature/add-timer/src/main/java/me/rezapour/add_timer/compose/AddTimerScreen.kt
index 762d5a9..5503ee1 100644
--- a/feature/add-timer/src/main/java/me/rezapour/add_timer/compose/AddTimerScreen.kt
+++ b/feature/add-timer/src/main/java/me/rezapour/add_timer/compose/AddTimerScreen.kt
@@ -6,19 +6,21 @@ package me.rezapour.add_timer.compose
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
@@ -28,39 +30,57 @@ import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import kotlinx.coroutines.launch
-import me.rezapour.add_timer.viewmodel.AddTimerUiEvent
+import me.rezapour.add_timer.viewmodel.AddTimerAction
+import me.rezapour.add_timer.viewmodel.AddTimerUiEffect
import me.rezapour.add_timer.viewmodel.AddTimerUiState
import me.rezapour.add_timer.viewmodel.AddTimerViewModel
-import me.rezapour.designsystem.theme.IntervalTimerTheme
+import me.rezapour.designsystem.components.button.IniButtonPicker
+import me.rezapour.designsystem.theme.IniTheme
+import me.rezapour.designsystem.util.IniPreview
+import me.rezapour.ui.formatter.TimerDurationFormatter
import org.koin.compose.viewmodel.koinViewModel
@Composable
-fun AddTimerScreen(viewModel: AddTimerViewModel = koinViewModel()) {
+fun AddTimerScreen(viewModel: AddTimerViewModel = koinViewModel(), onNavigationBack: () -> Unit) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
+ val snackbarHost = remember { SnackbarHostState() }
- AddTimerContent(uiState) { event ->
- viewModel.updateUiEvent(event)
+
+
+ LaunchedEffect(viewModel) {
+ viewModel.uiEffect.collect { effect ->
+ when (effect) {
+ AddTimerUiEffect.NavigationBack -> onNavigationBack()
+ is AddTimerUiEffect.ShowSnackBar -> snackbarHost.showSnackbar(effect.errorMessage)
+ }
+ }
}
+
+ AddTimerContent(
+ uiState = uiState,
+ snackBarHostState = snackbarHost,
+ onAction = viewModel::onAction,
+ onNavigationBack = onNavigationBack
+ )
}
@Composable
fun AddTimerContent(
uiState: AddTimerUiState,
- uiEvent: (AddTimerUiEvent) -> Unit
+ snackBarHostState: SnackbarHostState,
+ onAction: (AddTimerAction) -> Unit,
+ onNavigationBack: () -> Unit
) {
- val snackbarHostState = remember { SnackbarHostState() }
- val scope = rememberCoroutineScope()
+
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
@@ -69,17 +89,20 @@ fun AddTimerContent(
Text("Add Timer")
},
navigationIcon = {
- Icon(
- imageVector = Icons.Filled.ArrowBackIosNew,
- modifier = Modifier,
- tint = MaterialTheme.colorScheme.primary,
- contentDescription = null,
- )
+ IconButton(onClick = onNavigationBack) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBackIosNew,
+ modifier = Modifier,
+ tint = MaterialTheme.colorScheme.primary,
+ contentDescription = null,
+ )
+ }
+
}
)
},
snackbarHost = {
- SnackbarHost(hostState = snackbarHostState)
+ SnackbarHost(hostState = snackBarHostState)
}
) {
Content(
@@ -87,15 +110,9 @@ fun AddTimerContent(
.padding(top = it.calculateTopPadding())
.navigationBarsPadding(),
uiState = uiState,
- uiEvent = uiEvent
+ uiEvent = onAction
)
- if (uiState.errorMessage != null)
- LaunchedEffect(Unit) {
- scope.launch {
- snackbarHostState.showSnackbar(uiState.errorMessage)
- }
- }
}
}
@@ -104,14 +121,13 @@ fun AddTimerContent(
fun Content(
modifier: Modifier,
uiState: AddTimerUiState,
- uiEvent: (AddTimerUiEvent) -> Unit
+ uiEvent: (AddTimerAction) -> Unit
) {
-
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
- verticalArrangement = Arrangement.spacedBy(16.dp),
+ verticalArrangement = Arrangement.spacedBy(IniTheme.spacing.m),
horizontalAlignment = Alignment.CenterHorizontally
) {
@@ -126,115 +142,84 @@ fun Content(
TextField(
value = uiState.name ?: "",
onValueChange = {
- uiEvent(AddTimerUiEvent.OnNameChanged(it))
+ uiEvent(AddTimerAction.OnNameChanged(it))
},
singleLine = true,
)
}
+ Spacer(modifier = Modifier.height(IniTheme.spacing.xl))
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(
- modifier = Modifier.width(80.dp),
- text = "Timer"
- )
+ Picker(
+ title = "Run",
+ value = TimerDurationFormatter.formatForPicker(uiState.workoutSecond),
+ increaseValue = { uiEvent(AddTimerAction.OnWorkoutIncreased) },
+ decreaseValue = { uiEvent(AddTimerAction.OnWorkoutDecreased) }
+ )
- TextField(
- modifier = Modifier.size(60.dp),
- value = uiState.workMin,
- onValueChange = {
- uiEvent(AddTimerUiEvent.OnWorkMinChanged(it))
- },
- maxLines = 1,
- singleLine = true,
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
- label = {
- Text("Min")
- }
- )
- Text(":")
- TextField(
- modifier = Modifier.size(60.dp),
- value = uiState.workSec,
- onValueChange = {
- uiEvent(AddTimerUiEvent.OnWorkSecChanged(it))
- },
- maxLines = 1,
- singleLine = true,
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
- label = {
- Text("Sec")
- }
- )
+ Spacer(modifier = Modifier.height(IniTheme.spacing.xl))
- }
+ Picker(
+ title = "Rest",
+ value = TimerDurationFormatter.formatForPicker(uiState.restSecond),
+ increaseValue = { uiEvent(AddTimerAction.OnRestIncreased) },
+ decreaseValue = { uiEvent(AddTimerAction.OnRestDecreased) }
+ )
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(
- modifier = Modifier.width(80.dp),
- text = "Interval"
- )
+ Spacer(modifier = Modifier.height(IniTheme.spacing.xl))
- TextField(
- modifier = Modifier.size(60.dp),
- value = uiState.restMin,
- onValueChange = {
- uiEvent(AddTimerUiEvent.OnRestMinChanged(it))
- },
- maxLines = 1,
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
- label = {
- Text("Min")
- }
- )
- Text(":")
- TextField(
- modifier = Modifier.size(60.dp),
- value = uiState.restSec,
- onValueChange = {
- uiEvent(AddTimerUiEvent.OnRestSecChanged(it))
- },
- maxLines = 1,
- singleLine = true,
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
- label = {
- Text("Sec")
- }
- )
+ Picker(
+ title = "Round",
+ value = uiState.rounds.toString(),
+ increaseValue = { uiEvent(AddTimerAction.OnRoundIncreased) },
+ decreaseValue = { uiEvent(AddTimerAction.OnRoundDecreased) }
+ )
+ Spacer(modifier = Modifier.height(IniTheme.spacing.xl))
+
+ Button(onClick = {
+ uiEvent(AddTimerAction.SaveTimer)
+ }) {
+ Text("Save")
}
+ }
+}
+@Composable
+fun Picker(
+ modifier: Modifier = Modifier,
+ title: String,
+ value: String,
+ increaseValue: () -> Unit,
+ decreaseValue: () -> Unit
+) {
+
+ Column(
+ modifier = modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = title,
+ style = IniTheme.typography.titleLarge
+ )
+ Spacer(modifier = Modifier.height(IniTheme.spacing.s))
Row(
modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
+ IniButtonPicker(increaseMode = false) { decreaseValue() }
Text(
- modifier = Modifier.width(80.dp),
- text = "Set"
- )
-
- TextField(
- modifier = Modifier.size(60.dp),
- value = uiState.rounds,
- onValueChange = {
- uiEvent(AddTimerUiEvent.OnRoundsChanged(it))
- },
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
- maxLines = 1,
- singleLine = true,
+ modifier = Modifier
+ .padding(start = IniTheme.spacing.s, end = IniTheme.spacing.s)
+ .sizeIn(minWidth = 80.dp),
+ text = value,
+ textAlign = TextAlign.Center,
+ color = IniTheme.colors.onPrimaryContainer,
+ style = IniTheme.typography.displaySmall
)
- }
-
- Button(onClick = {
- uiEvent(AddTimerUiEvent.SaveTimer)
- }) {
- Text("Save")
+ IniButtonPicker { increaseValue() }
}
@@ -246,7 +231,20 @@ fun Content(
@Preview
@Composable
fun AddTimerContentPreview() {
- IntervalTimerTheme {
- AddTimerScreen()
+ IniTheme {
+ AddTimerContent(AddTimerUiState(), SnackbarHostState(), onAction = {}, onNavigationBack = {})
+ }
+}
+
+@IniPreview
+@Composable
+fun PickerPreview() {
+ IniTheme {
+ Picker(
+ title = "run",
+ value = "2 min",
+ increaseValue = {},
+ decreaseValue = {},
+ )
}
}
\ No newline at end of file
diff --git a/feature/add-timer/src/main/java/me/rezapour/add_timer/navigation/AddTimerNavigation.kt b/feature/add-timer/src/main/java/me/rezapour/add_timer/navigation/AddTimerNavigation.kt
index 1b91351..76f4924 100644
--- a/feature/add-timer/src/main/java/me/rezapour/add_timer/navigation/AddTimerNavigation.kt
+++ b/feature/add-timer/src/main/java/me/rezapour/add_timer/navigation/AddTimerNavigation.kt
@@ -1,12 +1,16 @@
package me.rezapour.add_timer.navigation
+import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.navigation3.runtime.EntryProviderScope
import me.rezapour.add_timer.compose.AddTimerScreen
data object AddTimerScreen
-fun EntryProviderScope.addTimerScreen() {
+fun EntryProviderScope.addTimerScreen(backStack: SnapshotStateList) {
entry {
- AddTimerScreen()
+
+ AddTimerScreen(onNavigationBack = {
+ backStack.removeLastOrNull()
+ })
}
}
\ No newline at end of file
diff --git a/feature/add-timer/src/main/java/me/rezapour/add_timer/viewmodel/AddTimerViewModel.kt b/feature/add-timer/src/main/java/me/rezapour/add_timer/viewmodel/AddTimerViewModel.kt
index 6eeabf9..f152e7d 100644
--- a/feature/add-timer/src/main/java/me/rezapour/add_timer/viewmodel/AddTimerViewModel.kt
+++ b/feature/add-timer/src/main/java/me/rezapour/add_timer/viewmodel/AddTimerViewModel.kt
@@ -2,14 +2,15 @@ package me.rezapour.add_timer.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-import me.rezapour.common.extentionfunctions.digitOnly
-import me.rezapour.common.extentionfunctions.toIntOrZero
-import me.rezapour.common.extentionfunctions.toLongOrZero
import me.rezapour.domain.model.Timer
import me.rezapour.domain.usecase.InsertTimerUseCase
@@ -18,94 +19,147 @@ class AddTimerViewModel(private val insertUseCase: InsertTimerUseCase) : ViewMod
private val _uiState: MutableStateFlow = MutableStateFlow(AddTimerUiState())
val uiState: StateFlow = _uiState.asStateFlow()
- fun saveTimer() {
+ private val _uiEffect: MutableSharedFlow = MutableSharedFlow()
+ val uiEffect: SharedFlow = _uiEffect.asSharedFlow()
+
+ private fun saveTimer() {
val state = uiState.value
- if (state.name == null) {
+ if (state.name.isBlank()) {
showError("Name can't be empty")
return
}
- val workSeconds = minuteSecondsToTotalSeconds(state.workMin, state.workSec)
- val restSeconds = minuteSecondsToTotalSeconds(state.restMin, state.restSec)
val timer = Timer(
name = state.name,
- workSeconds = workSeconds,
- restSeconds = restSeconds,
- rounds = state.rounds.toIntOrZero()
+ workSeconds = state.workoutSecond,
+ restSeconds = state.restSecond,
+ rounds = state.rounds
)
viewModelScope.launch {
- insertUseCase(timer)
+ isSaving(true)
+ try {
+ insertUseCase(timer)
+ _uiEffect.emit(AddTimerUiEffect.NavigationBack)
+ } catch (e: Exception) {
+ if (e is CancellationException) throw e
+ showError(e.message.toString())
+ } finally {
+ isSaving(false)
+ }
+
+
}
}
- fun updateUiEvent(e: AddTimerUiEvent) {
- when (e) {
- is AddTimerUiEvent.OnRestMinChanged -> onIntervalMinChanged(e.min)
- is AddTimerUiEvent.OnRestSecChanged -> onIntervalSecChange(e.sec)
- AddTimerUiEvent.SaveTimer -> saveTimer()
- is AddTimerUiEvent.OnNameChanged -> onNameChange(e.name)
- is AddTimerUiEvent.OnRoundsChanged -> onSetChange(e.set)
- is AddTimerUiEvent.OnWorkMinChanged -> onTimerMinChange(e.min)
- is AddTimerUiEvent.OnWorkSecChanged -> onTimerSecChange(e.sec)
+ fun onAction(event: AddTimerAction) {
+ when (event) {
+ AddTimerAction.SaveTimer -> saveTimer()
+ is AddTimerAction.OnNameChanged -> onNameChange(event.name)
+ AddTimerAction.OnRestDecreased -> restDecreaseValue()
+ AddTimerAction.OnRestIncreased -> restIncreaseValue()
+ AddTimerAction.OnRoundDecreased -> roundDecreaseValue()
+ AddTimerAction.OnRoundIncreased -> roundIncreaseValue()
+ AddTimerAction.OnWorkoutDecreased -> workoutDecreaseValue()
+ AddTimerAction.OnWorkoutIncreased -> workoutIncreaseValue()
+ AddTimerAction.BackClicked -> emitBackNavigation()
}
}
- private fun showError(message: String) {
- _uiState.update { it.copy(errorMessage = message) }
+ private fun isSaving(isSaving: Boolean) {
+ _uiState.update { it.copy(isSaving = isSaving) }
}
- private fun onNameChange(newNameValue: String) {
- _uiState.update { it.copy(name = newNameValue, errorMessage = null) }
+ private fun workoutIncreaseValue() {
+ _uiState.update {
+ it.copy(workoutSecond = it.workoutSecond + 30)
+ }
}
- private fun onTimerMinChange(newMin: String) {
- _uiState.update { it.copy(workMin = validateTimeInput(newMin)) }
+ private fun workoutDecreaseValue() {
+ _uiState.update {
+ if (it.workoutSecond > AddTimerUiState.MIN_WORK_OUT)
+ it.copy(workoutSecond = it.workoutSecond - 30)
+ else
+ it.copy(workoutSecond = AddTimerUiState.MIN_WORK_OUT)
+ }
}
- private fun onTimerSecChange(newSec: String) {
- _uiState.update { it.copy(workSec = validateTimeInput(newSec)) }
+ private fun restIncreaseValue() {
+ _uiState.update {
+ it.copy(restSecond = it.restSecond + 30)
+ }
}
- private fun onIntervalMinChanged(newMin: String) {
- _uiState.update { it.copy(restMin = validateTimeInput(newMin)) }
+ private fun restDecreaseValue() {
+ _uiState.update {
+ if (it.restSecond > AddTimerUiState.MIN_REST)
+ it.copy(restSecond = it.restSecond - 30)
+ else
+ it.copy(restSecond = AddTimerUiState.MIN_REST)
+ }
}
- private fun onIntervalSecChange(newSec: String) {
- _uiState.update { it.copy(restSec = validateTimeInput(newSec)) }
+ private fun roundIncreaseValue() {
+ _uiState.update {
+ it.copy(rounds = it.rounds + 1)
+ }
}
- private fun onSetChange(newSet: String) {
- _uiState.update { it.copy(rounds = newSet.digitOnly()) }
+ private fun roundDecreaseValue() {
+ _uiState.update {
+ if (it.rounds > AddTimerUiState.MIN_ROUNDS)
+ it.copy(rounds = it.rounds - 1)
+ else
+ it.copy(rounds = AddTimerUiState.MIN_ROUNDS)
+ }
}
- private fun validateTimeInput(value: String) =
- value.digitOnly(2).toIntOrZero().coerceIn(0, 59).toString()
+ private fun showError(message: String) {
+ viewModelScope.launch {
+ _uiEffect.emit(AddTimerUiEffect.ShowSnackBar(message))
+ }
+ }
- private fun minuteSecondsToTotalSeconds(minText: String, secText: String): Long {
- val min = minText.toLongOrZero() * 60L
- val sec = secText.toLongOrZero()
- return min + sec
+ private fun onNameChange(newNameValue: String) {
+ _uiState.update { it.copy(name = newNameValue) }
}
-}
-sealed class AddTimerUiEvent {
+ private fun emitBackNavigation() {
+ viewModelScope.launch {
+ _uiEffect.emit(AddTimerUiEffect.NavigationBack)
+ }
+ }
+}
- object SaveTimer : AddTimerUiEvent()
- data class OnNameChanged(val name: String) : AddTimerUiEvent()
- data class OnWorkMinChanged(val min: String) : AddTimerUiEvent()
- data class OnWorkSecChanged(val sec: String) : AddTimerUiEvent()
- data class OnRestMinChanged(val min: String) : AddTimerUiEvent()
- data class OnRestSecChanged(val sec: String) : AddTimerUiEvent()
- data class OnRoundsChanged(val set: String) : AddTimerUiEvent()
+sealed class AddTimerAction {
+
+ object SaveTimer : AddTimerAction()
+ data class OnNameChanged(val name: String) : AddTimerAction()
+ object OnWorkoutIncreased : AddTimerAction()
+ object OnWorkoutDecreased : AddTimerAction()
+ object OnRestIncreased : AddTimerAction()
+ object OnRestDecreased : AddTimerAction()
+ object OnRoundIncreased : AddTimerAction()
+ object OnRoundDecreased : AddTimerAction()
+ object BackClicked : AddTimerAction()
}
data class AddTimerUiState(
- val isLoading: Boolean = false,
- val errorMessage: String? = null,
- val name: String? = null,
- val workMin: String = "",
- val workSec: String = "",
- val restMin: String = "",
- val restSec: String = "",
- val rounds: String = ""
-)
\ No newline at end of file
+ val isSaving: Boolean = false,
+ val name: String = "",
+ val workoutSecond: Long = MIN_WORK_OUT,
+ val restSecond: Long = MIN_REST,
+ val rounds: Int = MIN_ROUNDS,
+) {
+ companion object {
+ const val MIN_REST = 30L
+ const val MIN_WORK_OUT = 30L
+ const val MIN_ROUNDS = 1
+ }
+}
+
+sealed class AddTimerUiEffect {
+ data class ShowSnackBar(val errorMessage: String) : AddTimerUiEffect()
+ object NavigationBack : AddTimerUiEffect()
+
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 330fb3f..b6ea29b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -30,3 +30,4 @@ include(":feature:add-timer")
include(":feature:di")
include(":core:designsystem")
include(":core:timer-core")
+include(":core:ui")