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
2 changes: 1 addition & 1 deletion .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 56 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,67 @@

![Android Build](https://github.com/t-regbs/MathAlarm/workflows/Android%20Build/badge.svg) ![My twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Ftimiaregbs) ![Shield](https://img.shields.io/badge/contributions-welcome-brightgreen) [![Made in Nigeria](https://img.shields.io/badge/made%20in-nigeria-008751.svg?style=flat-square)](https://github.com/acekyd/made-in-nigeria)

An Android alarm app in which you solve math problems of varying difficulty to dismiss alarm. Built with Kotlin, Room, Coroutines, the MVVM pattern with Clean architecture, ViewModel, Jetpack compose and some other libraries from the [Android Jetpack](https://developer.android.com/jetpack) .
A **Kotlin Multiplatform** alarm app for Android and iOS where you solve math problems of varying difficulty to dismiss the alarm. Built with Compose Multiplatform, Clean Architecture, and modern KMP libraries.

<a href='https://play.google.com/store/apps/details?id=com.timilehinaregbesola.mathalarm'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png' width="280"/></a>

## Technologies used:
## Architecture

* [Jetpack Compose](https://developer.android.com/jetpack/compose) - Android’s modern toolkit for building native UI
* [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) to store and manage UI-related data in a lifecycle conscious way.
* [Navigation Material](https://google.github.io/accompanist/navigation-material) - provides Compose Material support for Jetpack Navigation Compose, features composable bottom sheet destinations.
* [Timber](https://github.com/JakeWharton/timber) - a logger with a small, extensible API which provides utility on top of Android's normal Log class.
* [Material Design](https://material3.io/develop/android/docs/getting-started/)
* [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) used to manage the local storage i.e. `writing to and reading from the database`. Coroutines help in managing background threads and reduces the need for callbacks.
* [Room](https://developer.android.com/topic/libraries/architecture/room) persistence library which provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.
* [Dagger Hilt](https://dagger.dev/hilt/) provides a standard way to incorporate Dagger dependency injection into an Android application.
The project follows **Clean Architecture** with the **MVVM** pattern:

- **`:app`** - Main application module containing UI, navigation, and platform-specific implementations
- **`:core`** - Shared domain logic and business rules

## Technologies Used

### Kotlin Multiplatform
* [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) - Share code between Android and iOS
* [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/) - Declarative UI framework for both platforms

### UI & Navigation
* [Material 3](https://m3.material.io/) - Modern Material Design components
* [Navigation 3](https://developer.android.com/guide/navigation) - Jetpack Navigation for Compose Multiplatform
* [Compottie](https://github.com/alexzhirkevich/compottie) - Lottie animations for Compose Multiplatform

### Data & Storage
* [Room KMP](https://developer.android.com/kotlin/multiplatform/room) - Multiplatform database with SQLite
* [Multiplatform Settings](https://github.com/russhwolf/multiplatform-settings) - Key-value storage across platforms
* [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization) - JSON serialization

### Dependency Injection
* [Koin](https://insert-koin.io/) - Lightweight dependency injection framework for KMP

### Async & Reactive
* [Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) - Asynchronous programming
* [Kotlinx DateTime](https://github.com/Kotlin/kotlinx-datetime) - Multiplatform date/time library

### Logging & Analytics (Android)
* [Kermit](https://github.com/touchlab/Kermit) - Multiplatform logging library
* [Firebase Analytics](https://firebase.google.com/docs/analytics) - App analytics
* [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics) - Crash reporting

### Localization
* [Lyricist](https://github.com/adrielcafe/lyricist) - Type-safe string localization for Compose

### Testing
* [Kotlin Test](https://kotlinlang.org/api/latest/kotlin.test/) - Multiplatform testing
* [Turbine](https://github.com/cashapp/turbine) - Flow testing
* [Kotest](https://kotest.io/) - Assertions library
* [MockK](https://mockk.io/) - Mocking library (Android)

## Installation
Math Alarm requires a minimum API level of 21. Clone the repository.

Math Alarm requires a minimum API level of **26** (Android 8.0+).

```bash
# Clone the repository
git clone https://github.com/t-regbs/MathAlarm.git

# Open in Android Studio or IntelliJ IDEA
```

### Building for iOS
The iOS app is located in the `iosApp/` directory. Open the Xcode project to build and run on iOS devices/simulators.

## Contribution
All contributions are welcome. Simply make a PR!
Expand All @@ -28,7 +72,7 @@ All contributions are welcome. Simply make a PR!
```
MIT License

Copyright (c) 2023 Timilehin Aregbesola
Copyright (c) 2025 Timilehin Aregbesola

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
201 changes: 132 additions & 69 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,24 +1,133 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.gradle)
id("kotlin-android")
alias(libs.plugins.serialization)
id("org.jetbrains.kotlin.plugin.parcelize")
alias(libs.plugins.google.services)
alias(libs.plugins.crashlytics.gradle)
alias(libs.plugins.ksp)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidx.room)
}

ksp {
arg("lyricist.generateStringsProperty", "true")
}

// Room KMP configuration
room {
schemaDirectory("$projectDir/schemas")
}

kotlin {
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
allWarningsAsErrors = false
freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.Experimental")
}
}

listOf(
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "app"
isStatic = true
}
}

// Swift Export configuration for AlarmKit integration
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
swiftExport {
// Module name for Swift imports
moduleName = "MathAlarmShared"

// Flatten package structure for cleaner Swift code
flattenPackage = "com.timilehinaregbesola.mathalarm.alarm"

// Compiler configuration
configure {
freeCompilerArgs.add("-Xexpect-actual-classes")
}
}

sourceSets {
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.sqlite.driver.android)
implementation(project.dependencies.platform(libs.firebase.bom))
implementation(libs.firebase.analytics)
implementation(libs.firebase.crashlytics)
implementation(libs.firebase.messaging)

implementation(libs.androidx.core.splashscreen)
implementation(libs.android.material)
}
commonMain {
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
dependencies {
implementation(project(":core"))
implementation(libs.runtime)
implementation(libs.foundation)
implementation(libs.material3)
implementation(libs.ui)
implementation(libs.components.resources)
implementation(libs.ui.tooling.preview)

val koinBom = project.dependencies.platform(libs.koin.bom)
implementation(koinBom)
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)

implementation(libs.kermit)
implementation(libs.kermit.crashlytics)

implementation(libs.kotlinx.serialization)
implementation(libs.lyricist)

implementation(libs.jetbrains.navigation3.ui)
implementation(libs.jetbrains.lifecycle.viewmodel.navigation3)
implementation(libs.kotlinx.datetime)
implementation(libs.multiplatform.settings.no.arg)

implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.driver.bundled)

implementation(libs.compottie.lite)
}
}

// iOS dependencies
val iosMain by creating {
dependsOn(commonMain.get())
dependencies {
implementation(libs.androidx.sqlite.driver.bundled)
}
}
val iosArm64Main by getting { dependsOn(iosMain) }
val iosSimulatorArm64Main by getting { dependsOn(iosMain) }

commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.coroutines.test)
implementation(libs.turbine)
implementation(libs.kotest.assertions)
}
}
}

android {
namespace = "com.timilehinaregbesola.mathalarm"
defaultConfig {
applicationId = "com.timilehinaregbesola.mathalarm"
versionCode = 20
versionName = "2.3.1"
versionCode = 21
versionName = "2.3.2"
minSdk = libs.versions.android.min.sdk.get().toInt()
targetSdk = libs.versions.android.target.sdk.get().toInt()
compileSdk = libs.versions.android.compile.sdk.get().toInt()
Expand All @@ -37,18 +146,10 @@ android {
}

compileOptions {
// Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true

sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
allWarningsAsErrors = false
freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.Experimental")
jvmTarget = "17"
}
lint {
disable += setOf("LogNotTimber", "StringFormatInTimber", "ThrowableNotAtBeginning", "BinaryOperationInTimber", "TimberArgCount", "TimberArgTypes", "TimberTagLength", "TimberExceptionLogging")
}
Expand Down Expand Up @@ -78,70 +179,32 @@ android {

dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(project(":core"))

coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")

implementation(libs.android.material)
implementation(libs.androidx.ktx)

implementation(libs.androidx.test.core.ktx)
testImplementation(libs.junit)
testImplementation(libs.coroutines.test)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.espresso.core)
testImplementation(libs.mockk)

implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)

implementation(libs.lottie.compose)

implementation(libs.coroutines.core)
implementation(libs.coroutines.android)

implementation(libs.androidx.activity.compose)

implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics)
implementation(libs.firebase.crashlytics)
implementation(libs.firebase.messaging)

implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.datastore)
implementation(libs.kotlinx.serialization)

val composeBom = platform(libs.compose.bom)
implementation(composeBom)
androidTestImplementation(composeBom)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons)
debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.runtime.saveable)
implementation(libs.androidx.compose.runtime.livedata)
// Room KMP - compiler for each platform
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
androidTestImplementation(libs.androidx.compose.ui.test)

implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
implementation(libs.androidx.adaptive.navigation3)
implementation(libs.androidx.appcompat)
implementation(libs.kotlinx.datetime)

implementation(libs.lyricist)
ksp(libs.lyricist.processor)

val koinBom = platform(libs.koin.bom)
implementation(koinBom)
implementation(libs.koin.core)
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)

implementation(libs.kermit)
implementation(libs.kermit.crashlytics)
add("kspCommonMainMetadata", libs.lyricist.processor)
}

afterEvaluate {
// Make all compilation tasks depend on KSP common metadata
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
if (name != "kspCommonMainKotlinMetadata") {
dependsOn("kspCommonMainKotlinMetadata")
}
}
}

// Ensure all KSP tasks run after common metadata KSP
tasks.matching { it.name.startsWith("ksp") && it.name != "kspCommonMainKotlinMetadata" }.configureEach {
dependsOn(tasks.named("kspCommonMainKotlinMetadata"))
}
Binary file modified app/release/app-release.aab
Binary file not shown.
Loading
Loading