NimazTheme
The root theme composable wraps the entire app:presentation/theme/Theme.kt
@Composable
fun NimazTheme(
themeMode: ThemeMode = ThemeMode.SYSTEM,
dynamicColor: Boolean = false,
hapticEnabled: Boolean = true,
animationsEnabled: Boolean = true,
use24HourFormat: Boolean = false,
useHijriPrimary: Boolean = false,
showIslamicPatterns: Boolean = true,
localeCode: String = "en",
content: @Composable () -> Unit
) {
val darkTheme = when (themeMode) {
ThemeMode.SYSTEM -> isSystemInDarkTheme()
ThemeMode.LIGHT -> false
ThemeMode.DARK -> true
}
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
CompositionLocalProvider(
LocalHapticEnabled provides hapticEnabled,
LocalAnimationsEnabled provides animationsEnabled,
LocalUse24HourFormat provides use24HourFormat,
LocalUseHijriPrimary provides useHijriPrimary,
LocalShowIslamicPatterns provides showIslamicPatterns
) {
MaterialTheme(
colorScheme = colorScheme,
typography = typographyForLocale(localeCode),
shapes = NimazShapes,
content = content
)
}
}
Theme modes
enum class ThemeMode {
SYSTEM, // Follow system setting
LIGHT, // Always light
DARK // Always dark
}
Color system
Nimaz uses a comprehensive color palette defined inNimazColors:
Primary colors (Teal)
presentation/theme/Color.kt
object NimazColors {
// Primary - Teal
val Primary50 = Color(0xFFF0FDFA)
val Primary100 = Color(0xFFCCFBF1)
val Primary200 = Color(0xFF99F6E4)
val Primary400 = Color(0xFF2DD4BF)
val Primary = Color(0xFF14B8A6) // Primary 500
val Primary600 = Color(0xFF0D9488)
val Primary700 = Color(0xFF0F766E)
val Primary800 = Color(0xFF115E59)
val Primary900 = Color(0xFF134E4A)
val Primary950 = Color(0xFF042F2E)
}
Secondary colors (Gold/Amber)
presentation/theme/Color.kt
// Secondary - Gold/Amber
val Gold400 = Color(0xFFFACC15)
val Gold500 = Color(0xFFEAB308)
val Secondary = Gold500
val SecondaryLight = Gold400
Prayer time colors
presentation/theme/Color.kt
object PrayerColors {
val Fajr = Color(0xFF6366F1) // Indigo
val FajrGradientEnd = Color(0xFF9FA8DA)
val Sunrise = Color(0xFFF59E0B) // Amber
val SunriseGradientEnd = Color(0xFFFFE0B2)
val Dhuhr = Color(0xFFEAB308) // Yellow
val DhuhrGradientEnd = Color(0xFFFFF9C4)
val Asr = Color(0xFFF97316) // Orange
val AsrGradientEnd = Color(0xFFFFCCBC)
val Maghrib = Color(0xFFEF4444) // Red
val MaghribGradientEnd = Color(0xFFFFCDD2)
val Isha = Color(0xFF8B5CF6) // Purple
val IshaGradientEnd = Color(0xFFC5CAE9)
}
Tajweed colors
For Quranic recitation rules:presentation/theme/Color.kt
object TajweedColors {
// Light theme
val GhunnahLight = Color(0xFF059669) // Green
val IkhfaLight = Color(0xFF0D9488) // Teal
val IdghamGhunnahLight = Color(0xFFD97706) // Amber
val QalqalahLight = Color(0xFF2563EB) // Blue
val MaddNormalLight = Color(0xFFE11D48) // Rose
val IqlabLight = Color(0xFF7C3AED) // Violet
val LamShamsiyyahLight = Color(0xFF4F46E5) // Indigo
val SilentLight = Color(0xFF64748B) // Slate
// Dark theme (brighter for OLED)
val GhunnahDark = Color(0xFF34D399)
val IkhfaDark = Color(0xFF2DD4BF)
// ... additional dark variants
}
Typography
Nimaz uses custom fonts with Material 3 typography scale:Font families
presentation/theme/Type.kt
// Outfit (variable font) for headlines
val OutfitFontFamily = FontFamily(
Font(R.font.outfit_variable, weight = FontWeight.Normal),
Font(R.font.outfit_variable, weight = FontWeight.Medium),
Font(R.font.outfit_variable, weight = FontWeight.SemiBold),
Font(R.font.outfit_variable, weight = FontWeight.Bold)
)
// Plus Jakarta Sans (variable font) for body text
val PlusJakartaSansFontFamily = FontFamily(
Font(R.font.plus_jakarta_sans_variable, weight = FontWeight.Normal),
Font(R.font.plus_jakarta_sans_variable, weight = FontWeight.Medium),
Font(R.font.plus_jakarta_sans_variable, weight = FontWeight.SemiBold),
Font(R.font.plus_jakarta_sans_variable, weight = FontWeight.Bold)
)
// Amiri for Arabic text
val AmiriFontFamily = FontFamily(
Font(R.font.amiri_regular, weight = FontWeight.Normal),
Font(R.font.amiri_bold, weight = FontWeight.Bold)
)
Typography scale
presentation/theme/Type.kt
val NimazTypography = Typography(
// Display styles - For large, prominent text
displayLarge = TextStyle(
fontFamily = OutfitFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 57.sp,
lineHeight = 64.sp
),
// Headline styles - For section headers
headlineMedium = TextStyle(
fontFamily = OutfitFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 28.sp,
lineHeight = 36.sp
),
// Body styles - For main content
bodyLarge = TextStyle(
fontFamily = PlusJakartaSansFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
// Label styles - For buttons and small text
labelLarge = TextStyle(
fontFamily = PlusJakartaSansFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
)
)
Arabic text styles
presentation/theme/Type.kt
object ArabicTextStyles {
val quranLarge = TextStyle(
fontFamily = AmiriFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 32.sp,
lineHeight = 56.sp
)
val quranMedium = TextStyle(
fontFamily = AmiriFontFamily,
fontSize = 24.sp,
lineHeight = 44.sp
)
val hadithArabic = TextStyle(
fontFamily = AmiriFontFamily,
fontSize = 22.sp,
lineHeight = 40.sp
)
}
Prayer text styles
presentation/theme/Type.kt
object PrayerTextStyles {
val prayerName = TextStyle(
fontFamily = OutfitFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 18.sp,
lineHeight = 24.sp
)
val countdown = TextStyle(
fontFamily = OutfitFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
lineHeight = 40.sp
)
}
Shapes
Rounded corner shapes for components:presentation/theme/Shape.kt
val NimazShapes = Shapes(
extraSmall = RoundedCornerShape(4.dp),
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(12.dp),
large = RoundedCornerShape(16.dp),
extraLarge = RoundedCornerShape(20.dp)
)
CompositionLocals
Theme provides custom composition locals for app-wide settings:presentation/theme/Theme.kt
val LocalHapticEnabled = compositionLocalOf { true }
val LocalAnimationsEnabled = compositionLocalOf { true }
val LocalUse24HourFormat = compositionLocalOf { false }
val LocalUseHijriPrimary = compositionLocalOf { false }
val LocalShowIslamicPatterns = compositionLocalOf { true }
@Composable
fun SomeComponent() {
val animationsEnabled = LocalAnimationsEnabled.current
val useHijriPrimary = LocalUseHijriPrimary.current
// Conditionally enable animations
if (animationsEnabled) {
AnimatedContent(/* ... */) { }
}
// Show Hijri date first if preferred
Text(
text = if (useHijriPrimary) hijriDate else gregorianDate
)
}
Dynamic colors
On Android 12+, the app can extract colors from the user’s wallpaper:val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
Status bar styling
The theme automatically adjusts status bar appearance:presentation/theme/Theme.kt
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.background.toArgb()
window.navigationBarColor = colorScheme.background.toArgb()
WindowCompat.getInsetsController(window, view)
.isAppearanceLightStatusBars = !darkTheme
WindowCompat.getInsetsController(window, view)
.isAppearanceLightNavigationBars = !darkTheme
}
}
Usage in MainActivity
MainActivity.kt
class MainActivity : ComponentActivity() {
@Inject
lateinit var preferencesDataStore: PreferencesDataStore
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val themeModeString by preferencesDataStore.themeMode
.collectAsState(initial = "system")
val dynamicColor by preferencesDataStore.dynamicColor
.collectAsState(initial = false)
val hapticEnabled by preferencesDataStore.hapticFeedback
.collectAsState(initial = true)
val themeMode = when (themeModeString) {
"light" -> ThemeMode.LIGHT
"dark" -> ThemeMode.DARK
else -> ThemeMode.SYSTEM
}
NimazTheme(
themeMode = themeMode,
dynamicColor = dynamicColor,
hapticEnabled = hapticEnabled,
animationsEnabled = animationsEnabled,
use24HourFormat = use24HourFormat,
useHijriPrimary = useHijriPrimary
) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NavGraph()
}
}
}
}
}
Related
- Components - Themed components
- Compose screens - Screen structure
- Navigation - Navigation setup