init
18
.gitignore
vendored
Normal file
@ -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
|
14
README.md
Normal file
@ -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)…
|
11
build.gradle.kts
Normal file
@ -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
|
||||||
|
}
|
161
composeApp/build.gradle.kts
Normal file
@ -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)
|
||||||
|
}
|
26
composeApp/src/androidMain/AndroidManifest.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<application
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.Material.Light.NoActionBar"
|
||||||
|
tools:targetApi="33">
|
||||||
|
<activity
|
||||||
|
android:exported="true"
|
||||||
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
|
||||||
|
android:name=".MainActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -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()
|
||||||
|
}
|
@ -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()
|
@ -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)
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, Attendance>) {
|
||||||
|
val json = Json.encodeToString(map)
|
||||||
|
context.attendanceDataStore.edit { prefs ->
|
||||||
|
prefs[ATTENDANCE_KEY] = json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attendanceMapFlow(): Flow<Map<String, Attendance>> {
|
||||||
|
return context.attendanceDataStore.data.map { prefs ->
|
||||||
|
prefs[ATTENDANCE_KEY]?.let {
|
||||||
|
Json.decodeFromString<Map<String, Attendance>>(it)
|
||||||
|
} ?: emptyMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.example.presenceapp.data.local.storage.attendance
|
||||||
|
|
||||||
|
import org.example.presenceapp.PlatformContext
|
||||||
|
|
||||||
|
actual class AttendanceStorageProvider actual constructor(private val context: PlatformContext) {
|
||||||
|
actual fun provide(): AttendanceStorage {
|
||||||
|
return AttendanceStorageAndroid(context.context)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package org.example.presenceapp.data.local.storage.login
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.example.project.data.local.TokenStorage
|
||||||
|
|
||||||
|
class AndroidTokenStorage(private val context: Context): TokenStorage {
|
||||||
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "auth_store")
|
||||||
|
private val tokenKey = stringPreferencesKey("auth_token")
|
||||||
|
|
||||||
|
override suspend fun getToken(): String? {
|
||||||
|
return context.dataStore.data
|
||||||
|
.map { preferences -> preferences[tokenKey] }
|
||||||
|
.first()
|
||||||
|
}
|
||||||
|
override suspend fun setToken(token: String) {
|
||||||
|
context.dataStore.edit { preferences ->
|
||||||
|
preferences[tokenKey] = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override suspend fun deleteToken() {
|
||||||
|
context.dataStore.edit { preferences ->
|
||||||
|
preferences.remove(tokenKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.example.presenceapp.di
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.local.LocalDataSource
|
||||||
|
import org.example.presenceapp.data.local.storage.attendance.AttendanceStorage
|
||||||
|
import org.example.presenceapp.data.local.storage.attendance.AttendanceStorageAndroid
|
||||||
|
import org.example.presenceapp.data.local.storage.login.AndroidTokenStorage
|
||||||
|
import org.example.presenceapp.data.local.storage.login.AuthManager
|
||||||
|
import org.example.project.data.local.TokenStorage
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val androidModule = module {
|
||||||
|
single<TokenStorage> { AndroidTokenStorage(androidContext()) }
|
||||||
|
single { AuthManager(get()) }
|
||||||
|
|
||||||
|
single<AttendanceStorage> { AttendanceStorageAndroid(androidContext()) }
|
||||||
|
single { LocalDataSource(get()) }
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.example.presenceapp.ui.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import org.example.presenceapp.PlatformContext
|
||||||
|
import org.example.presenceapp.getPlatformContext
|
||||||
|
|
||||||
|
actual class SettingsManager actual constructor(platformContext: PlatformContext) {
|
||||||
|
private val sharedPreferences: SharedPreferences =
|
||||||
|
platformContext.context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
actual fun isDialogShown(): Boolean {
|
||||||
|
return sharedPreferences.getBoolean(KEY_DIALOG_SHOWN, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setDialogShown() {
|
||||||
|
sharedPreferences.edit() { putBoolean(KEY_DIALOG_SHOWN, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_DIALOG_SHOWN = "dialog_shown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun getSettingsManager(): SettingsManager {
|
||||||
|
val platformContext = getPlatformContext()
|
||||||
|
return SettingsManager(platformContext)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package org.example.presenceapp.ui.theme
|
||||||
|
|
||||||
|
import android.content.ComponentCallbacks
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import org.example.presenceapp.ui.theme.themeManager.AndroidThemeManager
|
||||||
|
import org.example.presenceapp.ui.theme.themeManager.ThemeState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberThemeState(): ThemeState {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val configuration = remember { mutableStateOf(context.resources.configuration) }
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
val listener = object: ComponentCallbacks {
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
configuration.value = newConfig
|
||||||
|
}
|
||||||
|
override fun onLowMemory() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.registerComponentCallbacks(listener)
|
||||||
|
onDispose {
|
||||||
|
context.unregisterComponentCallbacks(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return remember {
|
||||||
|
ThemeState(
|
||||||
|
themeManager = AndroidThemeManager(context),
|
||||||
|
isSystemDark = isSystemDark(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSystemDark(context: Context): Boolean {
|
||||||
|
val configuration = context.resources.configuration
|
||||||
|
return (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package org.example.presenceapp.ui.theme.themeManager
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
|
||||||
|
class AndroidThemeManager(
|
||||||
|
private val context: Context
|
||||||
|
) : ThemeManager {
|
||||||
|
private val prefs = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
override fun getCurrentTheme(): ThemeTypes {
|
||||||
|
return try {
|
||||||
|
ThemeTypes.valueOf(
|
||||||
|
prefs.getString("app_theme", ThemeTypes.SYSTEM.name) ?: ThemeTypes.SYSTEM.name
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ThemeTypes.SYSTEM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTheme(theme: ThemeTypes) {
|
||||||
|
prefs.edit().putString("app_theme", theme.name).apply()
|
||||||
|
applyThemeImmediately(context, theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyThemeImmediately(context: Context, theme: ThemeTypes) {
|
||||||
|
val mode = when (theme) {
|
||||||
|
ThemeTypes.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
ThemeTypes.DARK -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
ThemeTypes.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
|
}
|
||||||
|
AppCompatDelegate.setDefaultNightMode(mode)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
BIN
composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 7.3 KiB |
BIN
composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 12 KiB |
BIN
composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 16 KiB |
3
composeApp/src/androidMain/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">PresenceApp</string>
|
||||||
|
</resources>
|
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="17dp"
|
||||||
|
android:height="15dp"
|
||||||
|
android:viewportWidth="17"
|
||||||
|
android:viewportHeight="15">
|
||||||
|
<path
|
||||||
|
android:pathData="M7.454,13.69L1.081,7.477L7.293,1.103M15.465,7.293L1.081,7.477L15.465,7.293Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
@ -0,0 +1,36 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="600dp"
|
||||||
|
android:height="600dp"
|
||||||
|
android:viewportWidth="600"
|
||||||
|
android:viewportHeight="600">
|
||||||
|
<path
|
||||||
|
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
|
||||||
|
android:fillColor="#041619"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
|
||||||
|
android:fillColor="#37BF6E"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
|
||||||
|
android:fillColor="#3870B2"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
|
||||||
|
android:strokeWidth="10"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#083042"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
|
||||||
|
android:strokeWidth="10"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#083042"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
|
||||||
|
android:strokeWidth="10"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#083042"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="M8,8C6.9,8 5.958,7.608 5.175,6.825C4.392,6.042 4,5.1 4,4C4,2.9 4.392,1.958 5.175,1.175C5.958,0.392 6.9,0 8,0C9.1,0 10.042,0.392 10.825,1.175C11.608,1.958 12,2.9 12,4C12,5.1 11.608,6.042 10.825,6.825C10.042,7.608 9.1,8 8,8ZM0,16V13.2C0,12.633 0.146,12.113 0.438,11.637C0.729,11.163 1.117,10.8 1.6,10.55C2.633,10.033 3.683,9.646 4.75,9.387C5.817,9.129 6.9,9 8,9C9.1,9 10.183,9.129 11.25,9.387C12.317,9.646 13.367,10.033 14.4,10.55C14.883,10.8 15.271,11.163 15.563,11.637C15.854,12.113 16,12.633 16,13.2V16H0ZM2,14H14V13.2C14,13.017 13.954,12.85 13.863,12.7C13.771,12.55 13.65,12.433 13.5,12.35C12.6,11.9 11.692,11.563 10.775,11.337C9.858,11.113 8.933,11 8,11C7.067,11 6.142,11.113 5.225,11.337C4.308,11.563 3.4,11.9 2.5,12.35C2.35,12.433 2.229,12.55 2.138,12.7C2.046,12.85 2,13.017 2,13.2V14ZM8,6C8.55,6 9.021,5.804 9.413,5.412C9.804,5.021 10,4.55 10,4C10,3.45 9.804,2.979 9.413,2.588C9.021,2.196 8.55,2 8,2C7.45,2 6.979,2.196 6.588,2.588C6.196,2.979 6,3.45 6,4C6,4.55 6.196,5.021 6.588,5.412C6.979,5.804 7.45,6 8,6Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="12dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="12"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="M11.908,0.359V16H9.846V2.057H2.133V16H0.06V0.359H11.908Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="14dp"
|
||||||
|
android:height="17dp"
|
||||||
|
android:viewportWidth="14"
|
||||||
|
android:viewportHeight="17">
|
||||||
|
<path
|
||||||
|
android:pathData="M13.284,7.686V8.674C13.284,9.848 13.137,10.901 12.844,11.832C12.55,12.763 12.128,13.554 11.576,14.206C11.025,14.858 10.362,15.356 9.589,15.699C8.823,16.043 7.963,16.215 7.011,16.215C6.087,16.215 5.238,16.043 4.465,15.699C3.699,15.356 3.033,14.858 2.467,14.206C1.908,13.554 1.475,12.763 1.167,11.832C0.859,10.901 0.705,9.848 0.705,8.674V7.686C0.705,6.511 0.855,5.462 1.156,4.538C1.464,3.607 1.897,2.816 2.456,2.164C3.015,1.505 3.677,1.004 4.443,0.66C5.217,0.316 6.065,0.145 6.989,0.145C7.942,0.145 8.801,0.316 9.567,0.66C10.341,1.004 11.003,1.505 11.555,2.164C12.113,2.816 12.539,3.607 12.833,4.538C13.134,5.462 13.284,6.511 13.284,7.686ZM11.232,8.674V7.664C11.232,6.733 11.136,5.91 10.942,5.193C10.756,4.477 10.481,3.876 10.115,3.389C9.75,2.902 9.302,2.533 8.772,2.282C8.25,2.032 7.655,1.906 6.989,1.906C6.345,1.906 5.761,2.032 5.238,2.282C4.723,2.533 4.279,2.902 3.906,3.389C3.541,3.876 3.258,4.477 3.058,5.193C2.857,5.91 2.757,6.733 2.757,7.664V8.674C2.757,9.612 2.857,10.443 3.058,11.166C3.258,11.882 3.545,12.487 3.917,12.981C4.297,13.468 4.744,13.837 5.26,14.088C5.783,14.339 6.366,14.464 7.011,14.464C7.684,14.464 8.282,14.339 8.805,14.088C9.327,13.837 9.768,13.468 10.126,12.981C10.491,12.487 10.767,11.882 10.953,11.166C11.139,10.443 11.232,9.612 11.232,8.674Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="20dp"
|
||||||
|
android:height="20dp"
|
||||||
|
android:viewportWidth="20"
|
||||||
|
android:viewportHeight="20">
|
||||||
|
<path
|
||||||
|
android:pathData="M13.3,14.7L14.7,13.3L11,9.6V5H9V10.4L13.3,14.7ZM10,20C8.617,20 7.317,19.737 6.1,19.212C4.883,18.688 3.825,17.975 2.925,17.075C2.025,16.175 1.313,15.117 0.788,13.9C0.262,12.683 0,11.383 0,10C0,8.617 0.262,7.317 0.788,6.1C1.313,4.883 2.025,3.825 2.925,2.925C3.825,2.025 4.883,1.313 6.1,0.788C7.317,0.262 8.617,0 10,0C11.383,0 12.683,0.262 13.9,0.788C15.117,1.313 16.175,2.025 17.075,2.925C17.975,3.825 18.688,4.883 19.212,6.1C19.737,7.317 20,8.617 20,10C20,11.383 19.737,12.683 19.212,13.9C18.688,15.117 17.975,16.175 17.075,17.075C16.175,17.975 15.117,18.688 13.9,19.212C12.683,19.737 11.383,20 10,20ZM10,18C12.217,18 14.104,17.221 15.663,15.663C17.221,14.104 18,12.217 18,10C18,7.783 17.221,5.896 15.663,4.338C14.104,2.779 12.217,2 10,2C7.783,2 5.896,2.779 4.338,4.338C2.779,5.896 2,7.783 2,10C2,12.217 2.779,14.104 4.338,15.663C5.896,17.221 7.783,18 10,18Z"
|
||||||
|
android:fillColor="#2C2C2C"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="21dp"
|
||||||
|
android:height="20dp"
|
||||||
|
android:viewportWidth="21"
|
||||||
|
android:viewportHeight="20">
|
||||||
|
<path
|
||||||
|
android:pathData="M7.917,20L7.517,16.8C7.3,16.717 7.096,16.617 6.904,16.5C6.712,16.383 6.525,16.258 6.342,16.125L3.367,17.375L0.617,12.625L3.192,10.675C3.175,10.558 3.167,10.446 3.167,10.337V9.663C3.167,9.554 3.175,9.442 3.192,9.325L0.617,7.375L3.367,2.625L6.342,3.875C6.525,3.742 6.717,3.617 6.917,3.5C7.117,3.383 7.317,3.283 7.517,3.2L7.917,0H13.417L13.817,3.2C14.033,3.283 14.238,3.383 14.429,3.5C14.621,3.617 14.808,3.742 14.992,3.875L17.967,2.625L20.717,7.375L18.142,9.325C18.158,9.442 18.167,9.554 18.167,9.663V10.337C18.167,10.446 18.15,10.558 18.117,10.675L20.692,12.625L17.942,17.375L14.992,16.125C14.808,16.258 14.617,16.383 14.417,16.5C14.217,16.617 14.017,16.717 13.817,16.8L13.417,20H7.917ZM10.717,13.5C11.683,13.5 12.508,13.158 13.192,12.475C13.875,11.792 14.217,10.967 14.217,10C14.217,9.033 13.875,8.208 13.192,7.525C12.508,6.842 11.683,6.5 10.717,6.5C9.733,6.5 8.904,6.842 8.229,7.525C7.554,8.208 7.217,9.033 7.217,10C7.217,10.967 7.554,11.792 8.229,12.475C8.904,13.158 9.733,13.5 10.717,13.5Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</vector>
|
BIN
composeApp/src/commonMain/composeResources/font/montserrat.ttf
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package org.example.presenceapp
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import org.example.presenceapp.ui.schedule.ScheduleScreen
|
||||||
|
import org.example.presenceapp.ui.theme.AppTheme
|
||||||
|
import org.example.project.ui.login.LoginScreen
|
||||||
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun App() {
|
||||||
|
AppTheme {
|
||||||
|
Navigator(LoginScreen())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.example.presenceapp
|
||||||
|
|
||||||
|
interface Platform {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
expect fun getPlatform(): Platform
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
expect class PlatformContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
expect fun getPlatformContext(): PlatformContext
|
@ -0,0 +1,120 @@
|
|||||||
|
package org.example.presenceapp.data.common
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.PresettingRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.PresettingResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.AuthRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.AuthResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.GroupDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.ResponsibleDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.ResponsibleTypeDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.RoleResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.UserResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.schedule.ScheduleRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.schedule.ScheduleResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.schedule.SubjectResponseDto
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddPresettingCommand
|
||||||
|
import org.example.presenceapp.domain.command.GroupCommand
|
||||||
|
import org.example.presenceapp.domain.command.LoginCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Attendance
|
||||||
|
import org.example.presenceapp.domain.entities.AttendanceType
|
||||||
|
import org.example.presenceapp.domain.entities.GroupResponse
|
||||||
|
import org.example.presenceapp.domain.entities.LoginResponse
|
||||||
|
import org.example.presenceapp.domain.entities.Presetting
|
||||||
|
import org.example.presenceapp.domain.entities.Responsible
|
||||||
|
import org.example.presenceapp.domain.entities.ResponsibleType
|
||||||
|
import org.example.presenceapp.domain.entities.RoleResponse
|
||||||
|
import org.example.presenceapp.domain.entities.Schedule
|
||||||
|
import org.example.presenceapp.domain.entities.Subject
|
||||||
|
import org.example.presenceapp.domain.entities.UserResponse
|
||||||
|
|
||||||
|
fun LoginCommand.toDto(): AuthRequestDto = AuthRequestDto(
|
||||||
|
login = email,
|
||||||
|
password = password
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GroupCommand.toDto(): ScheduleRequestDto = ScheduleRequestDto(groupId)
|
||||||
|
|
||||||
|
fun AddAttendanceCommand.toDto(): AttendanceRequestDto = AttendanceRequestDto(
|
||||||
|
studentId = studentId,
|
||||||
|
scheduleId = scheduleId,
|
||||||
|
attendanceTypeId = attendanceTypeId,
|
||||||
|
attendanceDate = attendanceDate
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AddPresettingCommand.toDto(): PresettingRequestDto = PresettingRequestDto(
|
||||||
|
attendanceTypeId = attendanceTypeId,
|
||||||
|
startAt = startAt,
|
||||||
|
endAt = endAt
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun ScheduleResponseDto.toEntity(): Schedule = Schedule(
|
||||||
|
subject = subject.toEntity(),
|
||||||
|
audience = audience,
|
||||||
|
dayOfWeek = dayOfWeek,
|
||||||
|
lessonNumber = lessonNumber,
|
||||||
|
id = id
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SubjectResponseDto.toEntity(): Subject = Subject(
|
||||||
|
id = id,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AuthResponseDto.toEntity(): LoginResponse = LoginResponse(
|
||||||
|
token = token,
|
||||||
|
user = user.toEntity()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun UserResponseDto.toEntity(): UserResponse = UserResponse(
|
||||||
|
uuid = uuid,
|
||||||
|
email = email,
|
||||||
|
number = number,
|
||||||
|
fio = fio,
|
||||||
|
role = role.toEntity(),
|
||||||
|
responsible = responsible.map { it.toEntity() }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ResponsibleDto.toEntity(): Responsible = Responsible(
|
||||||
|
group = group.toEntity(),
|
||||||
|
responsibleType = responsibleType.toEntity()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GroupDto.toEntity(): GroupResponse = GroupResponse(
|
||||||
|
id = id,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ResponsibleTypeDto.toEntity(): ResponsibleType = ResponsibleType(
|
||||||
|
id = id,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
|
||||||
|
fun RoleResponseDto.toEntity(): RoleResponse = RoleResponse(
|
||||||
|
id = id,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AttendanceResponseDto.toEntity(): Attendance = Attendance(
|
||||||
|
attendanceTypeId = attendanceId,
|
||||||
|
studentId = studentId,
|
||||||
|
scheduleId = scheduleId,
|
||||||
|
date = attendanceDate,
|
||||||
|
type = attendanceTypeId
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AttendanceResponseDto.toAttendanceType(): AttendanceType = AttendanceType(
|
||||||
|
id = attendanceTypeId,
|
||||||
|
name = ""
|
||||||
|
)
|
||||||
|
fun PresettingResponseDto.toEntity(): Presetting = Presetting(
|
||||||
|
id = id,
|
||||||
|
attendanceType = attendanceType.toAttendanceType(),
|
||||||
|
studentId = studentId,
|
||||||
|
startAt = startAt,
|
||||||
|
endAt = endAt
|
||||||
|
)
|
@ -0,0 +1,19 @@
|
|||||||
|
package org.example.presenceapp.data.common.dto.attendance
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AttendanceRequestDto(
|
||||||
|
val studentId: Int,
|
||||||
|
val scheduleId: Int,
|
||||||
|
val attendanceTypeId: Int,
|
||||||
|
val attendanceDate: LocalDate
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PresettingRequestDto(
|
||||||
|
val attendanceTypeId: Int,
|
||||||
|
val startAt: LocalDate,
|
||||||
|
val endAt: LocalDate?,
|
||||||
|
)
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.example.presenceapp.data.common.dto.attendance
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AttendanceResponseDto(
|
||||||
|
val attendanceId: Int,
|
||||||
|
val scheduleId: Int,
|
||||||
|
val attendanceTypeId: Int,
|
||||||
|
val attendanceDate: LocalDate,
|
||||||
|
val studentId: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AttendanceTypeResponseDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PresettingResponseDto(
|
||||||
|
val id: Int,
|
||||||
|
val attendanceType: AttendanceResponseDto,
|
||||||
|
val studentId: Int,
|
||||||
|
val startAt: LocalDate,
|
||||||
|
val endAt: LocalDate,
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.example.presenceapp.data.common.dto.auth
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AuthRequestDto(
|
||||||
|
val login: String,
|
||||||
|
val password: String
|
||||||
|
)
|
@ -0,0 +1,43 @@
|
|||||||
|
package org.example.presenceapp.data.common.dto.auth
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AuthResponseDto(
|
||||||
|
val user: UserResponseDto,
|
||||||
|
val token: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserResponseDto(
|
||||||
|
val uuid: String,
|
||||||
|
val email: String,
|
||||||
|
val number: String,
|
||||||
|
val fio: String,
|
||||||
|
val role: RoleResponseDto,
|
||||||
|
var responsible: List<ResponsibleDto> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class RoleResponseDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GroupDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResponsibleTypeDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResponsibleDto(
|
||||||
|
val group: GroupDto,
|
||||||
|
val responsibleType: ResponsibleTypeDto
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package org.example.presenceapp.data.common.dto.group
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StudentResponseDto(
|
||||||
|
val studentId: Int,
|
||||||
|
val uuid: String,
|
||||||
|
val email: String,
|
||||||
|
val number: String,
|
||||||
|
val fio: String,
|
||||||
|
val enrollDate: LocalDate,
|
||||||
|
val expulsionDate: LocalDate? = null
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp.data.common.dto.schedule
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ScheduleRequestDto(
|
||||||
|
val groupId: Int
|
||||||
|
)
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.example.presenceapp.data.common.dto.schedule
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ScheduleResponseDto(
|
||||||
|
val id: Int,
|
||||||
|
val lessonNumber: Int,
|
||||||
|
val audience: String,
|
||||||
|
val subject: SubjectResponseDto,
|
||||||
|
val dayOfWeek: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SubjectResponseDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
@ -0,0 +1,27 @@
|
|||||||
|
package org.example.presenceapp.data.local
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.example.presenceapp.data.local.storage.attendance.AttendanceStorage
|
||||||
|
import org.example.presenceapp.domain.entities.AbsenceReason
|
||||||
|
import org.example.presenceapp.domain.entities.Attendance
|
||||||
|
import org.example.presenceapp.domain.entities.AttendanceType
|
||||||
|
|
||||||
|
class LocalDataSource(private val attendanceStorage: AttendanceStorage) {
|
||||||
|
suspend fun saveAttendance(map: Map<String, Attendance>) {
|
||||||
|
attendanceStorage.saveAttendanceMap(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun observeAttendance(): Flow<Map<String, Attendance>> {
|
||||||
|
return attendanceStorage.attendanceMapFlow()
|
||||||
|
.map { attendanceMap ->
|
||||||
|
attendanceMap
|
||||||
|
}
|
||||||
|
.catch { e ->
|
||||||
|
println("Error reading attendance: ${e.message}")
|
||||||
|
emit(emptyMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp.data.local.storage
|
||||||
|
|
||||||
|
import org.example.presenceapp.PlatformContext
|
||||||
|
|
||||||
|
expect class SettingsStorage(context: PlatformContext) {
|
||||||
|
suspend fun put(key: String, value: String)
|
||||||
|
suspend fun get(key: String, defaultValue: String): String
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.example.presenceapp.data.local.storage.attendance
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.example.presenceapp.domain.entities.Attendance
|
||||||
|
|
||||||
|
interface AttendanceStorage {
|
||||||
|
suspend fun saveAttendanceMap(map: Map<String, Attendance>)
|
||||||
|
fun attendanceMapFlow(): Flow<Map<String, Attendance>>
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.example.presenceapp.data.local.storage.attendance
|
||||||
|
|
||||||
|
import org.example.presenceapp.PlatformContext
|
||||||
|
|
||||||
|
expect class AttendanceStorageProvider(context: PlatformContext) {
|
||||||
|
fun provide(): AttendanceStorage
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.example.presenceapp.data.local.storage.login
|
||||||
|
|
||||||
|
import org.example.project.data.local.TokenStorage
|
||||||
|
|
||||||
|
class AuthManager(private val tokenStorage: TokenStorage) {
|
||||||
|
suspend fun setToken(token: String) = tokenStorage.setToken(token)
|
||||||
|
suspend fun deleteToken() = tokenStorage.deleteToken()
|
||||||
|
suspend fun getToken(): String? = tokenStorage.getToken()
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.example.project.data.local
|
||||||
|
|
||||||
|
interface TokenStorage {
|
||||||
|
suspend fun getToken(): String?
|
||||||
|
suspend fun setToken(token: String)
|
||||||
|
suspend fun deleteToken()
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.example.presenceapp.data.remote.api
|
||||||
|
|
||||||
|
import de.jensklingenberg.ktorfit.http.Body
|
||||||
|
import de.jensklingenberg.ktorfit.http.GET
|
||||||
|
import de.jensklingenberg.ktorfit.http.POST
|
||||||
|
import de.jensklingenberg.ktorfit.http.Path
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceTypeResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.PresettingResponseDto
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddPresettingCommand
|
||||||
|
|
||||||
|
interface AttendanceApi {
|
||||||
|
@POST("api/v1/presence")
|
||||||
|
suspend fun addAttendance(@Body commands: List<AttendanceRequestDto>): List<AttendanceResponseDto>
|
||||||
|
|
||||||
|
@GET("api/v1/presence/dictionary/attendance_type")
|
||||||
|
suspend fun getAttendanceTypes(): List<AttendanceTypeResponseDto>
|
||||||
|
|
||||||
|
@GET("api/v1/presence/{groupId}")
|
||||||
|
suspend fun getAttendance(@Path("groupId") groupId: Int): List<AttendanceResponseDto>
|
||||||
|
|
||||||
|
@POST("api/v1/presence/presetting")
|
||||||
|
suspend fun addPresetting(@Body command: AddPresettingCommand): Boolean
|
||||||
|
|
||||||
|
@GET("api/v1/presence/presetting/{groupId}")
|
||||||
|
suspend fun getPresetting(@Path("groupId") groupId: Int): List<PresettingResponseDto>
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.example.presenceapp.data.remote.api
|
||||||
|
|
||||||
|
import de.jensklingenberg.ktorfit.http.Body
|
||||||
|
import de.jensklingenberg.ktorfit.http.POST
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.AuthRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.AuthResponseDto
|
||||||
|
|
||||||
|
interface AuthApi {
|
||||||
|
@POST("api/v1/auth/login")
|
||||||
|
suspend fun login(@Body request: AuthRequestDto): AuthResponseDto
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.example.presenceapp.data.remote.api
|
||||||
|
|
||||||
|
import de.jensklingenberg.ktorfit.http.GET
|
||||||
|
import de.jensklingenberg.ktorfit.http.Path
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.group.StudentResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.schedule.ScheduleResponseDto
|
||||||
|
|
||||||
|
interface GroupApi {
|
||||||
|
@GET("api/v1/group/{id}/schedule")
|
||||||
|
suspend fun getSchedule(@Path id: Int): List<ScheduleResponseDto>
|
||||||
|
|
||||||
|
@GET("api/v1/group/{id}/students")
|
||||||
|
suspend fun getStudents(@Path id: Int): List<StudentResponseDto>
|
||||||
|
|
||||||
|
@GET("api/v1/group/{id}/presence")
|
||||||
|
suspend fun getPresence(@Path id: Int): List<AttendanceResponseDto>
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package org.example.presenceapp.data.remote.impl
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceTypeResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.PresettingResponseDto
|
||||||
|
import org.example.presenceapp.data.common.toDto
|
||||||
|
import org.example.presenceapp.data.remote.api.AttendanceApi
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddPresettingCommand
|
||||||
|
|
||||||
|
class AttendanceApiImpl(private val attendanceApi: AttendanceApi) {
|
||||||
|
suspend fun addAttendance(commands: List<AddAttendanceCommand>): List<AttendanceResponseDto> =
|
||||||
|
attendanceApi.addAttendance(commands.map { it.toDto() })
|
||||||
|
|
||||||
|
suspend fun getAttendance(groupId: Int): List<AttendanceResponseDto> =
|
||||||
|
attendanceApi.getAttendance(groupId)
|
||||||
|
|
||||||
|
suspend fun getAttendanceTypes(): List<AttendanceTypeResponseDto> =
|
||||||
|
attendanceApi.getAttendanceTypes()
|
||||||
|
|
||||||
|
suspend fun addPresetting(command: AddPresettingCommand): Boolean =
|
||||||
|
attendanceApi.addPresetting(command)
|
||||||
|
|
||||||
|
suspend fun getPresetting(groupId: Int): List<PresettingResponseDto> =
|
||||||
|
attendanceApi.getPresetting(groupId)
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.example.presenceapp.data.remote.impl
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.remote.api.AuthApi
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.AuthRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.auth.AuthResponseDto
|
||||||
|
|
||||||
|
class AuthApiImpl(private val authApi: AuthApi) {
|
||||||
|
suspend fun login(requestDto: AuthRequestDto): AuthResponseDto = authApi.login(requestDto)
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package org.example.presenceapp.data.remote.impl
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.common.dto.attendance.AttendanceResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.group.StudentResponseDto
|
||||||
|
import org.example.presenceapp.data.common.dto.schedule.ScheduleRequestDto
|
||||||
|
import org.example.presenceapp.data.common.dto.schedule.ScheduleResponseDto
|
||||||
|
import org.example.presenceapp.data.remote.api.GroupApi
|
||||||
|
|
||||||
|
class ScheduleApiImpl(private val groupApi: GroupApi) {
|
||||||
|
suspend fun getSchedule(scheduleRequestDto: ScheduleRequestDto): List<ScheduleResponseDto> = groupApi.getSchedule(scheduleRequestDto.groupId)
|
||||||
|
suspend fun getStudent(scheduleRequestDto: ScheduleRequestDto): List<StudentResponseDto> = groupApi.getStudents(scheduleRequestDto.groupId)
|
||||||
|
suspend fun getAttendance(scheduleRequestDto: ScheduleRequestDto): List<AttendanceResponseDto> = groupApi.getPresence(scheduleRequestDto.groupId)
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package org.example.presenceapp.data.remote.network
|
||||||
|
|
||||||
|
import de.jensklingenberg.ktorfit.Ktorfit
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.DefaultRequest
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.client.request.header
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.example.presenceapp.data.remote.api.AuthApi
|
||||||
|
import org.example.presenceapp.data.remote.api.GroupApi
|
||||||
|
import org.example.presenceapp.data.remote.api.AttendanceApi
|
||||||
|
|
||||||
|
object KtorfitClient {
|
||||||
|
private const val BASE_URL = "http://185.207.0.137:8080/"
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
private val ktorClient = HttpClient {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(json)
|
||||||
|
}
|
||||||
|
install(DefaultRequest) {
|
||||||
|
header(HttpHeaders.ContentType, ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val instance: Ktorfit = Ktorfit.Builder()
|
||||||
|
.baseUrl(BASE_URL)
|
||||||
|
.httpClient(ktorClient)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
fun createAuthApi(): AuthApi {
|
||||||
|
return instance.create<AuthApi>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createScheduleApi(): GroupApi {
|
||||||
|
return instance.create<GroupApi>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createAttendanceApi(): AttendanceApi {
|
||||||
|
return instance.create<AttendanceApi>()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.example.presenceapp.data.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.example.presenceapp.data.common.toDto
|
||||||
|
import org.example.presenceapp.data.common.toEntity
|
||||||
|
import org.example.presenceapp.data.local.LocalDataSource
|
||||||
|
import org.example.presenceapp.data.remote.impl.AttendanceApiImpl
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddPresettingCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.GetAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.GetPresettingCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Attendance
|
||||||
|
import org.example.presenceapp.domain.entities.Presetting
|
||||||
|
import org.example.presenceapp.domain.repo.attendance.AttendanceRepository
|
||||||
|
import org.example.presenceapp.domain.repo.attendance.PresettingRepository
|
||||||
|
|
||||||
|
class AttendanceNetRepository(
|
||||||
|
private val localDataSource: LocalDataSource,
|
||||||
|
private val attendanceApiImpl: AttendanceApiImpl
|
||||||
|
): AttendanceRepository, PresettingRepository {
|
||||||
|
suspend fun saveAttendanceLocally(attendance: Map<String, Attendance>) {
|
||||||
|
localDataSource.saveAttendance(attendance)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun observeLocalAttendance(): Flow<Map<String, Attendance>> {
|
||||||
|
return localDataSource.observeAttendance()
|
||||||
|
.map { attendanceMap ->
|
||||||
|
attendanceMap
|
||||||
|
}
|
||||||
|
.catch { e ->
|
||||||
|
emit(emptyMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addAttendance(addAttendanceCommand: AddAttendanceCommand): List<Attendance> {
|
||||||
|
val result = attendanceApiImpl.addAttendance(listOf(addAttendanceCommand))
|
||||||
|
return result.map { it.toEntity() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAttendance(getAttendanceCommand: GetAttendanceCommand): List<Attendance> {
|
||||||
|
val result = attendanceApiImpl.getAttendance(getAttendanceCommand.groupId)
|
||||||
|
return result.map { it.toEntity() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addPresetting(addPresettingCommand: AddPresettingCommand): Boolean {
|
||||||
|
return attendanceApiImpl.addPresetting(addPresettingCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPresetting(getPresettingCommand: GetPresettingCommand): List<Presetting> {
|
||||||
|
val result = attendanceApiImpl.getPresetting(getPresettingCommand.groupId)
|
||||||
|
return result.map { it.toEntity() }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.example.presenceapp.data.repository
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.common.toDto
|
||||||
|
import org.example.presenceapp.data.common.toEntity
|
||||||
|
import org.example.presenceapp.data.remote.impl.AuthApiImpl
|
||||||
|
import org.example.presenceapp.domain.command.LoginCommand
|
||||||
|
import org.example.presenceapp.domain.entities.LoginResponse
|
||||||
|
import org.example.presenceapp.domain.repo.LoginRepository
|
||||||
|
import org.example.presenceapp.data.local.storage.login.AuthManager
|
||||||
|
|
||||||
|
class AuthRepository(
|
||||||
|
private val authApiImpl: AuthApiImpl,
|
||||||
|
private val authManager: AuthManager,
|
||||||
|
): LoginRepository {
|
||||||
|
override suspend fun login(loginCommand: LoginCommand): LoginResponse {
|
||||||
|
val result = authApiImpl.login(loginCommand.toDto())
|
||||||
|
authManager.setToken(result.token)
|
||||||
|
return result.toEntity()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.example.presenceapp.data.repository
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.common.toDto
|
||||||
|
import org.example.presenceapp.data.common.toEntity
|
||||||
|
import org.example.presenceapp.data.remote.impl.ScheduleApiImpl
|
||||||
|
import org.example.presenceapp.domain.command.GroupCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Schedule
|
||||||
|
import org.example.presenceapp.domain.repo.ScheduleRepository
|
||||||
|
|
||||||
|
class ScheduleNetRepository(
|
||||||
|
private val scheduleApiImpl: ScheduleApiImpl
|
||||||
|
): ScheduleRepository {
|
||||||
|
override suspend fun getSchedule(groupCommand: GroupCommand): List<Schedule> {
|
||||||
|
val result = scheduleApiImpl.getSchedule(groupCommand.toDto())
|
||||||
|
return result.map { it.toEntity() }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp.data.repository.settings
|
||||||
|
|
||||||
|
import org.example.presenceapp.domain.entities.AttendanceType
|
||||||
|
|
||||||
|
interface SettingsRepository {
|
||||||
|
suspend fun getDefaultAttendanceStatus(): AttendanceType
|
||||||
|
suspend fun setDefaultAttendanceStatus(type: AttendanceType)
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package org.example.presenceapp.data.repository.settings
|
||||||
|
|
||||||
|
import org.example.presenceapp.data.local.storage.SettingsStorage
|
||||||
|
import org.example.presenceapp.domain.entities.AttendanceType
|
||||||
|
|
||||||
|
class SettingsRepositoryImpl(
|
||||||
|
private val settingsStorage: SettingsStorage
|
||||||
|
) : SettingsRepository {
|
||||||
|
|
||||||
|
override suspend fun getDefaultAttendanceStatus(): AttendanceType {
|
||||||
|
val statusString = settingsStorage.get(
|
||||||
|
key = "default_attendance_status",
|
||||||
|
defaultValue = AttendanceType.ABSENT.name
|
||||||
|
)
|
||||||
|
return enumValueOf(statusString)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setDefaultAttendanceStatus(type: AttendanceType) {
|
||||||
|
settingsStorage.put(
|
||||||
|
key = "default_attendance_status",
|
||||||
|
value = type.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.example.presenceapp.di
|
||||||
|
|
||||||
|
import de.jensklingenberg.ktorfit.Ktorfit
|
||||||
|
import org.example.presenceapp.data.remote.api.AuthApi
|
||||||
|
import org.example.presenceapp.data.remote.api.GroupApi
|
||||||
|
import org.example.presenceapp.data.remote.api.createAuthApi
|
||||||
|
import org.example.presenceapp.data.remote.api.createGroupApi
|
||||||
|
import org.example.presenceapp.data.remote.impl.AuthApiImpl
|
||||||
|
import org.example.presenceapp.data.remote.impl.ScheduleApiImpl
|
||||||
|
import org.example.presenceapp.data.remote.network.KtorfitClient
|
||||||
|
import org.example.presenceapp.data.repository.AuthRepository
|
||||||
|
import org.example.presenceapp.data.repository.ScheduleNetRepository
|
||||||
|
import org.example.presenceapp.domain.repo.LoginRepository
|
||||||
|
import org.example.presenceapp.domain.repo.ScheduleRepository
|
||||||
|
import org.example.presenceapp.domain.usecases.LoginUseCase
|
||||||
|
import org.example.project.ui.login.LoginViewModel
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val networkModule = module {
|
||||||
|
single { KtorfitClient.instance }
|
||||||
|
single<AuthApi> { get<Ktorfit>().createAuthApi() }
|
||||||
|
single<GroupApi> { get<Ktorfit>().createGroupApi() }
|
||||||
|
|
||||||
|
single { AuthApiImpl(get()) }
|
||||||
|
single<LoginRepository> { AuthRepository(get(), get()) }
|
||||||
|
|
||||||
|
single { LoginUseCase(get()) }
|
||||||
|
|
||||||
|
single { ScheduleApiImpl(get()) }
|
||||||
|
single<ScheduleRepository> { ScheduleNetRepository (get()) }
|
||||||
|
|
||||||
|
factory { LoginViewModel(get(), get()) }
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package org.example.presenceapp.domain.command
|
||||||
|
|
||||||
|
data class GroupCommand(
|
||||||
|
val groupId: Int
|
||||||
|
)
|
@ -0,0 +1,6 @@
|
|||||||
|
package org.example.presenceapp.domain.command
|
||||||
|
|
||||||
|
data class LoginCommand(
|
||||||
|
val email: String,
|
||||||
|
val password: String
|
||||||
|
)
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.example.presenceapp.domain.command.attendance
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
data class AddAttendanceCommand(
|
||||||
|
val attendanceDate: LocalDate,
|
||||||
|
val studentId: Int,
|
||||||
|
val attendanceTypeId: Int,
|
||||||
|
val scheduleId: Int
|
||||||
|
)
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.example.presenceapp.domain.command.attendance
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
data class AddPresettingCommand(
|
||||||
|
val attendanceTypeId: Int,
|
||||||
|
val studentId: Int,
|
||||||
|
val startAt: LocalDate,
|
||||||
|
val endAt: LocalDate,
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp.domain.command.attendance
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
data class GetAttendanceCommand(
|
||||||
|
val groupId: Int,
|
||||||
|
val beforeAt: LocalDate?
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp.domain.command.attendance
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
data class GetPresettingCommand(
|
||||||
|
val groupId: Int,
|
||||||
|
val endAt: LocalDate?
|
||||||
|
)
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.example.presenceapp.domain.common
|
||||||
|
|
||||||
|
import org.example.presenceapp.domain.entities.Schedule
|
||||||
|
|
||||||
|
data class Student(
|
||||||
|
val id: String,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class SomeStudents {
|
||||||
|
val students = listOf(
|
||||||
|
Student(id = "1", name = "Васильев Кирилл"),
|
||||||
|
Student(id = "2", name = "Игнатова Вероника"),
|
||||||
|
Student(id = "3", name = "Латышева Екатерина"),
|
||||||
|
Student(id = "4", name = "Ермолаев Егор"),
|
||||||
|
Student(id = "5", name = "Фролов Владимир"),
|
||||||
|
Student(id = "6", name = "Чеботарева Анастасия"),
|
||||||
|
Student(id = "7", name = "Попова Виктория"),
|
||||||
|
Student(id = "8", name = "Соловьева Лейла"),
|
||||||
|
Student(id = "9", name = "Орлова Анжелика"),
|
||||||
|
Student(id = "10", name = "Осипова Татьяна"),
|
||||||
|
Student(id = "11", name = "Николаева Ева"),
|
||||||
|
Student(id = "12", name = "Федосеева Майя")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object SampleData {
|
||||||
|
val lessonTimes = listOf("9:00", "9:55", "10:50", "11:55", "13:00", "14:00", "14:55", "15:45")
|
||||||
|
}
|
||||||
|
|
||||||
|
object SelectedLessonHolder {
|
||||||
|
var selectedLesson: Schedule? = null
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.example.project.domain.models
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.Month
|
||||||
|
import kotlinx.datetime.number
|
||||||
|
|
||||||
|
|
||||||
|
fun Week.formatWeek(): String {
|
||||||
|
val start = "${startDate.dayOfMonth.toString().padStart(2, '0')}.${startDate.month.number.toString().padStart(2, '0')}"
|
||||||
|
val end = "${endDate.dayOfMonth.toString().padStart(2, '0')}.${endDate.month.number.toString().padStart(2, '0')}"
|
||||||
|
return "$start - $end"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val russianMonths = mapOf(
|
||||||
|
Month.JANUARY to "Январь",
|
||||||
|
Month.FEBRUARY to "Февраль",
|
||||||
|
Month.MARCH to "Март",
|
||||||
|
Month.APRIL to "Апрель",
|
||||||
|
Month.MAY to "Май",
|
||||||
|
Month.JUNE to "Июнь",
|
||||||
|
Month.JULY to "Июль",
|
||||||
|
Month.AUGUST to "Август",
|
||||||
|
Month.SEPTEMBER to "Сентябрь",
|
||||||
|
Month.OCTOBER to "Октябрь",
|
||||||
|
Month.NOVEMBER to "Ноябрь",
|
||||||
|
Month.DECEMBER to "Декабрь"
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Month.toRussianName(): String = russianMonths[this] ?: name
|
||||||
|
|
||||||
|
|
||||||
|
fun LocalDate.formatDay(): String {
|
||||||
|
val currentDay = "${dayOfMonth.toString().padStart(2, '0')}.${month.number.toString().padStart(2, '0')}"
|
||||||
|
return currentDay
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package org.example.presenceapp.domain.entities
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Attendance(
|
||||||
|
val studentId: Int,
|
||||||
|
val attendanceTypeId: Int,
|
||||||
|
val scheduleId: Int,
|
||||||
|
val date: LocalDate,
|
||||||
|
val type: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AttendanceType(
|
||||||
|
val id: Int,
|
||||||
|
var name: String = ""
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.example.presenceapp.domain.entities
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
data class DayData(
|
||||||
|
val date: LocalDate,
|
||||||
|
val isCurrentMonth: Boolean,
|
||||||
|
val attendance: AttendanceType?
|
||||||
|
)
|
@ -0,0 +1,16 @@
|
|||||||
|
package org.example.project.domain.models
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.Month
|
||||||
|
|
||||||
|
data class MonthWithWeeks(
|
||||||
|
val month: Month,
|
||||||
|
val year: Int,
|
||||||
|
val weeks: List<Week>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Week(
|
||||||
|
val startDate: LocalDate,
|
||||||
|
val endDate: LocalDate,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.example.presenceapp.domain.entities
|
||||||
|
|
||||||
|
data class LoginResponse (
|
||||||
|
val user: UserResponse,
|
||||||
|
val token: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UserResponse(
|
||||||
|
val uuid: String,
|
||||||
|
val email: String,
|
||||||
|
val number: String,
|
||||||
|
val fio: String,
|
||||||
|
val role: RoleResponse,
|
||||||
|
var responsible: List<Responsible>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RoleResponse(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GroupResponse(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ResponsibleType(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Responsible(
|
||||||
|
val group: GroupResponse,
|
||||||
|
val responsibleType: ResponsibleType
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.example.presenceapp.domain.entities
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
|
||||||
|
data class Presetting(
|
||||||
|
val id: Int,
|
||||||
|
val studentId: Int,
|
||||||
|
val attendanceType: AttendanceType,
|
||||||
|
val startAt: LocalDate,
|
||||||
|
val endAt: LocalDate
|
||||||
|
)
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.example.presenceapp.domain.entities
|
||||||
|
|
||||||
|
sealed class ResponseState {
|
||||||
|
class Error(val error: String): ResponseState()
|
||||||
|
class Success<T>(val data: T): ResponseState()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Either<A, B> {
|
||||||
|
class Left<A, B>(val value: A): Either<A, B>()
|
||||||
|
class Right<A, B>(val value: B): Either<A, B>()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ServiceError {
|
||||||
|
NOT_FOUND,
|
||||||
|
UNAUTHORIZED,
|
||||||
|
NOT_CREATED
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package org.example.presenceapp.domain.entities
|
||||||
|
|
||||||
|
data class Schedule(
|
||||||
|
val id: Int,
|
||||||
|
val lessonNumber: Int,
|
||||||
|
val audience: String,
|
||||||
|
val subject: Subject,
|
||||||
|
val dayOfWeek: Int,
|
||||||
|
)
|
||||||
|
data class Subject(
|
||||||
|
val id: Int,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.example.presenceapp.domain.entities
|
||||||
|
|
||||||
|
object UserInfo {
|
||||||
|
var userName: String = ""
|
||||||
|
var userGroup: String = ""
|
||||||
|
var userRole: String = ""
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp.domain.repo
|
||||||
|
|
||||||
|
import org.example.presenceapp.domain.command.LoginCommand
|
||||||
|
import org.example.presenceapp.domain.entities.LoginResponse
|
||||||
|
|
||||||
|
interface LoginRepository {
|
||||||
|
suspend fun login(loginCommand: LoginCommand): LoginResponse
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.example.presenceapp.domain.repo
|
||||||
|
|
||||||
|
import org.example.presenceapp.domain.command.GroupCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Schedule
|
||||||
|
|
||||||
|
interface ScheduleRepository {
|
||||||
|
suspend fun getSchedule(groupCommand: GroupCommand): List<Schedule>
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.example.presenceapp.domain.repo.attendance
|
||||||
|
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.GetAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Attendance
|
||||||
|
|
||||||
|
interface AttendanceRepository {
|
||||||
|
suspend fun addAttendance(addAttendanceCommand: AddAttendanceCommand): List<Attendance>
|
||||||
|
suspend fun getAttendance(getAttendanceCommand: GetAttendanceCommand): List<Attendance>
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.example.presenceapp.domain.repo.attendance
|
||||||
|
|
||||||
|
import org.example.presenceapp.domain.entities.AttendanceType
|
||||||
|
|
||||||
|
interface AttendanceTypeRepository {
|
||||||
|
suspend fun getAttendanceTypes(): List<AttendanceType>
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.example.presenceapp.domain.repo.attendance
|
||||||
|
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddPresettingCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.GetPresettingCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Presetting
|
||||||
|
|
||||||
|
interface PresettingRepository {
|
||||||
|
suspend fun addPresetting(addPresettingCommand: AddPresettingCommand): Boolean
|
||||||
|
suspend fun getPresetting(getPresettingCommand: GetPresettingCommand): List<Presetting>
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package org.example.presenceapp.domain.usecases
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.AddPresettingCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.GetAttendanceCommand
|
||||||
|
import org.example.presenceapp.domain.command.attendance.GetPresettingCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Attendance
|
||||||
|
import org.example.presenceapp.domain.entities.Either
|
||||||
|
import org.example.presenceapp.domain.entities.Presetting
|
||||||
|
import org.example.presenceapp.domain.repo.attendance.AttendanceRepository
|
||||||
|
|
||||||
|
class AttendanceUseCase(
|
||||||
|
private val attendanceRepository: AttendanceRepository
|
||||||
|
) {
|
||||||
|
fun addAttendance(addAttendanceCommand: AddAttendanceCommand): Flow<Either<Exception, Attendance>> = flow {
|
||||||
|
return@flow try {
|
||||||
|
val result = attendanceRepository.addAttendance(addAttendanceCommand)
|
||||||
|
emit(Either.Right(result))
|
||||||
|
} catch (e:Exception) {
|
||||||
|
emit(Either.Left(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAttendance(getAttendanceCommand: GetAttendanceCommand): Flow<Either<Exception, List<Attendance>>> = flow {
|
||||||
|
return@flow try {
|
||||||
|
val result = attendanceRepository.getAttendance(getAttendanceCommand)
|
||||||
|
emit(Either.Right(result))
|
||||||
|
} catch (e:Exception) {
|
||||||
|
emit(Either.Left(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addPresetting(addPresettingCommand: AddPresettingCommand): Flow<Either<Exception, Presetting>> = flow {
|
||||||
|
return@flow try {
|
||||||
|
val result = attendanceRepository.addPresetting(addPresettingCommand)
|
||||||
|
emit(Either.Right(result))
|
||||||
|
} catch (e:Exception) {
|
||||||
|
emit(Either.Left(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPresetting(getPresettingCommand: GetPresettingCommand): Flow<Either<Exception, List<Presetting>>> = flow {
|
||||||
|
return@flow try {
|
||||||
|
val result = attendanceRepository.getPresetting(getPresettingCommand)
|
||||||
|
emit(Either.Right(result))
|
||||||
|
} catch (e:Exception) {
|
||||||
|
emit(Either.Left(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.example.presenceapp.domain.usecases
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import org.example.presenceapp.domain.entities.Either
|
||||||
|
import org.example.presenceapp.domain.command.LoginCommand
|
||||||
|
import org.example.presenceapp.domain.entities.LoginResponse
|
||||||
|
import org.example.presenceapp.domain.repo.LoginRepository
|
||||||
|
|
||||||
|
class LoginUseCase(
|
||||||
|
private val loginRepository: LoginRepository
|
||||||
|
) {
|
||||||
|
fun login(loginCommand: LoginCommand): Flow<Either<Exception, LoginResponse>> = flow {
|
||||||
|
return@flow try {
|
||||||
|
val result = loginRepository.login(loginCommand)
|
||||||
|
emit(Either.Right(result))
|
||||||
|
} catch (e:Exception) {
|
||||||
|
emit(Either.Left(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.example.presenceapp.domain.usecases
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import org.example.presenceapp.domain.command.GroupCommand
|
||||||
|
import org.example.presenceapp.domain.entities.Either
|
||||||
|
import org.example.presenceapp.domain.entities.Schedule
|
||||||
|
import org.example.presenceapp.domain.repo.ScheduleRepository
|
||||||
|
|
||||||
|
class ScheduleUseCase(
|
||||||
|
private val scheduleRepository: ScheduleRepository
|
||||||
|
) {
|
||||||
|
fun getSchedule(groupCommand: GroupCommand): Flow<Either<Exception, List<Schedule>>> = flow {
|
||||||
|
return@flow try {
|
||||||
|
val result = scheduleRepository.getSchedule(groupCommand)
|
||||||
|
emit(Either.Right(result))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emit(Either.Left(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|