commit 9703a42d644eb17beaabebabb3fa1abc81aa24bd
Author: Nana <10011001nana@gmail.com>
Date: Wed Apr 23 12:17:15 2025 +0300
init
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7d9c0e4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+*.iml
+.kotlin
+.gradle
+**/build/
+xcuserdata
+!src/**/build/
+local.properties
+.idea
+.DS_Store
+captures
+.externalNativeBuild
+.cxx
+*.xcodeproj/*
+!*.xcodeproj/project.pbxproj
+!*.xcodeproj/xcshareddata/
+!*.xcodeproj/project.xcworkspace/
+!*.xcworkspace/contents.xcworkspacedata
+**/xcshareddata/WorkspaceSettings.xcsettings
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1ba257b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+This is a Kotlin Multiplatform project targeting Android, iOS.
+
+* `/composeApp` is for code that will be shared across your Compose Multiplatform applications.
+ It contains several subfolders:
+ - `commonMain` is for code that’s common for all targets.
+ - Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name.
+ For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app,
+ `iosMain` would be the right folder for such calls.
+
+* `/iosApp` contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform,
+ you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
+
+
+Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)…
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..1ecfdb5
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,11 @@
+plugins {
+ // this is necessary to avoid the plugins to be loaded multiple times
+ // in each subproject's classloader
+ alias(libs.plugins.androidApplication) apply false
+ alias(libs.plugins.androidLibrary) apply false
+ alias(libs.plugins.composeMultiplatform) apply false
+ alias(libs.plugins.composeCompiler) apply false
+ alias(libs.plugins.kotlinMultiplatform) apply false
+ alias(libs.plugins.kotlinSerialization) apply false
+ alias(libs.plugins.ksp) apply false
+}
\ No newline at end of file
diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
new file mode 100644
index 0000000..e6c3582
--- /dev/null
+++ b/composeApp/build.gradle.kts
@@ -0,0 +1,161 @@
+import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.androidApplication)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+ alias(libs.plugins.kotlinSerialization)
+ alias(libs.plugins.ksp)
+ alias(libs.plugins.ktorfit)
+}
+
+compose.resources {
+ publicResClass = false
+ packageOfResClass = "com.presenceapp.composeapp.resources"
+ generateResClass = auto
+}
+
+kotlin {
+ androidTarget {
+ @OptIn(ExperimentalKotlinGradlePluginApi::class)
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_11)
+ }
+ }
+
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64()
+ ).forEach { iosTarget ->
+ iosTarget.binaries.framework {
+ baseName = "ComposeApp"
+ isStatic = true
+ }
+
+ iosTarget.compilations.getByName("main") {
+ cinterops {
+ val uikit by creating {
+ defFile(file("src/iosMain/c_interop/Uikit.def"))
+ }
+ }
+ }
+ }
+
+ sourceSets {
+ val androidMain by getting {
+ dependencies {
+ implementation(compose.preview)
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.androidx.lifecycle.viewmodel)
+ implementation(libs.androidx.lifecycle.runtime.compose)
+ implementation(libs.androidx.datastore.preferences)
+ implementation(libs.androidx.datastore.preferences.core)
+ implementation(libs.androidx.startup.runtime)
+
+ // ktor
+ implementation(libs.ktor.client.okhttp)
+
+ // koin
+ implementation(libs.koin.android)
+ implementation(libs.koin.androidx.compose)
+ implementation(libs.koin.ktor)
+ }
+ }
+
+ val commonMain by getting {
+ resources.srcDirs("src/commonMain/composeResources")
+ dependencies {
+ implementation(compose.runtime)
+ implementation(compose.foundation)
+ implementation(compose.material3)
+ implementation(compose.ui)
+ implementation(compose.components.resources)
+ implementation(compose.components.uiToolingPreview)
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.kotlinx.datetime)
+
+ // voyager
+ implementation(libs.voyager.navigator)
+ implementation(libs.voyager.screenmodel)
+ implementation(libs.voyager.koin)
+
+ // ktor
+ implementation(libs.ktor.client.core)
+ implementation(libs.ktor.client.websockets)
+ implementation(libs.ktor.client.content.negotiation)
+ implementation(libs.ktor.client.logging)
+ implementation(libs.ktor.client.cio)
+ implementation(libs.ktor.serialization.kotlinx.json)
+
+ // ktorfit
+ implementation(libs.ktorfit)
+
+ // koin
+ implementation(libs.koin.core)
+ implementation(libs.koin.compose)
+
+ // logging
+ implementation(libs.kotlin.logging)
+
+ // settings
+ implementation(libs.multiplatform.settings)
+ }
+ }
+
+ iosMain.dependencies {
+ // ktor
+ implementation(libs.ktor.client.darwin)
+
+ // koin
+ implementation(libs.koin.core.native)
+
+ implementation(libs.kotlinx.coroutines.core.v173nativemt)
+ implementation(libs.kermit)
+ }
+ }
+}
+
+android {
+ namespace = "org.example.presenceapp"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
+ defaultConfig {
+ applicationId = "org.example.presenceapp"
+ minSdk = libs.versions.android.minSdk.get().toInt()
+ targetSdk = libs.versions.android.targetSdk.get().toInt()
+ versionCode = 1
+ versionName = "1.0"
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.ui.android)
+ implementation(libs.androidx.annotation.jvm)
+ implementation(libs.androidx.ui.text.android)
+ implementation(libs.androidx.material3.android)
+ implementation(libs.androidx.foundation.layout.android)
+ debugImplementation(compose.uiTooling)
+
+ add("kspCommonMainMetadata", libs.ktorfit.ksp)
+ add("kspAndroid", libs.ktorfit.ksp)
+ add("kspIosX64", libs.ktorfit.ksp)
+ add("kspIosArm64", libs.ktorfit.ksp)
+}
\ No newline at end of file
diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml
new file mode 100644
index 0000000..480cf08
--- /dev/null
+++ b/composeApp/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/composeApp/src/androidMain/kotlin/org/example/presenceapp/MainActivity.kt b/composeApp/src/androidMain/kotlin/org/example/presenceapp/MainActivity.kt
new file mode 100644
index 0000000..05c432e
--- /dev/null
+++ b/composeApp/src/androidMain/kotlin/org/example/presenceapp/MainActivity.kt
@@ -0,0 +1,35 @@
+package org.example.presenceapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import org.example.presenceapp.di.androidModule
+import org.example.presenceapp.di.networkModule
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.context.GlobalContext.startKoin
+import org.koin.core.logger.Level
+import org.koin.core.logger.PrintLogger
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ startKoin {
+ logger(PrintLogger(Level.DEBUG))
+ androidContext(applicationContext)
+ modules(networkModule + androidModule)
+ }
+
+ setContent {
+ App()
+ }
+ }
+}
+
+@Preview
+@Composable
+fun AppAndroidPreview() {
+ App()
+}
\ No newline at end of file
diff --git a/composeApp/src/androidMain/kotlin/org/example/presenceapp/Platform.android.kt b/composeApp/src/androidMain/kotlin/org/example/presenceapp/Platform.android.kt
new file mode 100644
index 0000000..6536f4e
--- /dev/null
+++ b/composeApp/src/androidMain/kotlin/org/example/presenceapp/Platform.android.kt
@@ -0,0 +1,9 @@
+package org.example.presenceapp
+
+import android.os.Build
+
+class AndroidPlatform : Platform {
+ override val name: String = "Android ${Build.VERSION.SDK_INT}"
+}
+
+actual fun getPlatform(): Platform = AndroidPlatform()
\ No newline at end of file
diff --git a/composeApp/src/androidMain/kotlin/org/example/presenceapp/PlatformContext.android.kt b/composeApp/src/androidMain/kotlin/org/example/presenceapp/PlatformContext.android.kt
new file mode 100644
index 0000000..8771a15
--- /dev/null
+++ b/composeApp/src/androidMain/kotlin/org/example/presenceapp/PlatformContext.android.kt
@@ -0,0 +1,12 @@
+package org.example.presenceapp
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+actual class PlatformContext(val context: Context)
+
+@Composable
+actual fun getPlatformContext(): PlatformContext {
+ return PlatformContext(LocalContext.current)
+}
\ No newline at end of file
diff --git a/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/SettingsStorage.kt b/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/SettingsStorage.kt
new file mode 100644
index 0000000..586457a
--- /dev/null
+++ b/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/SettingsStorage.kt
@@ -0,0 +1,19 @@
+package org.example.presenceapp.data.local.storage
+
+import android.content.Context
+import android.content.SharedPreferences
+import org.example.presenceapp.PlatformContext
+
+actual class SettingsStorage actual constructor(private val context: PlatformContext) {
+ private val sharedPreferences: SharedPreferences by lazy {
+ context.context.getSharedPreferences("app_settings", Context.MODE_PRIVATE)
+ }
+
+ actual suspend fun put(key: String, value: String) {
+ sharedPreferences.edit().putString(key, value).apply()
+ }
+
+ actual suspend fun get(key: String, defaultValue: String): String {
+ return sharedPreferences.getString(key, defaultValue) ?: defaultValue
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.android.kt b/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.android.kt
new file mode 100644
index 0000000..5500874
--- /dev/null
+++ b/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.android.kt
@@ -0,0 +1,32 @@
+package org.example.presenceapp.data.local.storage.attendance
+
+import android.content.Context
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.serialization.json.Json
+import org.example.presenceapp.domain.entities.Attendance
+
+private val Context.attendanceDataStore by preferencesDataStore(name = "attendance_prefs")
+
+class AttendanceStorageAndroid(private val context: Context): AttendanceStorage {
+
+ private val ATTENDANCE_KEY = stringPreferencesKey("attendance_map")
+
+ override suspend fun saveAttendanceMap(map: Map) {
+ val json = Json.encodeToString(map)
+ context.attendanceDataStore.edit { prefs ->
+ prefs[ATTENDANCE_KEY] = json
+ }
+ }
+
+ override fun attendanceMapFlow(): Flow