This commit is contained in:
Nana 2025-04-23 12:17:15 +03:00
commit 9703a42d64
179 changed files with 3083 additions and 0 deletions

18
.gitignore vendored Normal file
View 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
View 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 thats 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 Apples CoreCrypto for the iOS part of your Kotlin app,
`iosMain` would be the right folder for such calls.
* `/iosApp` contains iOS applications. Even if youre 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
View 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
View 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)
}

View 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>

View File

@ -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()
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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()
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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()) }
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">PresenceApp</string>
</resources>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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())
}
}

View File

@ -0,0 +1,7 @@
package org.example.presenceapp
interface Platform {
val name: String
}
expect fun getPlatform(): Platform

View File

@ -0,0 +1,8 @@
package org.example.presenceapp
import androidx.compose.runtime.Composable
expect class PlatformContext
@Composable
expect fun getPlatformContext(): PlatformContext

View File

@ -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
)

View File

@ -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?,
)

View File

@ -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,
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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
)

View File

@ -0,0 +1,8 @@
package org.example.presenceapp.data.common.dto.schedule
import kotlinx.serialization.Serializable
@Serializable
data class ScheduleRequestDto(
val groupId: Int
)

View File

@ -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
)

View File

@ -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())
}
}
}

View File

@ -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
}

View File

@ -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>>
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -0,0 +1,7 @@
package org.example.project.data.local
interface TokenStorage {
suspend fun getToken(): String?
suspend fun setToken(token: String)
suspend fun deleteToken()
}

View File

@ -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>
}

View File

@ -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
}

View File

@ -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>
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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>()
}
}

View File

@ -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() }
}
}

View File

@ -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()
}
}

View File

@ -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() }
}
}

View File

@ -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)
}

View File

@ -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
)
}
}

View File

@ -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()) }
}

View File

@ -0,0 +1,5 @@
package org.example.presenceapp.domain.command
data class GroupCommand(
val groupId: Int
)

View File

@ -0,0 +1,6 @@
package org.example.presenceapp.domain.command
data class LoginCommand(
val email: String,
val password: String
)

View File

@ -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
)

View File

@ -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,
)

View File

@ -0,0 +1,8 @@
package org.example.presenceapp.domain.command.attendance
import kotlinx.datetime.LocalDate
data class GetAttendanceCommand(
val groupId: Int,
val beforeAt: LocalDate?
)

View File

@ -0,0 +1,8 @@
package org.example.presenceapp.domain.command.attendance
import kotlinx.datetime.LocalDate
data class GetPresettingCommand(
val groupId: Int,
val endAt: LocalDate?
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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 = ""
)

View File

@ -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?
)

View File

@ -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,
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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
}

View File

@ -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
)

View File

@ -0,0 +1,7 @@
package org.example.presenceapp.domain.entities
object UserInfo {
var userName: String = ""
var userGroup: String = ""
var userRole: String = ""
}

View File

@ -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
}

View File

@ -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>
}

View File

@ -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>
}

View File

@ -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>
}

View File

@ -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>
}

View File

@ -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))
}
}
}

View File

@ -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))
}
}
}

View File

@ -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))
}
}
}

Some files were not shown because too many files have changed in this diff Show More