Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/src/main/java/me/rezapour/intervaltimer/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ 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() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
IntervalTimerTheme {
IniTheme {
AppRoot()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ fun AppRoot() {
backStack.add(AddTimerScreen)
})
}
addTimerScreen()
addTimerScreen(backStack)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,9 +16,9 @@ fun MainScreen(

Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.align(Alignment.Center)) {
Button(onClick = {
IniButtonPicker (onClick = {
onAddTimerClicked()
}) { Text(text = "AddTimer") }
})
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide accessibility labels for icon-only buttons

IniButtonPicker renders an IconButton whose icon has contentDescription = null, which produces unlabeled controls for screen-reader users. This component is used for primary interactive actions (including icon-only entry and +/- controls), so assistive-tech users cannot reliably understand what each button does.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it later

tint = IniTheme.colors.primary,
)
}
}

@IniPreview
@Composable
fun IniButtonPreview() {
IniTheme() {
IniButtonPicker(increaseMode = true) {

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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)
126 changes: 103 additions & 23 deletions core/designsystem/src/main/java/me/rezapour/designsystem/theme/Theme.kt
Original file line number Diff line number Diff line change
@@ -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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Follow system theme instead of forcing dark mode

IniTheme now defaults darkTheme to true, and current call sites use the default, so the app always renders with DarkColorScheme even when the device is in light mode. This makes LightColorScheme effectively unreachable in normal app usage and regresses expected Material behavior for users who prefer light theme.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it latter

// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
Expand All @@ -49,9 +100,38 @@ fun IntervalTimerTheme(
else -> LightColorScheme
}

MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
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
}
Original file line number Diff line number Diff line change
@@ -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
)
Loading
Loading