From 47a0e7dc3ccc1a63c2392ae99302b61448fcdee3 Mon Sep 17 00:00:00 2001 From: Nana <10011001nana@gmail.com> Date: Thu, 15 May 2025 11:21:46 +0300 Subject: [PATCH] init --- .../attendance/AttendanceStorage.android.kt | 8 +- .../settings/SettingsManager.android.kt | 2 +- .../kotlin/org/example/presenceapp/App.kt | 3 +- .../example/presenceapp/data/common/Mapper.kt | 80 +++- .../dto/attendance/AttendanceRequestDto.kt | 7 - .../dto/attendance/AttendanceResponseDto.kt | 11 +- .../dto/attendance/PresettingRequestDto.kt | 11 + .../dto/attendance/PresettingResponseDto.kt | 13 + .../common/dto/group/StudentRequestDto.kt | 8 + .../{StudentDto.kt => StudentResponseDto.kt} | 0 .../common/dto/schedule/ScheduleRequestDto.kt | 2 +- .../presenceapp/data/local/LocalDataSource.kt | 9 +- .../storage/attendance/AttendanceStorage.kt | 6 +- .../data/remote/api/AttendanceApi.kt | 8 +- .../presenceapp/data/remote/api/GroupApi.kt | 6 +- .../data/remote/impl/AttendanceApiImpl.kt | 9 +- .../data/remote/impl/ScheduleApiImpl.kt | 6 +- .../repository/AttendanceNetRepository.kt | 27 +- .../data/repository/ScheduleNetRepository.kt | 10 +- .../repository/settings/SettingsRepository.kt | 6 +- .../settings/SettingsRepositoryImpl.kt | 8 +- .../example/presenceapp/di/NetworkModule.kt | 4 +- .../domain/command/GroupCommand.kt | 5 - .../attendance/GetAttendanceTypesCommand.kt | 6 + .../schedule/GetStudentsByGroupIdCommand.kt | 5 + .../domain/command/schedule/GroupCommand.kt | 5 + .../presenceapp/domain/common/SomeStudents.kt | 37 +- .../domain/common/ToDayMonthString.kt | 1 + .../presenceapp/domain/entities/Attendance.kt | 2 +- .../domain/entities/AttendanceView.kt | 26 ++ .../presenceapp/domain/entities/DayData.kt | 2 +- .../presenceapp/domain/entities/DaysOfWeek.kt | 2 +- .../domain/entities/ResponseState.kt | 6 - .../presenceapp/domain/entities/Schedule.kt | 16 + .../domain/repo/AttendanceRepository.kt | 24 + .../domain/repo/ScheduleRepository.kt | 7 +- .../repo/attendance/AttendanceRepository.kt | 10 - .../attendance/AttendanceTypeRepository.kt | 7 - .../repo/attendance/PresettingRepository.kt | 10 - .../domain/usecases/AttendanceUseCase.kt | 112 ++++- .../domain/usecases/ScheduleUseCase.kt | 14 +- .../ui/attendance/AttendanceScreen.kt | 0 .../ui/attendance/AttendanceScreenModel.kt | 0 .../attendance/components/AttendanceColumn.kt | 0 .../components/AttendanceToString.kt | 0 .../presenceapp/ui/base/BaseViewModel.kt | 61 +++ .../presenceapp/ui/calendar/CalendarScreen.kt | 0 .../ui/calendar/CalendarScreenModel.kt | 0 .../ui/calendar/components/CalendarDayCell.kt | 0 .../ui/calendar/components/CalendarGrid.kt | 0 .../ui/calendar/components/CalendarMonth.kt | 0 .../ui/calendar/components/CalendarUtils.kt | 0 .../calendar/components/CalendarWeekDays.kt | 0 .../presenceapp/ui/commons/CommonBottomBar.kt | 0 .../presenceapp/ui/commons/CommonButton.kt | 0 .../presenceapp/ui/commons/CommonDataText.kt | 0 .../presenceapp/ui/commons/CommonDialog.kt | 0 .../ui/commons/CommonIconButton.kt | 0 .../presenceapp/ui/commons/CommonLabel.kt | 0 .../presenceapp/ui/commons/CommonMainText.kt | 0 .../ui/commons/CommonMediumText.kt | 0 .../ui/commons/CommonRegularText.kt | 0 .../presenceapp/ui/commons/CommonTopBar.kt | 0 .../presenceapp/ui/commons/ErrorDialog.kt | 0 .../feature/attendance/AttendanceContract.kt | 48 ++ .../ui/feature/attendance/AttendanceScreen.kt | 147 ++++++ .../attendance/AttendanceScreenModel.kt | 425 ++++++++++++++++++ .../composables/AttendanceColumn.kt | 90 ++++ .../composables/AttendanceDialog.kt | 77 ++++ .../composables/AttendanceHeader.kt | 24 + .../attendance/composables/AttendanceItem.kt | 76 ++++ .../attendance/composables/AttendanceList.kt | 63 +++ .../composables/AttendanceSelection.kt | 52 +++ .../composables/AttendanceToString.kt | 19 + .../ui/feature/calendar/CalendarScreen.kt | 84 ++++ .../feature/calendar/CalendarScreenModel.kt | 47 ++ .../calendar/components/CalendarDayCell.kt | 60 +++ .../calendar/components/CalendarGrid.kt | 31 ++ .../calendar/components/CalendarMonth.kt | 20 + .../calendar/components/CalendarUtils.kt | 35 ++ .../calendar/components/CalendarWeekDays.kt | 29 ++ .../ui/feature/commons/CommonBottomBar.kt | 101 +++++ .../ui/feature/commons/CommonButton.kt | 25 ++ .../ui/feature/commons/CommonDataText.kt | 22 + .../ui/feature/commons/CommonDialog.kt | 23 + .../ui/feature/commons/CommonIconButton.kt | 56 +++ .../ui/feature/commons/CommonLabel.kt | 22 + .../ui/feature/commons/CommonMainText.kt | 24 + .../ui/feature/commons/CommonMediumText.kt | 22 + .../ui/feature/commons/CommonRegularText.kt | 24 + .../ui/feature/commons/CommonTopBar.kt | 111 +++++ .../ui/feature/commons/ErrorDialog.kt | 36 ++ .../presenceapp/ui/feature/info/InfoScreen.kt | 71 +++ .../ui/feature/info/InfoScreenModel.kt | 25 ++ .../ui/feature/info/InfoScreenState.kt | 7 + .../ui/feature/info/components/InfoCard.kt | 46 ++ .../ui/feature/login/LoginScreen.kt | 116 +++++ .../ui/feature/login/LoginScreenModel.kt | 100 +++++ .../ui/feature/login/LoginScreenState.kt | 32 ++ .../feature/login/components/LoginButton.kt | 36 ++ .../feature/login/components/LoginCheckBox.kt | 50 +++ .../login/components/LoginTextField.kt | 53 +++ .../ui/feature/schedule/ScheduleScreen.kt | 112 +++++ .../feature/schedule/ScheduleScreenModel.kt | 57 +++ .../feature/schedule/ScheduleScreenState.kt | 17 + .../components/EmptyScheduleLessonsItem.kt | 67 +++ .../components/ScheduleDaySelector.kt | 77 ++++ .../schedule/components/ScheduleDayText.kt | 50 +++ .../schedule/components/ScheduleLessonItem.kt | 66 +++ .../schedule/components/ScheduleLessonList.kt | 54 +++ .../presenceapp/ui/feature/settings/Preset.kt | 70 +++ .../ui/feature/settings/SettingsManager.kt | 12 + .../ui/feature/settings/SettingsScreen.kt | 51 +++ .../feature/settings/SettingsScreenModel.kt | 38 ++ .../components/SettingsReasonOption.kt | 92 ++++ .../ui/feature/weeks/WeeksScreen.kt | 107 +++++ .../ui/feature/weeks/WeeksScreenState.kt | 17 + .../ui/feature/weeks/WeeksViewModel.kt | 104 +++++ .../feature/weeks/components/MonthHeader.kt | 20 + .../feature/weeks/components/ScheduleCard.kt | 43 ++ .../example/presenceapp/ui/info/InfoScreen.kt | 0 .../presenceapp/ui/info/InfoScreenModel.kt | 0 .../presenceapp/ui/info/InfoScreenState.kt | 0 .../ui/info/components/InfoCard.kt | 0 .../presenceapp/ui/login/LoginScreen.kt | 0 .../presenceapp/ui/login/LoginScreenState.kt | 0 .../presenceapp/ui/login/LoginViewModel.kt | 0 .../ui/login/components/LoginButton.kt | 0 .../ui/login/components/LoginCheckBox.kt | 0 .../ui/login/components/LoginTextField.kt | 0 .../presenceapp/ui/schedule/ScheduleScreen.kt | 0 .../ui/schedule/ScheduleScreenModel.kt | 0 .../ui/schedule/ScheduleScreenState.kt | 0 .../components/EmptyScheduleLessonsItem.kt | 0 .../components/ScheduleDaySelector.kt | 0 .../ui/schedule/components/ScheduleDayText.kt | 0 .../schedule/components/ScheduleLessonItem.kt | 0 .../schedule/components/ScheduleLessonList.kt | 0 .../example/presenceapp/ui/settings/Preset.kt | 0 .../ui/settings/SettingsManager.kt | 0 .../presenceapp/ui/settings/SettingsScreen.kt | 0 .../ui/settings/SettingsScreenModel.kt | 0 .../components/SettingsReasonOption.kt | 0 .../org/example/presenceapp/ui/theme/Theme.kt | 123 +++++ .../ui/theme/themeManager/ThemeManager.kt | 6 + .../ui/theme/themeManager/ThemeState.kt | 26 ++ .../ui/theme/themeManager/ThemeTypes.kt | 7 + .../presenceapp/ui/types/AbsenceType.kt | 27 ++ .../presenceapp/ui/types/ButtonType.kt | 6 + .../presenceapp/ui/types/ScreenType.kt | 17 + .../presenceapp/ui/weeks/WeeksScreen.kt | 0 .../presenceapp/ui/weeks/WeeksScreenState.kt | 0 .../presenceapp/ui/weeks/WeeksViewModel.kt | 0 .../ui/weeks/components/MonthHeader.kt | 0 .../ui/weeks/components/ScheduleCard.kt | 0 .../settings/SettingsManager.ios.kt | 2 +- gradle/libs.versions.toml | 2 + 157 files changed, 4047 insertions(+), 154 deletions(-) rename composeApp/src/androidMain/kotlin/org/example/presenceapp/ui/{ => feature}/settings/SettingsManager.android.kt (94%) create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingRequestDto.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingResponseDto.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentRequestDto.kt rename composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/{StudentDto.kt => StudentResponseDto.kt} (100%) delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/GroupCommand.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/attendance/GetAttendanceTypesCommand.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GetStudentsByGroupIdCommand.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GroupCommand.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/AttendanceView.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/AttendanceRepository.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceRepository.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceTypeRepository.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/PresettingRepository.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/AttendanceScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/AttendanceScreenModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/components/AttendanceColumn.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/components/AttendanceToString.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/base/BaseViewModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/CalendarScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/CalendarScreenModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarDayCell.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarGrid.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarMonth.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarUtils.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarWeekDays.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonBottomBar.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonButton.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonDataText.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonDialog.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonIconButton.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonLabel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonMainText.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonMediumText.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonRegularText.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonTopBar.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/ErrorDialog.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceContract.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreenModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceColumn.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceDialog.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceHeader.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceItem.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceList.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceSelection.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceToString.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreenModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarDayCell.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarGrid.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarMonth.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarUtils.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarWeekDays.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonBottomBar.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonButton.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDataText.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDialog.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonIconButton.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonLabel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMainText.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMediumText.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonRegularText.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonTopBar.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/ErrorDialog.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenState.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/components/InfoCard.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenState.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginButton.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginCheckBox.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginTextField.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenState.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/EmptyScheduleLessonsItem.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDaySelector.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDayText.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonItem.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonList.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/Preset.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreenModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/components/SettingsReasonOption.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreenState.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/MonthHeader.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/ScheduleCard.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreenModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreenState.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/components/InfoCard.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginScreenState.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginViewModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginButton.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginCheckBox.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginTextField.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreenModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreenState.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/EmptyScheduleLessonsItem.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleDaySelector.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleDayText.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleLessonItem.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleLessonList.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/Preset.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsScreenModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/components/SettingsReasonOption.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/AbsenceType.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksScreenState.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksViewModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/components/MonthHeader.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/components/ScheduleCard.kt rename composeApp/src/iosMain/kotlin/org/example/presenceapp/ui/{ => feature}/settings/SettingsManager.ios.kt (93%) diff --git a/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.android.kt b/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.android.kt index 5500874..dea9b45 100644 --- a/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.android.kt @@ -7,7 +7,7 @@ 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 +import org.example.presenceapp.domain.entities.AttendanceView private val Context.attendanceDataStore by preferencesDataStore(name = "attendance_prefs") @@ -15,17 +15,17 @@ class AttendanceStorageAndroid(private val context: Context): AttendanceStorage private val ATTENDANCE_KEY = stringPreferencesKey("attendance_map") - override suspend fun saveAttendanceMap(map: Map) { + override suspend fun saveAttendanceMap(map: Map) { val json = Json.encodeToString(map) context.attendanceDataStore.edit { prefs -> prefs[ATTENDANCE_KEY] = json } } - override fun attendanceMapFlow(): Flow> { + override fun attendanceMapFlow(): Flow> { return context.attendanceDataStore.data.map { prefs -> prefs[ATTENDANCE_KEY]?.let { - Json.decodeFromString>(it) + Json.decodeFromString>(it) } ?: emptyMap() } } diff --git a/composeApp/src/androidMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.android.kt b/composeApp/src/androidMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.android.kt similarity index 94% rename from composeApp/src/androidMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.android.kt rename to composeApp/src/androidMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.android.kt index 59679ef..e9f55f0 100644 --- a/composeApp/src/androidMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.android.kt +++ b/composeApp/src/androidMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.android.kt @@ -1,4 +1,4 @@ -package org.example.presenceapp.ui.settings +package org.example.presenceapp.ui.feature.settings import android.content.Context import android.content.SharedPreferences diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/App.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/App.kt index ae79935..9b9022c 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/App.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/App.kt @@ -2,9 +2,8 @@ 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.feature.login.LoginScreen import org.example.presenceapp.ui.theme.AppTheme -import org.example.project.ui.login.LoginScreen import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/Mapper.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/Mapper.kt index d8f23e8..6e3f223 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/Mapper.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/Mapper.kt @@ -2,6 +2,7 @@ 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.AttendanceTypeResponseDto 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 @@ -11,15 +12,21 @@ 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.group.StudentRequestDto +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.common.dto.schedule.SubjectResponseDto +import org.example.presenceapp.domain.command.LoginCommand 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.command.schedule.GetStudentsByGroupIdCommand +import org.example.presenceapp.domain.command.schedule.GroupCommand +import org.example.presenceapp.domain.entities.AbsenceReason import org.example.presenceapp.domain.entities.Attendance import org.example.presenceapp.domain.entities.AttendanceType +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.domain.entities.AttendanceView import org.example.presenceapp.domain.entities.GroupResponse import org.example.presenceapp.domain.entities.LoginResponse import org.example.presenceapp.domain.entities.Presetting @@ -27,6 +34,7 @@ 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.Student import org.example.presenceapp.domain.entities.Subject import org.example.presenceapp.domain.entities.UserResponse @@ -37,6 +45,10 @@ fun LoginCommand.toDto(): AuthRequestDto = AuthRequestDto( fun GroupCommand.toDto(): ScheduleRequestDto = ScheduleRequestDto(groupId) +fun GetStudentsByGroupIdCommand.toDto(): StudentRequestDto = StudentRequestDto( + id = groupId +) + fun AddAttendanceCommand.toDto(): AttendanceRequestDto = AttendanceRequestDto( studentId = studentId, scheduleId = scheduleId, @@ -50,6 +62,27 @@ fun AddPresettingCommand.toDto(): PresettingRequestDto = PresettingRequestDto( endAt = endAt ) +fun AttendanceView.toDto( + scheduleId: Int, + absenceReason: String? +): Attendance { + return Attendance( + studentId = this.studentId, + attendanceTypeId = this.type.toDto(absenceReason) + ?: throw IllegalArgumentException("Unknown AttendanceTypeView: $type with reason: $absenceReason"), + scheduleId = scheduleId, + date = this.date, + type = this.type.ordinal + ) +} + +fun AttendanceTypeView.toDto(absenceReason: String?): Int? { + return when (this) { + AttendanceTypeView.PRESENT -> 4 + AttendanceTypeView.ABSENT -> absenceReason?.let { AbsenceReason.valueOf(it).id } + } +} + fun ScheduleResponseDto.toEntity(): Schedule = Schedule( @@ -60,6 +93,15 @@ fun ScheduleResponseDto.toEntity(): Schedule = Schedule( id = id ) +fun StudentResponseDto.toEntity(): Student = Student( + id = studentId, + uuid = uuid, + fio = fio, + role = "", + enrollDate = enrollDate, + expulsionDate = expulsionDate +) + fun SubjectResponseDto.toEntity(): Subject = Subject( id = id, name = name @@ -78,22 +120,18 @@ fun UserResponseDto.toEntity(): UserResponse = UserResponse( 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 @@ -107,6 +145,11 @@ fun AttendanceResponseDto.toEntity(): Attendance = Attendance( type = attendanceTypeId ) +fun AttendanceTypeResponseDto.toAttendanceType(): AttendanceType = AttendanceType( + id = id, + name = name +) + fun AttendanceResponseDto.toAttendanceType(): AttendanceType = AttendanceType( id = attendanceTypeId, name = "" @@ -117,4 +160,27 @@ fun PresettingResponseDto.toEntity(): Presetting = Presetting( studentId = studentId, startAt = startAt, endAt = endAt -) \ No newline at end of file +) + +fun Attendance.toEntity( + attendanceTypeMapper: (Int) -> AttendanceTypeView, + absenceReasonProvider: (Int, Int) -> String? +): AttendanceView { + return AttendanceView( + studentId = this.studentId, + date = this.date, + type = attendanceTypeMapper(this.attendanceTypeId), + isModified = false, + absenceReason = absenceReasonProvider(this.studentId, this.attendanceTypeId) + ) +} + +fun AttendanceType.toEntity(id: Int): Pair { + return when (id) { + 4 -> AttendanceTypeView.PRESENT to null + 1 -> AttendanceTypeView.ABSENT to AbsenceReason.SICK + 2 -> AttendanceTypeView.ABSENT to AbsenceReason.COMPETITION + 3 -> AttendanceTypeView.ABSENT to AbsenceReason.SKIP + else -> throw IllegalArgumentException("Unknown attendance id: $id") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceRequestDto.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceRequestDto.kt index 59f5e72..2854ac8 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceRequestDto.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceRequestDto.kt @@ -9,11 +9,4 @@ data class AttendanceRequestDto( val scheduleId: Int, val attendanceTypeId: Int, val attendanceDate: LocalDate -) - -@Serializable -data class PresettingRequestDto( - val attendanceTypeId: Int, - val startAt: LocalDate, - val endAt: LocalDate?, ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceResponseDto.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceResponseDto.kt index 5894106..2bfb47f 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceResponseDto.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/AttendanceResponseDto.kt @@ -15,14 +15,5 @@ data class AttendanceResponseDto( @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, + val name: String ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingRequestDto.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingRequestDto.kt new file mode 100644 index 0000000..27f1ede --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingRequestDto.kt @@ -0,0 +1,11 @@ +package org.example.presenceapp.data.common.dto.attendance + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable + +@Serializable +data class PresettingRequestDto( + val attendanceTypeId: Int, + val startAt: LocalDate, + val endAt: LocalDate? +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingResponseDto.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingResponseDto.kt new file mode 100644 index 0000000..5c158c5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/attendance/PresettingResponseDto.kt @@ -0,0 +1,13 @@ +package org.example.presenceapp.data.common.dto.attendance + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable + +@Serializable +data class PresettingResponseDto( + val id: Int, + val attendanceType: AttendanceResponseDto, + val studentId: Int, + val startAt: LocalDate, + val endAt: LocalDate +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentRequestDto.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentRequestDto.kt new file mode 100644 index 0000000..dd1ff8d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentRequestDto.kt @@ -0,0 +1,8 @@ +package org.example.presenceapp.data.common.dto.group + +import kotlinx.serialization.Serializable + +@Serializable +data class StudentRequestDto( + val id: Int +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentDto.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentResponseDto.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentDto.kt rename to composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/group/StudentResponseDto.kt diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/schedule/ScheduleRequestDto.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/schedule/ScheduleRequestDto.kt index 32826ee..bd5f260 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/schedule/ScheduleRequestDto.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/common/dto/schedule/ScheduleRequestDto.kt @@ -5,4 +5,4 @@ import kotlinx.serialization.Serializable @Serializable data class ScheduleRequestDto( val groupId: Int -) +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/LocalDataSource.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/LocalDataSource.kt index 1697f8a..1ee8bcf 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/LocalDataSource.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/LocalDataSource.kt @@ -2,19 +2,16 @@ 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.domain.entities.AttendanceView 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) { + suspend fun saveAttendance(map: Map) { attendanceStorage.saveAttendanceMap(map) } - fun observeAttendance(): Flow> { + fun observeAttendance(): Flow> { return attendanceStorage.attendanceMapFlow() .map { attendanceMap -> attendanceMap diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.kt index 5d80c32..390238f 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/local/storage/attendance/AttendanceStorage.kt @@ -1,9 +1,9 @@ package org.example.presenceapp.data.local.storage.attendance import kotlinx.coroutines.flow.Flow -import org.example.presenceapp.domain.entities.Attendance +import org.example.presenceapp.domain.entities.AttendanceView interface AttendanceStorage { - suspend fun saveAttendanceMap(map: Map) - fun attendanceMapFlow(): Flow> + suspend fun saveAttendanceMap(map: Map) + fun attendanceMapFlow(): Flow> } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/AttendanceApi.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/AttendanceApi.kt index 5e2efe1..04484b8 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/AttendanceApi.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/AttendanceApi.kt @@ -12,14 +12,14 @@ import org.example.presenceapp.domain.command.attendance.AddPresettingCommand interface AttendanceApi { @POST("api/v1/presence") - suspend fun addAttendance(@Body commands: List): List - - @GET("api/v1/presence/dictionary/attendance_type") - suspend fun getAttendanceTypes(): List + suspend fun addAttendance(@Body command: List): List @GET("api/v1/presence/{groupId}") suspend fun getAttendance(@Path("groupId") groupId: Int): List + @GET("api/v1/presence/dictionary/attendance_type/{typeId}") + suspend fun getAttendanceTypes(@Path("typeId") typeId: Int): List + @POST("api/v1/presence/presetting") suspend fun addPresetting(@Body command: AddPresettingCommand): Boolean diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/GroupApi.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/GroupApi.kt index de84433..f3577d6 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/GroupApi.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/api/GroupApi.kt @@ -5,14 +5,12 @@ 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 +import org.example.presenceapp.domain.entities.Group interface GroupApi { @GET("api/v1/group/{id}/schedule") suspend fun getSchedule(@Path id: Int): List @GET("api/v1/group/{id}/students") - suspend fun getStudents(@Path id: Int): List - - @GET("api/v1/group/{id}/presence") - suspend fun getPresence(@Path id: Int): List + suspend fun getStudentsByGroupId(@Path id: Int): List } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/AttendanceApiImpl.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/AttendanceApiImpl.kt index 522f0b0..4474e99 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/AttendanceApiImpl.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/AttendanceApiImpl.kt @@ -7,16 +7,17 @@ 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 +import org.example.presenceapp.domain.command.attendance.GetAttendanceTypesCommand class AttendanceApiImpl(private val attendanceApi: AttendanceApi) { - suspend fun addAttendance(commands: List): List = - attendanceApi.addAttendance(commands.map { it.toDto() }) + suspend fun addAttendance(command: List): List = + attendanceApi.addAttendance(command.map { it.toDto() }) suspend fun getAttendance(groupId: Int): List = attendanceApi.getAttendance(groupId) - suspend fun getAttendanceTypes(): List = - attendanceApi.getAttendanceTypes() + suspend fun getAttendanceTypes(typeId: Int): List = + attendanceApi.getAttendanceTypes(typeId) suspend fun addPresetting(command: AddPresettingCommand): Boolean = attendanceApi.addPresetting(command) diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/ScheduleApiImpl.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/ScheduleApiImpl.kt index 4ecf352..1f4485c 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/ScheduleApiImpl.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/remote/impl/ScheduleApiImpl.kt @@ -1,13 +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.StudentRequestDto 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 +import org.example.presenceapp.domain.entities.Group class ScheduleApiImpl(private val groupApi: GroupApi) { suspend fun getSchedule(scheduleRequestDto: ScheduleRequestDto): List = groupApi.getSchedule(scheduleRequestDto.groupId) - suspend fun getStudent(scheduleRequestDto: ScheduleRequestDto): List = groupApi.getStudents(scheduleRequestDto.groupId) - suspend fun getAttendance(scheduleRequestDto: ScheduleRequestDto): List = groupApi.getPresence(scheduleRequestDto.groupId) + suspend fun getStudentsByGroupId(studentRequestDto: StudentRequestDto): List = groupApi.getStudentsByGroupId(studentRequestDto.id) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/AttendanceNetRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/AttendanceNetRepository.kt index fde4d08..97bc2a6 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/AttendanceNetRepository.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/AttendanceNetRepository.kt @@ -3,35 +3,33 @@ 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.toAttendanceType 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.GetAttendanceTypesCommand import org.example.presenceapp.domain.command.attendance.GetPresettingCommand import org.example.presenceapp.domain.entities.Attendance +import org.example.presenceapp.domain.entities.AttendanceType +import org.example.presenceapp.domain.entities.AttendanceView import org.example.presenceapp.domain.entities.Presetting -import org.example.presenceapp.domain.repo.attendance.AttendanceRepository -import org.example.presenceapp.domain.repo.attendance.PresettingRepository +import org.example.presenceapp.domain.repo.AttendanceRepository class AttendanceNetRepository( private val localDataSource: LocalDataSource, private val attendanceApiImpl: AttendanceApiImpl -): AttendanceRepository, PresettingRepository { - suspend fun saveAttendanceLocally(attendance: Map) { +): AttendanceRepository { + override suspend fun saveAttendanceLocally(attendance: Map) { localDataSource.saveAttendance(attendance) } - fun observeLocalAttendance(): Flow> { + override fun observeLocalAttendance(): Flow> { return localDataSource.observeAttendance() - .map { attendanceMap -> - attendanceMap - } - .catch { e -> - emit(emptyMap()) - } + .map { attendanceMap -> attendanceMap } + .catch { e -> emit(emptyMap()) } } override suspend fun addAttendance(addAttendanceCommand: AddAttendanceCommand): List { @@ -44,6 +42,11 @@ class AttendanceNetRepository( return result.map { it.toEntity() } } + override suspend fun getAttendanceTypes(getAttendanceTypesCommand: GetAttendanceTypesCommand): List { + val result = attendanceApiImpl.getAttendanceTypes(getAttendanceTypesCommand.typeId) + return result.map { it.toAttendanceType() } + } + override suspend fun addPresetting(addPresettingCommand: AddPresettingCommand): Boolean { return attendanceApiImpl.addPresetting(addPresettingCommand) } diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/ScheduleNetRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/ScheduleNetRepository.kt index 2bf2db0..db35ad8 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/ScheduleNetRepository.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/ScheduleNetRepository.kt @@ -3,8 +3,10 @@ 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.command.schedule.GetStudentsByGroupIdCommand +import org.example.presenceapp.domain.command.schedule.GroupCommand import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.Student import org.example.presenceapp.domain.repo.ScheduleRepository class ScheduleNetRepository( @@ -14,4 +16,10 @@ class ScheduleNetRepository( val result = scheduleApiImpl.getSchedule(groupCommand.toDto()) return result.map { it.toEntity() } } + + override suspend fun getStudentsByGroupId(getStudentsByGroupIdCommand: GetStudentsByGroupIdCommand): List { + val result = scheduleApiImpl.getStudentsByGroupId(getStudentsByGroupIdCommand.toDto()) + println("Students from API: $result") + return result.map { it.toEntity() } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepository.kt index f766123..9249746 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepository.kt @@ -1,8 +1,8 @@ package org.example.presenceapp.data.repository.settings -import org.example.presenceapp.domain.entities.AttendanceType +import org.example.presenceapp.domain.entities.AttendanceTypeView interface SettingsRepository { - suspend fun getDefaultAttendanceStatus(): AttendanceType - suspend fun setDefaultAttendanceStatus(type: AttendanceType) + suspend fun getDefaultAttendanceStatus(): AttendanceTypeView + suspend fun setDefaultAttendanceStatus(type: AttendanceTypeView) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepositoryImpl.kt index d4fe695..84c3ebc 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/data/repository/settings/SettingsRepositoryImpl.kt @@ -1,21 +1,21 @@ package org.example.presenceapp.data.repository.settings +import org.example.presenceapp.domain.entities.AttendanceTypeView 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 { + override suspend fun getDefaultAttendanceStatus(): AttendanceTypeView { val statusString = settingsStorage.get( key = "default_attendance_status", - defaultValue = AttendanceType.ABSENT.name + defaultValue = AttendanceTypeView.ABSENT.name ) return enumValueOf(statusString) } - override suspend fun setDefaultAttendanceStatus(type: AttendanceType) { + override suspend fun setDefaultAttendanceStatus(type: AttendanceTypeView) { settingsStorage.put( key = "default_attendance_status", value = type.name diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/di/NetworkModule.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/di/NetworkModule.kt index 028850f..e665086 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/di/NetworkModule.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/di/NetworkModule.kt @@ -13,7 +13,7 @@ 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.example.presenceapp.ui.feature.login.LoginScreenModel import org.koin.dsl.module val networkModule = module { @@ -29,5 +29,5 @@ val networkModule = module { single { ScheduleApiImpl(get()) } single { ScheduleNetRepository (get()) } - factory { LoginViewModel(get(), get()) } + factory { org.example.presenceapp.ui.feature.login.LoginScreenModel(get(), get()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/GroupCommand.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/GroupCommand.kt deleted file mode 100644 index 867fdde..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/GroupCommand.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.example.presenceapp.domain.command - -data class GroupCommand( - val groupId: Int -) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/attendance/GetAttendanceTypesCommand.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/attendance/GetAttendanceTypesCommand.kt new file mode 100644 index 0000000..77771df --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/attendance/GetAttendanceTypesCommand.kt @@ -0,0 +1,6 @@ +package org.example.presenceapp.domain.command.attendance + +data class GetAttendanceTypesCommand( + val typeId: Int, + val typeName: String +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GetStudentsByGroupIdCommand.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GetStudentsByGroupIdCommand.kt new file mode 100644 index 0000000..a13359e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GetStudentsByGroupIdCommand.kt @@ -0,0 +1,5 @@ +package org.example.presenceapp.domain.command.schedule + +data class GetStudentsByGroupIdCommand( + val groupId: Int +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GroupCommand.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GroupCommand.kt new file mode 100644 index 0000000..69d849c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/command/schedule/GroupCommand.kt @@ -0,0 +1,5 @@ +package org.example.presenceapp.domain.command.schedule + +data class GroupCommand( + val groupId: Int +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/SomeStudents.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/SomeStudents.kt index 14abfd9..142a908 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/SomeStudents.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/SomeStudents.kt @@ -2,27 +2,22 @@ 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 = "Федосеева Майя") - ) -} +//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") diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/ToDayMonthString.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/ToDayMonthString.kt index 4a48fde..7f4d50b 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/ToDayMonthString.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/common/ToDayMonthString.kt @@ -3,6 +3,7 @@ package org.example.project.domain.models import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.number +import org.example.presenceapp.domain.entities.Week fun Week.formatWeek(): String { diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Attendance.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Attendance.kt index 4b3a59d..747fd31 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Attendance.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Attendance.kt @@ -15,5 +15,5 @@ data class Attendance( @Serializable data class AttendanceType( val id: Int, - var name: String = "" + var name: String ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/AttendanceView.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/AttendanceView.kt new file mode 100644 index 0000000..96aaf38 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/AttendanceView.kt @@ -0,0 +1,26 @@ +package org.example.presenceapp.domain.entities + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable + +@Serializable +data class AttendanceView( + val studentId: Int, + val date: LocalDate, + val type: AttendanceTypeView, + val isModified: Boolean, + val absenceReason: String? = null +) + +@Serializable +enum class AttendanceTypeView { + PRESENT, + ABSENT +} + +@Serializable +enum class AbsenceReason(val id: Int) { + SICK(1), + COMPETITION(2), + SKIP(3) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DayData.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DayData.kt index 3341234..39811af 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DayData.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DayData.kt @@ -5,5 +5,5 @@ import kotlinx.datetime.LocalDate data class DayData( val date: LocalDate, val isCurrentMonth: Boolean, - val attendance: AttendanceType? + val attendance: AttendanceTypeView? ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DaysOfWeek.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DaysOfWeek.kt index f45fb8d..2548ada 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DaysOfWeek.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/DaysOfWeek.kt @@ -1,4 +1,4 @@ -package org.example.project.domain.models +package org.example.presenceapp.domain.entities import kotlinx.datetime.LocalDate import kotlinx.datetime.Month diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/ResponseState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/ResponseState.kt index 83b6af0..73e58bd 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/ResponseState.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/ResponseState.kt @@ -8,10 +8,4 @@ sealed class ResponseState { sealed class Either { class Left(val value: A): Either() class Right(val value: B): Either() -} - -enum class ServiceError { - NOT_FOUND, - UNAUTHORIZED, - NOT_CREATED } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Schedule.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Schedule.kt index 438e121..729aa6b 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Schedule.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/entities/Schedule.kt @@ -1,5 +1,7 @@ package org.example.presenceapp.domain.entities +import kotlinx.datetime.LocalDate + data class Schedule( val id: Int, val lessonNumber: Int, @@ -7,9 +9,23 @@ data class Schedule( val subject: Subject, val dayOfWeek: Int, ) + data class Subject( val id: Int, val name: String ) +data class Student( + val id: Int, + val uuid: String, + val fio: String, + val role: String, + val enrollDate: LocalDate, + var expulsionDate: LocalDate? = null +) +data class Group( + val groupId: Int, + var name: String, + var students: List = emptyList() +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/AttendanceRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/AttendanceRepository.kt new file mode 100644 index 0000000..ab9e938 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/AttendanceRepository.kt @@ -0,0 +1,24 @@ +package org.example.presenceapp.domain.repo + +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.GetAttendanceTypesCommand +import org.example.presenceapp.domain.command.attendance.GetPresettingCommand +import org.example.presenceapp.domain.entities.Attendance +import org.example.presenceapp.domain.entities.AttendanceType +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Presetting + +interface AttendanceRepository { + suspend fun saveAttendanceLocally(attendance: Map) + fun observeLocalAttendance(): Flow> + + suspend fun addAttendance(addAttendanceCommand: AddAttendanceCommand): List + suspend fun getAttendance(getAttendanceCommand: GetAttendanceCommand): List + suspend fun getAttendanceTypes(getAttendanceTypesCommand: GetAttendanceTypesCommand): List + + suspend fun addPresetting(addPresettingCommand: AddPresettingCommand): Boolean + suspend fun getPresetting(getPresettingCommand: GetPresettingCommand): List +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/ScheduleRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/ScheduleRepository.kt index 310104b..11f9c99 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/ScheduleRepository.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/ScheduleRepository.kt @@ -1,8 +1,13 @@ package org.example.presenceapp.domain.repo -import org.example.presenceapp.domain.command.GroupCommand +import org.example.presenceapp.data.common.dto.group.StudentResponseDto +import org.example.presenceapp.domain.command.schedule.GetStudentsByGroupIdCommand +import org.example.presenceapp.domain.command.schedule.GroupCommand +import org.example.presenceapp.domain.entities.Group import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.Student interface ScheduleRepository { suspend fun getSchedule(groupCommand: GroupCommand): List + suspend fun getStudentsByGroupId(getStudentsByGroupIdCommand: GetStudentsByGroupIdCommand): List } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceRepository.kt deleted file mode 100644 index b1998c2..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -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 - suspend fun getAttendance(getAttendanceCommand: GetAttendanceCommand): List -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceTypeRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceTypeRepository.kt deleted file mode 100644 index 6e20f04..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/AttendanceTypeRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.example.presenceapp.domain.repo.attendance - -import org.example.presenceapp.domain.entities.AttendanceType - -interface AttendanceTypeRepository { - suspend fun getAttendanceTypes(): List -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/PresettingRepository.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/PresettingRepository.kt deleted file mode 100644 index 21cd972..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/repo/attendance/PresettingRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -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 -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/AttendanceUseCase.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/AttendanceUseCase.kt index 4901bf8..bb312ca 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/AttendanceUseCase.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/AttendanceUseCase.kt @@ -2,50 +2,140 @@ package org.example.presenceapp.domain.usecases import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import org.example.presenceapp.data.common.toDto +import org.example.presenceapp.data.common.toEntity 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.GetAttendanceTypesCommand import org.example.presenceapp.domain.command.attendance.GetPresettingCommand import org.example.presenceapp.domain.entities.Attendance +import org.example.presenceapp.domain.entities.AttendanceType +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.domain.entities.AttendanceView import org.example.presenceapp.domain.entities.Either import org.example.presenceapp.domain.entities.Presetting -import org.example.presenceapp.domain.repo.attendance.AttendanceRepository +import org.example.presenceapp.domain.repo.AttendanceRepository class AttendanceUseCase( private val attendanceRepository: AttendanceRepository ) { - fun addAttendance(addAttendanceCommand: AddAttendanceCommand): Flow> = flow { - return@flow try { + private fun Attendance.toView( + attendanceTypeMapper: (Int) -> AttendanceTypeView, + absenceReasonProvider: (Int, Int) -> String? + ): AttendanceView = this.toEntity(attendanceTypeMapper, absenceReasonProvider) + + private fun AttendanceView.toApi( + scheduleId: Int, + absenceReason: String? + ): Attendance = this.toDto(scheduleId, absenceReason) + + + + suspend fun saveAttendanceLocally(attendance: Map) { + attendanceRepository.saveAttendanceLocally(attendance) + } + + fun observeLocalAttendance(): Flow> { + return attendanceRepository.observeLocalAttendance() + } + + fun addAttendance(addAttendanceCommand: AddAttendanceCommand): Flow>> = flow { + try { val result = attendanceRepository.addAttendance(addAttendanceCommand) emit(Either.Right(result)) - } catch (e:Exception) { + } catch (e: Exception) { + emit(Either.Left(e)) + } + } + + fun addAttendanceView( + attendanceViews: List, + scheduleId: Int, + absenceReasonProvider: (AttendanceView) -> String? + ): Flow>> = flow { + try { + val results = mutableListOf() + attendanceViews.forEach { view -> + val absenceReason = absenceReasonProvider(view) + val dto = view.toApi(scheduleId, absenceReason) + val command = AddAttendanceCommand( + attendanceDate = dto.date, + studentId = dto.studentId, + attendanceTypeId = dto.attendanceTypeId, + scheduleId = dto.scheduleId + ) + val result = attendanceRepository.addAttendance(command) + results.addAll(result) + } + emit(Either.Right(results)) + } catch (e: Exception) { emit(Either.Left(e)) } } fun getAttendance(getAttendanceCommand: GetAttendanceCommand): Flow>> = flow { - return@flow try { + try { val result = attendanceRepository.getAttendance(getAttendanceCommand) emit(Either.Right(result)) - } catch (e:Exception) { + } catch (e: Exception) { emit(Either.Left(e)) } } - fun addPresetting(addPresettingCommand: AddPresettingCommand): Flow> = flow { - return@flow try { + fun getAttendanceView( + getAttendanceCommand: GetAttendanceCommand, + attendanceTypeMapper: (Int) -> AttendanceTypeView, + absenceReasonProvider: (Int, Int) -> String? + ): Flow>> = flow { + try { + val result = attendanceRepository.getAttendance(getAttendanceCommand) + .map { it.toView(attendanceTypeMapper, absenceReasonProvider) } + emit(Either.Right(result)) + } catch (e: Exception) { + emit(Either.Left(e)) + } + } + + fun getAttendanceTypes(getAttendanceTypesCommand: GetAttendanceTypesCommand): Flow>> = flow { + try { + val result = attendanceRepository.getAttendanceTypes(getAttendanceTypesCommand) + emit(Either.Right(result)) + } catch (e: Exception) { + emit(Either.Left(e)) + } + } + + fun getAttendanceTypesView( + getAttendanceTypesCommand: GetAttendanceTypesCommand, + attendanceTypeMapper: (Int) -> AttendanceTypeView + ): Flow>> = flow { + try { + val result = attendanceRepository.getAttendanceTypes(getAttendanceTypesCommand) + .map { attendanceType -> + val (attendanceTypeView, _) = attendanceType.toEntity(attendanceType.id) + attendanceTypeView + } + emit(Either.Right(result)) + } catch (e: Exception) { + emit(Either.Left(e)) + } + } + + fun addPresetting(addPresettingCommand: AddPresettingCommand): Flow> = flow { + try { val result = attendanceRepository.addPresetting(addPresettingCommand) emit(Either.Right(result)) - } catch (e:Exception) { + } catch (e: Exception) { emit(Either.Left(e)) } } fun getPresetting(getPresettingCommand: GetPresettingCommand): Flow>> = flow { - return@flow try { + try { val result = attendanceRepository.getPresetting(getPresettingCommand) emit(Either.Right(result)) - } catch (e:Exception) { + } catch (e: Exception) { emit(Either.Left(e)) } } diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/ScheduleUseCase.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/ScheduleUseCase.kt index 0e86022..d5fdec1 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/ScheduleUseCase.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/domain/usecases/ScheduleUseCase.kt @@ -2,9 +2,12 @@ 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.data.common.dto.group.StudentResponseDto +import org.example.presenceapp.domain.command.schedule.GetStudentsByGroupIdCommand +import org.example.presenceapp.domain.command.schedule.GroupCommand import org.example.presenceapp.domain.entities.Either import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.Student import org.example.presenceapp.domain.repo.ScheduleRepository class ScheduleUseCase( @@ -18,4 +21,13 @@ class ScheduleUseCase( emit(Either.Left(e)) } } + + fun getStudentsByGroupId(getStudentsByGroupIdCommand: GetStudentsByGroupIdCommand): Flow>> = flow { + return@flow try { + val result = scheduleRepository.getStudentsByGroupId(getStudentsByGroupIdCommand) + emit(Either.Right(result)) + } catch (e: Exception) { + emit(Either.Left(e)) + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/AttendanceScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/AttendanceScreen.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/AttendanceScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/AttendanceScreenModel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/components/AttendanceColumn.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/components/AttendanceColumn.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/components/AttendanceToString.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/attendance/components/AttendanceToString.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/base/BaseViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/base/BaseViewModel.kt new file mode 100644 index 0000000..44c42ba --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/base/BaseViewModel.kt @@ -0,0 +1,61 @@ +package org.example.presenceapp.ui.base + +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +interface ViewEvent + +interface ViewState + +interface ViewSideEffect + +const val SIDE_EFFECTS_KEY = "side-effects_key" + +abstract class BaseViewModel : ScreenModel { + + abstract fun setInitialState(): UiState + abstract fun handleEvents(event: Event) + + private val initialState: UiState by lazy { setInitialState() } + + private val _viewState = MutableStateFlow(initialState) + val viewState: StateFlow = _viewState + + private val _event = Channel(Channel.UNLIMITED) + private val _effect = Channel(Channel.UNLIMITED) + val effect = _effect.receiveAsFlow() + + init { + subscribeToEvents() + } + + private fun subscribeToEvents() { + screenModelScope.launch { + _event.consumeAsFlow().collect { event -> + handleEvents(event) + } + } + } + + fun setEvent(event: Event) { + screenModelScope.launch { + _event.send(event) + } + } + + protected fun setState(reducer: UiState.() -> UiState) { + _viewState.value = _viewState.value.reducer() + } + + protected fun setEffect(builder: () -> Effect) { + screenModelScope.launch { + _effect.send(builder()) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/CalendarScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/CalendarScreen.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/CalendarScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/CalendarScreenModel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarDayCell.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarDayCell.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarGrid.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarGrid.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarMonth.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarMonth.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarUtils.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarUtils.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarWeekDays.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/calendar/components/CalendarWeekDays.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonBottomBar.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonBottomBar.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonButton.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonButton.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonDataText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonDataText.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonDialog.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonDialog.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonIconButton.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonIconButton.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonLabel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonLabel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonMainText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonMainText.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonMediumText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonMediumText.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonRegularText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonRegularText.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonTopBar.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/CommonTopBar.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/ErrorDialog.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/commons/ErrorDialog.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceContract.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceContract.kt new file mode 100644 index 0000000..df0c951 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceContract.kt @@ -0,0 +1,48 @@ +package org.example.presenceapp.ui.feature.attendance + +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Student +import org.example.presenceapp.ui.base.ViewEvent +import org.example.presenceapp.ui.base.ViewSideEffect +import org.example.presenceapp.ui.base.ViewState + +object AttendanceContract { + sealed class Event : ViewEvent { + data object LoadStudents : Event() + data object LoadAttendance : Event() + data class UpdateDefaultStatus(val type: AttendanceTypeView) : Event() + data class UpdateAttendanceForSelected(val studentIds: Set, val type: AttendanceTypeView) : Event() + data class ChangeSortType(val sortType: AttendanceTypeView) : Event() + data class UpdateAbsenceReason(val studentId: Int, val reason: String) : Event() + data class SaveAttendanceToApi(val scheduleId: Int) : Event() + data class LoadAttendanceFromApi(val groupId: Int, val date: LocalDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date) : Event() + data object CheckDialogState : Event() + data object DismissDialog : Event() + } + + data class State( + val students: List = emptyList(), + val attendanceMap: Map = emptyMap(), + val sortType: AttendanceTypeView = AttendanceTypeView.PRESENT, + val showDialog: Boolean = false + ) : ViewState + + sealed class Effect : ViewSideEffect { + data class ShowError(val message: String?) : Effect() + } +} + +fun AttendanceContract.State.groupedStudents(): List>> { + val present = students.filter { attendanceMap[it.id]?.type == AttendanceTypeView.PRESENT }.sortedBy { it.fio } + val absent = students.filter { attendanceMap[it.id]?.type == AttendanceTypeView.ABSENT }.sortedBy { it.fio } + + return when (sortType) { + AttendanceTypeView.PRESENT -> listOf("Присутствующие" to present, "Отсутствующие" to absent) + AttendanceTypeView.ABSENT -> listOf("Отсутствующие" to absent, "Присутствующие" to present) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreen.kt new file mode 100644 index 0000000..eb07b83 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreen.kt @@ -0,0 +1,147 @@ +package org.example.presenceapp.ui.feature.attendance + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.ui.Modifier +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.example.presenceapp.data.local.LocalDataSource +import org.example.presenceapp.data.local.storage.SettingsStorage +import org.example.presenceapp.data.local.storage.attendance.AttendanceStorageProvider +import org.example.presenceapp.data.remote.impl.AttendanceApiImpl +import org.example.presenceapp.data.remote.impl.ScheduleApiImpl +import org.example.presenceapp.data.remote.network.KtorfitClient +import org.example.presenceapp.data.repository.AttendanceNetRepository +import org.example.presenceapp.data.repository.ScheduleNetRepository +import org.example.presenceapp.data.repository.settings.SettingsRepository +import org.example.presenceapp.data.repository.settings.SettingsRepositoryImpl +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.repo.AttendanceRepository +import org.example.presenceapp.domain.repo.ScheduleRepository +import org.example.presenceapp.domain.usecases.AttendanceUseCase +import org.example.presenceapp.domain.usecases.ScheduleUseCase +import org.example.presenceapp.getPlatformContext +import org.example.presenceapp.ui.base.SIDE_EFFECTS_KEY +import org.example.presenceapp.ui.feature.attendance.composables.AttendanceColumn +import org.example.presenceapp.ui.feature.commons.CommonTopBar +import org.example.presenceapp.ui.feature.settings.Preset +import org.example.presenceapp.ui.feature.settings.SettingsScreenModel +import org.example.presenceapp.ui.feature.settings.getSettingsManager +import org.example.presenceapp.ui.theme.AppTheme +import org.example.presenceapp.ui.types.ScreenType + +class AttendanceScreen(private val selectedLesson: Schedule) : Screen { + @Composable + override fun Content() { + val platformContext = getPlatformContext() + val attendanceStorage = AttendanceStorageProvider(platformContext).provide() + val localDataSource = LocalDataSource(attendanceStorage) + val attendanceApi = KtorfitClient.createAttendanceApi() + val attendanceApiImpl = AttendanceApiImpl(attendanceApi) + val scheduleApi = KtorfitClient.createScheduleApi() + val scheduleApiImpl = ScheduleApiImpl(scheduleApi) + val attendanceRepository: AttendanceRepository = AttendanceNetRepository( + localDataSource = localDataSource, + attendanceApiImpl = attendanceApiImpl + ) + val scheduleRepository: ScheduleRepository = ScheduleNetRepository( + scheduleApiImpl = scheduleApiImpl + ) + val attendanceUseCase = AttendanceUseCase(attendanceRepository) + val scheduleUseCase = ScheduleUseCase(scheduleRepository) + val settingsStorage = SettingsStorage(platformContext) + val settingsRepository: SettingsRepository = SettingsRepositoryImpl(settingsStorage) + val settingsScreenModel = rememberScreenModel { SettingsScreenModel(settingsRepository = settingsRepository) } + val settingsManager = getSettingsManager() + + val screenModel = rememberScreenModel { + AttendanceScreenModel( + attendanceUseCase = attendanceUseCase, + scheduleUseCase = scheduleUseCase, + settingsScreenModel = settingsScreenModel, + settingsManager = settingsManager + ) + } + + LaunchedEffect(SIDE_EFFECTS_KEY) { + screenModel.setEvent(AttendanceContract.Event.LoadStudents) + screenModel.setEvent(AttendanceContract.Event.LoadAttendance) + screenModel.setEvent( + AttendanceContract.Event.LoadAttendanceFromApi( + groupId = selectedLesson.id, + date = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date + ) + ) + } + + Attendance( + screenModel = screenModel, + selectedLesson = selectedLesson + ) + } +} + +@Composable +fun Attendance( + screenModel: AttendanceScreenModel, + selectedLesson: Schedule +) { + val state by screenModel.viewState.collectAsState() + val showDialog = state.showDialog + + LaunchedEffect(SIDE_EFFECTS_KEY) { + screenModel.setEvent(AttendanceContract.Event.CheckDialogState) + } + + if (showDialog) { + key(showDialog) { + Preset( + onDismiss = { screenModel.setEvent(AttendanceContract.Event.DismissDialog) }, + onStatusSelected = { selectedStatus -> + screenModel.setEvent( + AttendanceContract.Event.UpdateDefaultStatus(selectedStatus) + ) + } + ) + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(AppTheme.colors.white) + ) { + Scaffold( + topBar = { + CommonTopBar( + screenType = ScreenType.GROUP, + text = selectedLesson.subject.name, + onChangeSortType = { newSortType -> + screenModel.setEvent( + AttendanceContract.Event.ChangeSortType(newSortType) + ) + } + ) + } + ) { padding -> + AttendanceColumn( + modifier = Modifier + .padding(padding) + .fillMaxSize(), + state = state, + onEvent = { event -> screenModel.setEvent(event) } + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreenModel.kt new file mode 100644 index 0000000..4e1036a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/AttendanceScreenModel.kt @@ -0,0 +1,425 @@ +package org.example.presenceapp.ui.feature.attendance + +import cafe.adriel.voyager.core.model.screenModelScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.example.presenceapp.domain.command.attendance.GetAttendanceCommand +import org.example.presenceapp.domain.command.schedule.GetStudentsByGroupIdCommand +import org.example.presenceapp.domain.entities.AbsenceReason +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Either +import org.example.presenceapp.domain.usecases.AttendanceUseCase +import org.example.presenceapp.domain.usecases.ScheduleUseCase +import org.example.presenceapp.ui.base.BaseViewModel +import org.example.presenceapp.ui.feature.attendance.composables.toReadableString +import org.example.presenceapp.ui.feature.settings.SettingsManager +import org.example.presenceapp.ui.feature.settings.SettingsScreenModel + +class AttendanceScreenModel( + private val attendanceUseCase: AttendanceUseCase, + private val scheduleUseCase: ScheduleUseCase, + private val settingsScreenModel: SettingsScreenModel, + private val settingsManager: SettingsManager +) : BaseViewModel() { + + private val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date + + override fun setInitialState() = AttendanceContract.State() + + override fun handleEvents(event: AttendanceContract.Event) { + when (event) { + AttendanceContract.Event.LoadStudents -> loadStudents() + AttendanceContract.Event.LoadAttendance -> loadAttendance() + is AttendanceContract.Event.UpdateDefaultStatus -> updateDefaultStatus(event.type) + is AttendanceContract.Event.UpdateAttendanceForSelected -> updateAttendanceForSelected(event.studentIds, event.type) + is AttendanceContract.Event.ChangeSortType -> setState { copy(sortType = event.sortType) } + is AttendanceContract.Event.UpdateAbsenceReason -> updateAbsenceReason(event.studentId, event.reason) + is AttendanceContract.Event.SaveAttendanceToApi -> saveAttendanceToApi(event.scheduleId) + is AttendanceContract.Event.LoadAttendanceFromApi -> loadAttendanceFromApi(event.groupId, event.date) + AttendanceContract.Event.CheckDialogState -> checkDialogState() + AttendanceContract.Event.DismissDialog -> dismissDialog() + } + } + + companion object { + const val PRESENT = "присут" + const val ABSENT = "отсут" + + fun AttendanceView.toDisplayStatus(): String { + return when (type) { + AttendanceTypeView.PRESENT -> PRESENT + AttendanceTypeView.ABSENT -> absenceReason?.toReadableString() ?: ABSENT + } + } + } + + private fun checkDialogState() { + if (!settingsManager.isDialogShown()) { + setState { copy(showDialog = true) } + } + } + + private fun dismissDialog() { + setState { copy(showDialog = false) } + settingsManager.setDialogShown() + } + + private fun loadStudents() { + screenModelScope.launch { + scheduleUseCase.getStudentsByGroupId(GetStudentsByGroupIdCommand(groupId = 1)) + .collect { result -> + when (result) { + is Either.Right -> setState { copy(students = result.value) } + is Either.Left -> setEffect { AttendanceContract.Effect.ShowError(result.value.message) } + } + } + } + } + + private fun loadAttendance() { + screenModelScope.launch { + val defaultStatus = settingsScreenModel.defaultStatus.first() + val students = viewState.value.students + + if (students.isEmpty()) return@launch + + attendanceUseCase.observeLocalAttendance() + .combine(settingsScreenModel.defaultStatus) { savedAttendance, defaultStatusValue -> + students.associate { student -> + val saved = savedAttendance[student.id] + if (saved != null) { + student.id to saved + } else { + student.id to AttendanceView( + studentId = student.id, + date = today, + type = defaultStatusValue, + isModified = false, + absenceReason = if (defaultStatusValue == AttendanceTypeView.ABSENT) AbsenceReason.SKIP.name else null + ) + } + } + }.collect { map -> + setState { copy(attendanceMap = map) } + } + } + } + + private fun updateDefaultStatus(type: AttendanceTypeView) { + settingsScreenModel.updateDefaultStatus(type) + + val updatedMap = viewState.value.attendanceMap.toMutableMap().apply { + entries.forEach { (studentId, attendance) -> + if (!attendance.isModified) { + this[studentId] = attendance.copy(type = type) + } + } + } + + setState { copy(attendanceMap = updatedMap) } + + screenModelScope.launch { + attendanceUseCase.saveAttendanceLocally(updatedMap) + } + } + + private fun updateAttendanceForSelected(studentIds: Set, type: AttendanceTypeView) { + val updatedMap = viewState.value.attendanceMap.toMutableMap().apply { + studentIds.forEach { studentId -> + this[studentId] = AttendanceView( + studentId = studentId, + date = today, + type = type, + isModified = true, + absenceReason = if (type == AttendanceTypeView.ABSENT) AbsenceReason.SKIP.name else null + ) + } + } + + setState { copy(attendanceMap = updatedMap) } + + screenModelScope.launch { + attendanceUseCase.saveAttendanceLocally(updatedMap) + } + } + + private fun updateAbsenceReason(studentId: Int, reason: String) { + val updatedMap = viewState.value.attendanceMap.toMutableMap().apply { + val attendance = this[studentId] + if (attendance != null && attendance.type == AttendanceTypeView.ABSENT) { + this[studentId] = attendance.copy(absenceReason = reason, isModified = true) + } + } + + setState { copy(attendanceMap = updatedMap) } + + screenModelScope.launch { + attendanceUseCase.saveAttendanceLocally(updatedMap) + } + } + + private fun saveAttendanceToApi(scheduleId: Int) { + screenModelScope.launch { + attendanceUseCase.addAttendanceView( + attendanceViews = viewState.value.attendanceMap.values.toList(), + scheduleId = scheduleId, + absenceReasonProvider = { it.absenceReason } + ).collect { result -> + if (result is Either.Left) { + setEffect { AttendanceContract.Effect.ShowError(result.value.message) } + } + } + } + } + + private fun loadAttendanceFromApi(groupId: Int, date: LocalDate) { + screenModelScope.launch { + attendanceUseCase.getAttendanceView( + getAttendanceCommand = GetAttendanceCommand(groupId, date), + attendanceTypeMapper = { id -> if (id == 4) AttendanceTypeView.PRESENT else AttendanceTypeView.ABSENT }, + absenceReasonProvider = { _, attendanceTypeId -> AbsenceReason.entries.firstOrNull { it.id == attendanceTypeId }?.name } + ).collect { result -> + when (result) { + is Either.Right -> setState { copy(attendanceMap = result.value.associateBy { it.studentId }) } + is Either.Left -> setEffect { AttendanceContract.Effect.ShowError(result.value.message) } + } + } + } + } +} + +//class AttendanceScreenModel( +// private val attendanceUseCase: AttendanceUseCase, +// private val scheduleUseCase: ScheduleUseCase, +// private val settingsScreenModel: SettingsScreenModel, +// private val settingsManager: SettingsManager +//) : ScreenModel { +// private val _students = MutableStateFlow>(emptyList()) +// private val _attendanceMap = MutableStateFlow>(emptyMap()) +// private val _sortType = MutableStateFlow(AttendanceTypeView.PRESENT) +// private val _defaultStatus: StateFlow = settingsScreenModel.defaultStatus +// +// val attendanceMap: StateFlow> = _attendanceMap.asStateFlow() +// +// val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date +// +// private val _showDialog = mutableStateOf(false) +// val showDialog: State = _showDialog +// +// fun checkDialogState() { +// if (!settingsManager.isDialogShown()) { +// _showDialog.value = true +// println("Dialog should be shown") +// } else { +// println("Dialog already shown, not showing again") +// } +// } +// +// fun onDialogDismissed() { +// _showDialog.value = false +// settingsManager.setDialogShown() +// } +// +// companion object { +// const val PRESENT = "присут" +// const val ABSENT = "отсут" +// +// fun AttendanceView.toDisplayStatus(): String { +// return when (type) { +// AttendanceTypeView.PRESENT -> PRESENT +// AttendanceTypeView.ABSENT -> absenceReason?.toReadableString() ?: ABSENT +// } +// } +// } +// +// init { +// loadStudents() +// loadAttendance() +// } +// +// private fun loadStudents() { +// screenModelScope.launch { +// scheduleUseCase.getStudentsByGroupId( +// GetStudentsByGroupIdCommand(groupId = 1) +// ).collect { result -> +// when (result) { +// is Either.Right -> { +// _students.value = result.value +// } +// +// is Either.Left -> { +// println("Error loading students: ${result.value.message}") +// _students.value = emptyList() +// } +// } +// } +// } +// } +// +// @OptIn(ExperimentalCoroutinesApi::class) +// private fun loadAttendance() { +// screenModelScope.launch { +// _students +// .filter { it.isNotEmpty() } +// .distinctUntilChanged() +// .flatMapLatest { students -> +// attendanceUseCase.observeLocalAttendance() +// .combine(_defaultStatus) { savedAttendance, defaultStatusValue -> +// students.associate { student -> +// val saved = savedAttendance[student.id] +// if (saved != null) { +// student.id to saved +// } else { +// student.id to AttendanceView( +// studentId = student.id, +// date = today, +// type = defaultStatusValue, +// isModified = false, +// absenceReason = if (defaultStatusValue == AttendanceTypeView.ABSENT) AbsenceReason.SKIP.name else null +// ) +// } +// } +// } +// } +// .collect { +// _attendanceMap.value = it +// } +// } +// } +// +// val groupedStudents = combine( +// _students, +// _attendanceMap, +// _sortType +// ) { students, attendance, sortType -> +// val present = students.filter { attendance[it.id]?.type == AttendanceTypeView.PRESENT } +// .sortedBy { it.fio } +// val absent = students.filter { attendance[it.id]?.type == AttendanceTypeView.ABSENT } +// .sortedBy { it.fio } +// +// when (sortType) { +// AttendanceTypeView.PRESENT -> listOf( +// "Присутствующие" to present, +// "Отсутствующие" to absent +// ) +// +// AttendanceTypeView.ABSENT -> listOf( +// "Отсутствующие" to absent, +// "Присутствующие" to present +// ) +// } +// } +// +// fun updateDefaultStatus(type: AttendanceTypeView) { +// settingsScreenModel.updateDefaultStatus(type) +// +// val updatedMap = _attendanceMap.value.toMutableMap().apply { +// entries.forEach { (studentId, attendance) -> +// if (!attendance.isModified) { +// this[studentId] = attendance.copy(type = type) +// } +// } +// } +// _attendanceMap.value = updatedMap +// screenModelScope.launch { +// saveAttendanceToStorage(updatedMap) +// } +// } +// +// fun updateAttendanceForSelected(studentIds: Set, type: AttendanceTypeView) { +// val updatedMap = _attendanceMap.value.toMutableMap().apply { +// studentIds.forEach { studentId -> +// this[studentId] = AttendanceView( +// studentId = studentId, +// date = today, +// type = type, +// isModified = true, +// absenceReason = if (type == AttendanceTypeView.ABSENT) AbsenceReason.SKIP.name else null +// ) +// } +// } +// _attendanceMap.value = updatedMap +// screenModelScope.launch { +// attendanceUseCase.saveAttendanceLocally(updatedMap) +// } +// } +// +// fun changeSortType(newSortType: AttendanceTypeView) { +// _sortType.value = newSortType +// } +// +// private suspend fun saveAttendanceToStorage(map: Map) { +// attendanceUseCase.saveAttendanceLocally(map) +// } +// +// fun updateAbsenceReason(studentId: Int, reason: String) { +// val updatedMap = _attendanceMap.value.toMutableMap().apply { +// val attendance = this[studentId] +// if (attendance != null && attendance.type == AttendanceTypeView.ABSENT) { +// this[studentId] = attendance.copy(absenceReason = reason, isModified = true) +// } +// } +// screenModelScope.launch { +// saveAttendanceToStorage(updatedMap) +// _attendanceMap.emit(updatedMap) +// } +// } +// +// +// fun saveAttendanceToApi(scheduleId: Int) { +// screenModelScope.launch { +// attendanceUseCase.addAttendanceView( +// attendanceViews = _attendanceMap.value.values.toList(), +// scheduleId = scheduleId, +// absenceReasonProvider = { it.absenceReason } +// ).collect { result -> +// when (result) { +// is Either.Right -> { +// println("Attendance saved to API: ${result.value}") +// } +// +// is Either.Left -> { +// println("Error saving attendance to API: ${result.value.message}") +// } +// } +// } +// } +// } +// +// fun loadAttendanceFromApi(groupId: Int, date: LocalDate = today) { +// screenModelScope.launch { +// attendanceUseCase.getAttendanceView( +// getAttendanceCommand = GetAttendanceCommand( +// groupId = groupId, +// beforeAt = date +// ), +// attendanceTypeMapper = { id -> +// when (id) { +// 4 -> AttendanceTypeView.PRESENT +// else -> AttendanceTypeView.ABSENT +// } +// }, +// absenceReasonProvider = { _: Int, attendanceTypeId: Int -> +// AbsenceReason.entries.firstOrNull { it.id == attendanceTypeId }?.name +// } +// ).collect { result -> +// when (result) { +// is Either.Right -> { +// _attendanceMap.value = result.value.associateBy { it.studentId } +// println("Attendance loaded from API") +// } +// +// is Either.Left -> { +// println("Error loading attendance from API: ${result.value.message}") +// } +// } +// } +// } +// } +//} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceColumn.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceColumn.kt new file mode 100644 index 0000000..06bba30 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceColumn.kt @@ -0,0 +1,90 @@ +package org.example.presenceapp.ui.feature.attendance.composables + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.ui.feature.attendance.AttendanceContract +import org.example.presenceapp.ui.feature.attendance.groupedStudents +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun AttendanceColumn( + modifier: Modifier, + state: AttendanceContract.State, + onEvent: (AttendanceContract.Event) -> Unit +) { + val groups = state.groupedStudents() + val attendanceMap = state.attendanceMap + + var expanded by remember { mutableStateOf(null) } + val selectedStudents = remember { mutableStateOf>(emptySet()) } + var isSelectionMode by remember { mutableStateOf(false) } + + val listState = rememberLazyListState() + + LaunchedEffect(groups) { + listState.scrollToItem(0) + } + + LaunchedEffect(selectedStudents.value) { + if (selectedStudents.value.isEmpty()) { + isSelectionMode = false + } + } + + Box( + modifier = modifier + .fillMaxSize() + .background(color = AppTheme.colors.white) + ) { + AttendanceList( + groups = groups, + attendanceMap = attendanceMap, + listState = listState, + selectedStudents = selectedStudents, + isSelectionMode = isSelectionMode, + onStudentClicked = { studentId -> + if (!isSelectionMode && attendanceMap[studentId]?.type == AttendanceTypeView.ABSENT) { + expanded = studentId + } + }, + onSelectionChanged = { isSelectionMode = it }, + modifier = Modifier + .fillMaxSize() + .padding(bottom = if (isSelectionMode) 80.dp else 0.dp) + ) + + if (isSelectionMode) { + AttendanceSelection( + selectedStudents = selectedStudents.value, + onEvent = onEvent, + onSelectionCleared = { + isSelectionMode = false + selectedStudents.value = emptySet() + }, + modifier = Modifier.align(Alignment.BottomCenter) + ) + } + + expanded?.let { studentId -> + AttendanceDialog( + studentId = studentId, + groups = groups, + onEvent = onEvent, + onDismiss = { expanded = null } + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceDialog.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceDialog.kt new file mode 100644 index 0000000..d48af6f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceDialog.kt @@ -0,0 +1,77 @@ +package org.example.presenceapp.ui.feature.attendance.composables + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.entities.AbsenceReason +import org.example.presenceapp.domain.entities.Student +import org.example.presenceapp.ui.feature.attendance.AttendanceContract +import org.example.presenceapp.ui.feature.commons.CommonButton +import org.example.presenceapp.ui.feature.commons.CommonDialog +import org.example.presenceapp.ui.feature.commons.CommonMediumText +import org.example.presenceapp.ui.feature.commons.CommonRegularText +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun AttendanceDialog( + studentId: Int, + groups: List>>, + onEvent: (AttendanceContract.Event) -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier +) { + val student = groups + .flatMap { it.second } + .find { it.id == studentId } + + if (student != null) { + CommonDialog( + expanded = true, + onDismiss = onDismiss, + content = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(16.dp) + ) { + CommonMediumText( + text = "Выберите причину отсутствия", + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(20.dp)) + + AbsenceReason.entries.forEach { reason -> + CommonButton( + onClick = { + onEvent(AttendanceContract.Event.UpdateAbsenceReason(studentId, reason.name)) + onDismiss() + }, + content = { + CommonRegularText( + text = reason.toCustomString(), + color = AppTheme.colors.white, + modifier = Modifier + ) + }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + ) + } + } + } + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceHeader.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceHeader.kt new file mode 100644 index 0000000..0788f8b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceHeader.kt @@ -0,0 +1,24 @@ +package org.example.presenceapp.ui.feature.attendance.composables + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.feature.commons.CommonLabel + +@Composable +fun AttendanceHeader( + title: String, + modifier: Modifier = Modifier +) { + CommonLabel( + text = title, + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .wrapContentWidth(Alignment.CenterHorizontally) + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceItem.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceItem.kt new file mode 100644 index 0000000..4dab499 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceItem.kt @@ -0,0 +1,76 @@ +package org.example.presenceapp.ui.feature.attendance.composables + +import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Student +import org.example.presenceapp.ui.feature.attendance.AttendanceScreenModel.Companion.toDisplayStatus +import org.example.presenceapp.ui.feature.commons.CommonRegularText +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun AttendanceItem( + student: Student, + attendance: AttendanceView?, + isSelected: Boolean, + isSelectionMode: Boolean, + onLongPress: () -> Unit, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .fillMaxWidth() + .height(72.dp) + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { onLongPress() }, + onTap = { onClick() } + ) + } + .border( + width = 1.dp, + color = if (isSelected) AppTheme.colors.black else AppTheme.colors.gray, + shape = RoundedCornerShape(16.dp) + ) + .padding(12.dp) + ) { + CommonRegularText( + text = student.fio, + color = AppTheme.colors.black, + modifier = Modifier + .weight(1f) + .padding(8.dp) + ) + Box( + modifier = Modifier + .widthIn(min = 80.dp) + .wrapContentWidth(Alignment.CenterHorizontally) + .padding(8.dp) + ) { + attendance?.type?.let { + CommonRegularText( + text = attendance.toDisplayStatus(), + color = AppTheme.colors.black, + textAlign = TextAlign.Center, + modifier = Modifier + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceList.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceList.kt new file mode 100644 index 0000000..89fb465 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceList.kt @@ -0,0 +1,63 @@ +package org.example.presenceapp.ui.feature.attendance.composables + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Student + +@Composable +fun AttendanceList( + groups: List>>, + attendanceMap: Map, + listState: LazyListState, + selectedStudents: MutableState>, + isSelectionMode: Boolean, + onStudentClicked: (Int) -> Unit, + onSelectionChanged: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + LazyColumn( + state = listState, + verticalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 16.dp), + modifier = modifier + ) { + groups.forEach { (title, students) -> + if (students.isNotEmpty()) { + item { + AttendanceHeader(title = title) + } + items(students, key = { student -> student.id }) { student -> + AttendanceItem( + student = student, + attendance = attendanceMap[student.id], + isSelected = selectedStudents.value.contains(student.id), + isSelectionMode = isSelectionMode, + onLongPress = { + onSelectionChanged(true) + selectedStudents.value = selectedStudents.value.toMutableSet().apply { + if (contains(student.id)) remove(student.id) else add(student.id) + } + }, + onClick = { + if (isSelectionMode) { + selectedStudents.value = selectedStudents.value.toMutableSet().apply { + if (contains(student.id)) remove(student.id) else add(student.id) + } + } else { + onStudentClicked(student.id) + } + } + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceSelection.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceSelection.kt new file mode 100644 index 0000000..b9604a1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceSelection.kt @@ -0,0 +1,52 @@ +package org.example.presenceapp.ui.feature.attendance.composables + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.ui.feature.attendance.AttendanceContract +import org.example.presenceapp.ui.feature.commons.CommonButton +import org.example.presenceapp.ui.feature.commons.CommonRegularText +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun AttendanceSelection( + selectedStudents: Set, + onEvent: (AttendanceContract.Event) -> Unit, + onSelectionCleared: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = modifier + .fillMaxWidth() + .background(color = AppTheme.colors.white) + .padding(vertical = 8.dp, horizontal = 16.dp) + .padding(bottom = 16.dp) + ) { + listOf( + AttendanceTypeView.PRESENT to "присут", + AttendanceTypeView.ABSENT to "отсут" + ).forEach { (status, label) -> + CommonButton( + onClick = { + onEvent(AttendanceContract.Event.UpdateAttendanceForSelected(selectedStudents, status)) + onSelectionCleared() + }, + content = { + CommonRegularText( + text = label, + color = AppTheme.colors.white, + modifier = Modifier + ) + }, + modifier = Modifier.weight(1f).padding(horizontal = 4.dp) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceToString.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceToString.kt new file mode 100644 index 0000000..2baad6c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/attendance/composables/AttendanceToString.kt @@ -0,0 +1,19 @@ +package org.example.presenceapp.ui.feature.attendance.composables + +import org.example.presenceapp.domain.entities.AbsenceReason + +fun AbsenceReason.toCustomString(): String { + return when (this) { + AbsenceReason.SKIP -> "отсут" + AbsenceReason.SICK -> "болезнь" + AbsenceReason.COMPETITION -> "соревнования" + } +} + +fun String.toReadableString(): String { + return try { + AbsenceReason.valueOf(this).toCustomString() + } catch (e: IllegalArgumentException) { + this + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreen.kt new file mode 100644 index 0000000..be0ae70 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreen.kt @@ -0,0 +1,84 @@ +package org.example.presenceapp.ui.feature.calendar + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.example.presenceapp.ui.feature.calendar.components.CalendarGrid +import org.example.presenceapp.ui.feature.calendar.components.CalendarMonth +import org.example.presenceapp.ui.feature.calendar.components.WeekDaysHeader +import org.example.presenceapp.ui.feature.commons.CommonBottomBar +import org.example.presenceapp.ui.feature.commons.CommonTopBar +import org.example.presenceapp.ui.theme.AppTheme +import org.example.presenceapp.ui.types.ScreenType + +class CalendarScreen : Screen { + @Composable + override fun Content() { + val screenModel = remember { CalendarScreenModel() } + + Calendar(screenModel = screenModel) + } +} + +@Composable +fun Calendar(screenModel: CalendarScreenModel) { + val navigator = LocalNavigator.currentOrThrow + val viewModel = remember { screenModel } + val currentDate = remember { Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date } + val monthData by viewModel.monthData.collectAsState() + + LaunchedEffect(currentDate) { + viewModel.loadMonthData(currentDate.year, currentDate.monthNumber) + } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + CommonTopBar( + screenType = ScreenType.CALENDAR, + text = "" + ) + }, + bottomBar = { CommonBottomBar() } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .background(color = AppTheme.colors.white) + .padding(padding) + .padding(horizontal = 16.dp) + ) { + CalendarMonth( + month = currentDate.month, + year = currentDate.year, + modifier = Modifier.padding(vertical = 16.dp) + ) + WeekDaysHeader( + modifier = Modifier + .padding(bottom = 8.dp) + ) + CalendarGrid( + monthData = monthData, + currentDate = currentDate, + onDayClick = { date -> + viewModel.toggleAttendance(date) + } + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreenModel.kt new file mode 100644 index 0000000..7a3c874 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/CalendarScreenModel.kt @@ -0,0 +1,47 @@ +package org.example.presenceapp.ui.feature.calendar + +import cafe.adriel.voyager.core.model.ScreenModel +import kotlinx.coroutines.flow.* +import kotlinx.datetime.* +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.domain.entities.DayData +import org.example.presenceapp.ui.feature.calendar.components.CalendarUtils + +class CalendarScreenModel: ScreenModel { + private val _monthData = MutableStateFlow>(emptyList()) + val monthData: StateFlow> = _monthData.asStateFlow() + + fun loadMonthData(year: Int, month: Int) { + _monthData.value = CalendarUtils.generateMonthData( + year = year, + month = month, + attendanceData = attendanceData() + ) + } + + fun toggleAttendance(date: LocalDate) { + val currentStatus = _monthData.value + .firstOrNull { it.date == date } + ?.attendance + + val newStatus = when (currentStatus) { + null -> AttendanceTypeView.ABSENT + AttendanceTypeView.ABSENT -> AttendanceTypeView.PRESENT + AttendanceTypeView.PRESENT -> null + } + + _monthData.update { days -> + days.map { day -> + if (day.date == date) day.copy(attendance = newStatus) + else day + } + } + } + + private fun attendanceData(): Map { + return mapOf( + LocalDate(2025, 3, 7) to AttendanceTypeView.ABSENT, + LocalDate(2025, 3, 8) to AttendanceTypeView.PRESENT + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarDayCell.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarDayCell.kt new file mode 100644 index 0000000..de3022e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarDayCell.kt @@ -0,0 +1,60 @@ +package org.example.presenceapp.ui.feature.calendar.components + +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.domain.entities.DayData +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun DayCell( + day: DayData, + isToday: Boolean, + onClick: () -> Unit +) { + val borderColor = when { + isToday -> AppTheme.colors.black + !day.isCurrentMonth -> Color.Transparent + else -> when (day.attendance) { + AttendanceTypeView.PRESENT -> AppTheme.colors.gray + AttendanceTypeView.ABSENT -> AppTheme.colors.red + null -> AppTheme.colors.gray + } + } + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .aspectRatio(1f) + .clip(RoundedCornerShape(10.dp)) + .border(1.dp, borderColor, RoundedCornerShape(10.dp)) + .clickable( + enabled = day.isCurrentMonth, + onClick = onClick + ) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = day.date.dayOfMonth.toString(), + color = when { + !day.isCurrentMonth -> AppTheme.colors.black.copy(alpha = 0.3f) + else -> AppTheme.colors.black + }, + style = AppTheme.typography.message + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarGrid.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarGrid.kt new file mode 100644 index 0000000..871a2a0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarGrid.kt @@ -0,0 +1,31 @@ +package org.example.presenceapp.ui.feature.calendar.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import kotlinx.datetime.LocalDate +import org.example.presenceapp.domain.entities.DayData + +@Composable +fun CalendarGrid( + monthData: List, + currentDate: LocalDate, + onDayClick: (LocalDate) -> Unit +) { + LazyVerticalGrid( + columns = GridCells.Fixed(7), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(monthData) { day -> + DayCell( + day = day, + isToday = day.date == currentDate, + onClick = { onDayClick(day.date) } + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarMonth.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarMonth.kt new file mode 100644 index 0000000..d2d959b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarMonth.kt @@ -0,0 +1,20 @@ +package org.example.presenceapp.ui.feature.calendar.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import kotlinx.datetime.Month +import org.example.presenceapp.ui.feature.commons.CommonMainText + +@Composable +fun CalendarMonth( + month: Month, + year: Int, + modifier: Modifier +) { + CommonMainText( + text = "${month.name.lowercase().replaceFirstChar { it.titlecase() }} $year", + textAlign = TextAlign.Center, + modifier = modifier + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarUtils.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarUtils.kt new file mode 100644 index 0000000..77ab428 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarUtils.kt @@ -0,0 +1,35 @@ +package org.example.presenceapp.ui.feature.calendar.components + +import kotlinx.datetime.* +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.domain.entities.DayData +import kotlin.sequences.generateSequence + +object CalendarUtils { + fun generateMonthData( + year: Int, + month: Int, + attendanceData: Map + ): List { + val firstDay = LocalDate(year, month, 1) + val lastDay = firstDay.plus(DatePeriod(months = 1)).minus(DatePeriod(days = 1)) + + val startDate = firstDay.minus(DatePeriod(days = firstDay.dayOfWeek.isoDayNumber - 1)) + val endDate = lastDay.plus(DatePeriod(days = 7 - lastDay.dayOfWeek.isoDayNumber)) + + return generateSequence(startDate) { it.plus(1, DateTimeUnit.DAY) } + .takeWhile { it <= endDate } + .map { date -> + DayData( + date = date, + isCurrentMonth = date.monthNumber == month, + attendance = attendanceData[date] + ) + } + .toList() + } + + private fun isLeapYear(year: Int): Boolean { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarWeekDays.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarWeekDays.kt new file mode 100644 index 0000000..74fa9b0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/calendar/components/CalendarWeekDays.kt @@ -0,0 +1,29 @@ +package org.example.presenceapp.ui.feature.calendar.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun WeekDaysHeader(modifier: Modifier = Modifier) { + val daysOfWeek = listOf("Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс") + + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + daysOfWeek.forEach { day -> + Text( + text = day, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + modifier = Modifier.weight(1f).wrapContentWidth() + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonBottomBar.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonBottomBar.kt new file mode 100644 index 0000000..041c5c3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonBottomBar.kt @@ -0,0 +1,101 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemColors +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import com.presenceapp.composeapp.resources.Res +import com.presenceapp.composeapp.resources.info +import com.presenceapp.composeapp.resources.schedule +import com.presenceapp.composeapp.resources.settings +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.ui.feature.info.InfoScreen +import org.example.presenceapp.ui.feature.settings.SettingsScreen +import org.example.presenceapp.ui.theme.AppTheme +import org.example.project.ui.weeks.WeeksScreen +import org.jetbrains.compose.resources.painterResource + +@Composable +fun CommonBottomBar() { + val navigator = LocalNavigator.currentOrThrow + val currentScreen = navigator.lastItem + val lessons = emptyList() + val routes = listOf( + Triple(Res.drawable.info, "Информация", InfoScreen()), + Triple(Res.drawable.schedule, "Расписание", WeeksScreen(lessons = lessons)), + Triple(Res.drawable.settings, "Настройки", SettingsScreen()) + ) + + NavigationBar( + containerColor = AppTheme.colors.black, + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + ) { + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + ) { + routes.forEach { (icon, label, screen) -> + val isSelected = currentScreen?.key == screen.key + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .weight(1f) + .clickable { + if (currentScreen?.key != screen.key) { + navigator.push(screen) + } + } + ) { + this@Row.NavigationBarItem( + selected = isSelected, + onClick = { + if (currentScreen?.key != screen.key) { + navigator.push(screen) + } + }, + icon = { + Icon( + painter = painterResource(icon), + contentDescription = null, + modifier = Modifier + .size(24.dp) + ) + }, + colors = NavigationBarItemColors( + selectedIndicatorColor = AppTheme.colors.white, + selectedIconColor = AppTheme.colors.black, + selectedTextColor = AppTheme.colors.white, + unselectedIconColor = AppTheme.colors.white, + unselectedTextColor = AppTheme.colors.white, + disabledIconColor = AppTheme.colors.white, + disabledTextColor = AppTheme.colors.white, + ) + ) + Text( + text = label, + color = AppTheme.colors.white, + style = AppTheme.typography.bottomBar + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonButton.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonButton.kt new file mode 100644 index 0000000..e0a20e6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonButton.kt @@ -0,0 +1,25 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun CommonButton( + onClick: () -> Unit, + content: @Composable () -> Unit, + modifier: Modifier +) { + Button( + colors = ButtonDefaults.buttonColors( + containerColor = AppTheme.colors.black, + contentColor = AppTheme.colors.white + ), + onClick = onClick, + modifier = modifier + ) { + content() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDataText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDataText.kt new file mode 100644 index 0000000..93f6ba6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDataText.kt @@ -0,0 +1,22 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun CommonDataText( + text: String, + textAlign: TextAlign? = null, + modifier: Modifier +) { + Text( + text = text, + color = AppTheme.colors.black, + style = AppTheme.typography.data, + textAlign = textAlign, + modifier = modifier + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDialog.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDialog.kt new file mode 100644 index 0000000..41f7dad --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonDialog.kt @@ -0,0 +1,23 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.material3.AlertDialog +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun CommonDialog( + expanded: Boolean, + onDismiss: () -> Unit, + content: @Composable () -> Unit +) { + if (expanded) { + AlertDialog( + containerColor = AppTheme.colors.textField, + onDismissRequest = { onDismiss() }, + properties = DialogProperties(dismissOnClickOutside = true), + confirmButton = {}, + text = content + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonIconButton.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonIconButton.kt new file mode 100644 index 0000000..9c94270 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonIconButton.kt @@ -0,0 +1,56 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.types.ButtonType + +@Composable +fun CommonIconButton( + background: Color, + icon: Painter, + switchedIcon: Painter? = null, + iconColor: Color, + buttonType: ButtonType, + selectedIconState: State? = null, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val currentIcon = when { + buttonType == ButtonType.SWITCHABLE && selectedIconState != null -> { + if (selectedIconState.value) icon else switchedIcon ?: icon + } + else -> icon + } + + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .size(44.dp) + .clip(CircleShape) + .background(background) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { onClick() } + ) { + Icon( + painter = currentIcon, + contentDescription = null, + tint = iconColor + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonLabel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonLabel.kt new file mode 100644 index 0000000..cca3ad3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonLabel.kt @@ -0,0 +1,22 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun CommonLabel( + text: String, + textAlign: TextAlign? = null, + modifier: Modifier +) { + Text( + text = text, + color = AppTheme.colors.black, + style = AppTheme.typography.name, + textAlign = textAlign, + modifier = modifier + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMainText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMainText.kt new file mode 100644 index 0000000..7fd83d1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMainText.kt @@ -0,0 +1,24 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun CommonMainText( + text: String, + textAlign: TextAlign? = null, + modifier: Modifier +) { + Text( + text = text, + color = AppTheme.colors.black, + style = AppTheme.typography.main, + textAlign = textAlign, + modifier = modifier + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMediumText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMediumText.kt new file mode 100644 index 0000000..ee1d7e4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonMediumText.kt @@ -0,0 +1,22 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun CommonMediumText( + text: String, + textAlign: TextAlign? = null, + modifier: Modifier +) { + Text( + text = text, + color = AppTheme.colors.black, + style = AppTheme.typography.messageFrag, + textAlign = textAlign, + modifier = modifier + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonRegularText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonRegularText.kt new file mode 100644 index 0000000..f853fc7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonRegularText.kt @@ -0,0 +1,24 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun CommonRegularText( + text: String, + color: Color, + textAlign: TextAlign? = null, + modifier: Modifier +) { + Text( + text = text, + color = color, + style = AppTheme.typography.message, + textAlign = textAlign, + modifier = modifier + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonTopBar.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonTopBar.kt new file mode 100644 index 0000000..472598f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/CommonTopBar.kt @@ -0,0 +1,111 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import com.presenceapp.composeapp.resources.Res +import com.presenceapp.composeapp.resources.arrow_back +import com.presenceapp.composeapp.resources.is_here +import com.presenceapp.composeapp.resources.isnt_here +import com.presenceapp.composeapp.resources.settings +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.ui.feature.calendar.CalendarScreen +import org.example.presenceapp.ui.theme.AppTheme +import org.example.presenceapp.ui.types.ButtonType +import org.example.presenceapp.ui.types.ScreenType +import org.jetbrains.compose.resources.painterResource + +@Composable +fun CommonTopBar( + screenType: ScreenType, + text: String, + onChangeSortType: ((AttendanceTypeView) -> Unit)? = null +) { + val navigator = LocalNavigator.currentOrThrow + val selectedIconState = remember { mutableStateOf(false) } + + if (screenType == ScreenType.WEEKS || screenType == ScreenType.CALENDAR) { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .background(AppTheme.colors.white) + .padding(horizontal = 16.dp) + .padding(top = 16.dp) + .height(75.dp) + ) { + CommonIconButton( + background = AppTheme.colors.black, + icon = painterResource(Res.drawable.settings), + iconColor = AppTheme.colors.white, + buttonType = ButtonType.notSWITCHABLE, + onClick = { + if (screenType == ScreenType.WEEKS) { navigator.push(CalendarScreen()) } + else { navigator.pop() } + }, + modifier = Modifier + ) + } + } + else { + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .background(AppTheme.colors.white) + .padding(horizontal = 16.dp) + .padding(top = 16.dp) + .height(75.dp) + ) { + CommonIconButton( + background = AppTheme.colors.black, + icon = painterResource(Res.drawable.arrow_back), + iconColor = AppTheme.colors.white, + buttonType = ButtonType.notSWITCHABLE, + onClick = { navigator.pop() }, + modifier = Modifier + ) + CommonLabel( + text = text, + textAlign = TextAlign.Center, + modifier = Modifier + .weight(1f) + .padding(end = if (screenType == ScreenType.GROUP) 0.dp else 44.dp) + ) + if (screenType == ScreenType.GROUP) { + val newSortType = when (selectedIconState.value) { + false -> AttendanceTypeView.ABSENT + true -> AttendanceTypeView.PRESENT + } + + CommonIconButton( + background = AppTheme.colors.black, + icon = painterResource(Res.drawable.isnt_here), + switchedIcon = painterResource(Res.drawable.is_here), + iconColor = AppTheme.colors.white, + buttonType = ButtonType.SWITCHABLE, + selectedIconState = selectedIconState, + onClick = { + selectedIconState.value = !selectedIconState.value + onChangeSortType?.invoke(newSortType) + }, + modifier = Modifier + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/ErrorDialog.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/ErrorDialog.kt new file mode 100644 index 0000000..38e6204 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/commons/ErrorDialog.kt @@ -0,0 +1,36 @@ +package org.example.presenceapp.ui.feature.commons + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog + +@Composable +fun ErrorDialog( + onDismiss: ()-> Unit, + text: String +){ + Dialog( + onDismissRequest = { onDismiss() } + ){ + Column( + modifier = Modifier.fillMaxWidth().wrapContentHeight().background(Color.White, RoundedCornerShape(10.dp)).padding(10.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreen.kt new file mode 100644 index 0000000..cce53f1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreen.kt @@ -0,0 +1,71 @@ +package org.example.presenceapp.ui.feature.info + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import org.example.presenceapp.ui.feature.commons.CommonBottomBar +import org.example.presenceapp.ui.feature.info.components.InfoCard +import org.example.presenceapp.ui.theme.AppTheme + +class InfoScreen(): Screen { + @Composable + override fun Content() { + val viewModel = rememberScreenModel { InfoScreenModel() } + Info(viewModel) + } + @Composable + fun Info(viewModel: InfoScreenModel) { + + Scaffold( + modifier = Modifier.fillMaxSize(), + bottomBar = { CommonBottomBar() } + ) { padding -> + + Column( + modifier = Modifier.fillMaxSize().background(AppTheme.colors.white).padding(32.dp) + ) { + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ){ + Text( + "Информация", + modifier = Modifier.padding(top = 10.dp), + color = AppTheme.colors.black, + style = AppTheme.typography.main + ) + } + Column( + modifier = Modifier.fillMaxWidth().padding(top = 43.dp) + ) { + viewModel.user.forEach { + InfoCard( + onClick = {}, + text = it.toString() + ) + Spacer(Modifier.height(10.dp)) + } + } + } + } + } +} + + + diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenModel.kt new file mode 100644 index 0000000..febd961 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenModel.kt @@ -0,0 +1,25 @@ +package org.example.presenceapp.ui.feature.info + +import cafe.adriel.voyager.core.model.ScreenModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import org.example.presenceapp.domain.entities.UserInfo +import org.example.presenceapp.domain.entities.UserResponse + +class InfoScreenModel: ScreenModel { + val state = MutableStateFlow(org.example.presenceapp.ui.feature.info.InfoScreenState()) + + fun getUserInfo(userResponse: UserResponse?){ + state.update { + it.copy( + userInfo = userResponse + ) + } + } + + val user = listOf( + UserInfo.userGroup, + UserInfo.userName, + UserInfo.userRole + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenState.kt new file mode 100644 index 0000000..d330797 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/InfoScreenState.kt @@ -0,0 +1,7 @@ +package org.example.presenceapp.ui.feature.info + +import org.example.presenceapp.domain.entities.UserResponse + +data class InfoScreenState( + val userInfo: UserResponse? = null +) diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/components/InfoCard.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/components/InfoCard.kt new file mode 100644 index 0000000..beebc17 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/info/components/InfoCard.kt @@ -0,0 +1,46 @@ +package org.example.presenceapp.ui.feature.info.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun InfoCard( + onClick: () -> Unit, + text: String +) { + Card( + colors = CardDefaults.cardColors(containerColor = Color.Transparent), + border = BorderStroke(1.dp, AppTheme.colors.gray), + modifier = Modifier + .fillMaxWidth() + .background(AppTheme.colors.white) + .clickable { onClick() } + ) { + Row( + modifier = Modifier + .padding(16.dp) + ) { + Text( + text = text, + color = AppTheme.colors.black, + style = AppTheme.typography.name, + modifier = Modifier + .padding(start = 5.dp, end = 21.dp) + .align(Alignment.CenterVertically) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreen.kt new file mode 100644 index 0000000..0233e70 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreen.kt @@ -0,0 +1,116 @@ +package org.example.presenceapp.ui.feature.login + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.koin.koinScreenModel +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.presenceapp.ui.feature.commons.ErrorDialog +import org.example.presenceapp.ui.feature.login.components.LoginButton +import org.example.presenceapp.ui.feature.login.components.LoginCheckBox +import org.example.presenceapp.ui.feature.login.components.LoginTextField +import org.example.presenceapp.ui.theme.AppTheme +import org.example.project.ui.weeks.WeeksScreen + +class LoginScreen : Screen { + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val screenModel: org.example.presenceapp.ui.feature.login.LoginScreenModel = koinScreenModel() + val state by screenModel.state.collectAsState() + val effect = screenModel.effect + + LaunchedEffect(Unit) { + effect.collect { loginEffect -> + when (loginEffect) { + is LoginEffect.ShowToast -> { + println("TOAST: ${loginEffect.message}") + } + is LoginEffect.NavigateToWeeks -> { + navigator.push(WeeksScreen(loginEffect.lessons)) + } + } + } + } + + Login(screenModel, state) + } + + @Composable + fun Login( + screenModel: org.example.presenceapp.ui.feature.login.LoginScreenModel, + state: LoginScreenState + ) { + Column( + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .background(AppTheme.colors.white) + .padding(horizontal = 32.dp) + ) { + state.error?.let { + ErrorDialog( + onDismiss = { screenModel.onEvent(LoginEvent.ResetError) }, + text = it + ) + } + + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(top = 142.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + "Добро пожаловать!", + color = AppTheme.colors.black, + textAlign = TextAlign.Center, + style = AppTheme.typography.main + ) + LoginTextField( + value = state.login, + onValue = { screenModel.onEvent(LoginEvent.EnterLogin(it)) }, + placeholder = "xyz", + text = "Логин", + top = 145 + ) + LoginTextField( + value = state.password, + onValue = { screenModel.onEvent(LoginEvent.EnterPassword(it)) }, + placeholder = "********", + text = "Пароль", + top = 18 + ) + LoginCheckBox( + check = state.check, + onCheck = { screenModel.onEvent(LoginEvent.ToggleCheck) }, + top = 24 + ) + } + + LoginButton( + text = "Войти", + onClick = { screenModel.onEvent(LoginEvent.SubmitLogin) }, + bottom = 80 + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenModel.kt new file mode 100644 index 0000000..1500fc4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenModel.kt @@ -0,0 +1,100 @@ +package org.example.presenceapp.ui.feature.login + +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.example.presenceapp.domain.command.schedule.GroupCommand +import org.example.presenceapp.domain.command.LoginCommand +import org.example.presenceapp.domain.entities.UserInfo +import org.example.presenceapp.domain.repo.LoginRepository +import org.example.presenceapp.domain.repo.ScheduleRepository + +class LoginScreenModel( + private val loginRepository: LoginRepository, + private val scheduleRepository: ScheduleRepository +) : ScreenModel { + + private val _state = MutableStateFlow(_root_ide_package_.org.example.presenceapp.ui.feature.login.LoginScreenState()) + val state: StateFlow = _state + + private val _effect = MutableSharedFlow() + val effect: SharedFlow = _effect + + fun onEvent(event: org.example.presenceapp.ui.feature.login.LoginEvent) { + when (event) { + is org.example.presenceapp.ui.feature.login.LoginEvent.EnterLogin -> { + _state.update { it.copy(login = event.value) } + } + + is _root_ide_package_.org.example.presenceapp.ui.feature.login.LoginEvent.EnterPassword -> { + _state.update { it.copy(password = event.value) } + } + + is _root_ide_package_.org.example.presenceapp.ui.feature.login.LoginEvent.ToggleCheck -> { + _state.update { it.copy(check = !it.check) } + } + + is _root_ide_package_.org.example.presenceapp.ui.feature.login.LoginEvent.ResetError -> { + _state.update { it.copy(error = null) } + } + + is _root_ide_package_.org.example.presenceapp.ui.feature.login.LoginEvent.SubmitLogin -> { + login() + } + } + } + + private fun login() { + val currentState = _state.value + val loginCommand = LoginCommand(currentState.login, currentState.password) + + screenModelScope.launch { + try { + val loginResponse = loginRepository.login(loginCommand) + val userResponse = loginResponse.user + val groupId = userResponse.responsible.first().group.id + + UserInfo.userGroup = userResponse.responsible.first().group.name + UserInfo.userName = userResponse.fio + UserInfo.userRole = userResponse.role.name + + getSchedule(groupId) + + _state.update { + it.copy(userInfo = userResponse) + } + + } catch (e: Exception) { + _state.update { it.copy(error = e.message) } + _effect.emit( + _root_ide_package_.org.example.presenceapp.ui.feature.login.LoginEffect.ShowToast( + e.message ?: "Неизвестная ошибка" + ) + ) + } + } + } + + private suspend fun getSchedule(groupId: Int) { + try { + val scheduleList = scheduleRepository.getSchedule(GroupCommand(groupId)) + _state.update { it.copy(lessonsList = scheduleList) } + _effect.emit( + _root_ide_package_.org.example.presenceapp.ui.feature.login.LoginEffect.NavigateToWeeks( + scheduleList + ) + ) + } catch (e: Exception) { + _effect.emit( + _root_ide_package_.org.example.presenceapp.ui.feature.login.LoginEffect.ShowToast( + e.message ?: "Ошибка" + ) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenState.kt new file mode 100644 index 0000000..7c5f216 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/LoginScreenState.kt @@ -0,0 +1,32 @@ +package org.example.presenceapp.ui.feature.login + +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.Student +import org.example.presenceapp.domain.entities.UserResponse + +sealed interface LoginEvent { + data class EnterLogin(val value: String) : LoginEvent + data class EnterPassword(val value: String) : LoginEvent + object ToggleCheck : LoginEvent + object SubmitLogin : LoginEvent + object ResetError : LoginEvent +} + +sealed interface LoginEffect { + data class ShowToast(val message: String) : LoginEffect + data class NavigateToWeeks(val lessons: List) : LoginEffect +} + +data class LoginScreenState( + val login: String = "", + val password: String = "", + val error: String? = null, + val success: Boolean = false, + val getAllData: Boolean = false, + val check: Boolean = false, + val userInfo: UserResponse? = null, + val lessonsList: List = emptyList(), + val groupList: List = emptyList(), + val groupPresence: List = emptyList() +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginButton.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginButton.kt new file mode 100644 index 0000000..f659e1d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginButton.kt @@ -0,0 +1,36 @@ +package org.example.presenceapp.ui.feature.login.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun LoginButton( + text: String, + onClick: () -> Unit, + bottom: Int +){ + Button( + modifier = Modifier.padding(bottom = bottom.dp).fillMaxWidth(), + onClick = { onClick() }, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = AppTheme.colors.black, + contentColor = AppTheme.colors.textField, + disabledContentColor = AppTheme.colors.gray, + disabledContainerColor =AppTheme.colors.textField + ) + ){ + Text( + text, + style = AppTheme.typography.message + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginCheckBox.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginCheckBox.kt new file mode 100644 index 0000000..6abc23e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginCheckBox.kt @@ -0,0 +1,50 @@ +package org.example.presenceapp.ui.feature.login.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.feature.commons.CommonRegularText +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun LoginCheckBox( + check: Boolean, + onCheck: (Boolean) -> Unit, + top: Int +){ + Row( + modifier = Modifier.padding(top = top.dp).fillMaxWidth().height(44.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = check, + onCheckedChange = onCheck, + modifier = Modifier.size(18.dp), + colors = CheckboxDefaults.colors( + checkedColor = AppTheme.colors.black, + checkmarkColor = AppTheme.colors.white, + uncheckedColor = AppTheme.colors.black + ) + ) + CommonRegularText( + text = "Пользовательское\nсоглашение", + color = AppTheme.colors.black, + textAlign = TextAlign.Start, + modifier = Modifier + .padding(start = 16.dp) + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginTextField.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginTextField.kt new file mode 100644 index 0000000..11312c3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/login/components/LoginTextField.kt @@ -0,0 +1,53 @@ +package org.example.presenceapp.ui.feature.login.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.feature.commons.CommonRegularText +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun LoginTextField( + onValue: (String) -> Unit, + value: String, + placeholder: String, + top: Int, + text: String +){ + Column( + modifier = Modifier.fillMaxWidth().padding(top = top.dp), + ) { + CommonRegularText( + text = text, + color = AppTheme.colors.black, + modifier = Modifier + ) + OutlinedTextField( + value = value, + onValueChange = onValue, + shape = RoundedCornerShape(8.dp), + modifier = Modifier.padding(top = 9.dp).fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = AppTheme.colors.black, + unfocusedBorderColor = AppTheme.colors.gray, + cursorColor = AppTheme.colors.black, + focusedTextColor = AppTheme.colors.black, + unfocusedTextColor = AppTheme.colors.black + ), + placeholder = { + CommonRegularText( + text = placeholder, + color = AppTheme.colors.gray, + modifier = Modifier + ) + } + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreen.kt new file mode 100644 index 0000000..bf8e31a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreen.kt @@ -0,0 +1,112 @@ +package org.example.presenceapp.ui.feature.schedule + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.plus +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.ui.theme.AppTheme +import org.example.presenceapp.ui.types.ScreenType +import org.example.presenceapp.domain.entities.Week +import org.example.presenceapp.ui.feature.attendance.AttendanceScreen +import org.example.presenceapp.ui.feature.commons.CommonTopBar +import org.example.presenceapp.ui.feature.schedule.components.ScheduleDaySelector +import org.example.presenceapp.ui.feature.schedule.components.ScheduleLessonList +import org.example.project.domain.models.formatDay + +data class ScheduleScreen( + private val lessons: List, + private val week: Week, +): Screen { + @Composable + override fun Content() { + val screenModel = rememberScreenModel { ScheduleScreenModel(lessons, week) } + + Schedule(screenModel = screenModel) + + } + + @Composable + fun Schedule(screenModel: ScheduleScreenModel) { + val navigator = LocalNavigator.currentOrThrow + val state = screenModel.state.collectAsState().value + val daysOfWeek = listOf("Пн", "Вт", "Ср", "Чт", "Пт", "Сб") + val currentDayIndex by screenModel.currentDayIndex.collectAsState() + val pagerState = rememberPagerState( + initialPage = currentDayIndex, + pageCount = { daysOfWeek.size } + ) + val currentPage = pagerState.currentPage + pagerState.currentPageOffsetFraction + val currentDay = week.startDate.plus(currentPage.toInt(), DateTimeUnit.DAY) + + + LaunchedEffect(pagerState) { + snapshotFlow { pagerState.isScrollInProgress } + .collect { isScrolling -> + screenModel.setSwipeState(isScrolling) + if (!isScrolling) { + screenModel.selectDay(pagerState.currentPage) + } + } + } + LaunchedEffect(currentDayIndex) { + if (!pagerState.isScrollInProgress) { + pagerState.animateScrollToPage(currentDayIndex) + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(AppTheme.colors.white) + ) { + Scaffold( + topBar = { + CommonTopBar( + screenType = ScreenType.SCHEDULE, + text = currentDay.formatDay() + ) + }, + ) { padding -> + Column( + modifier = Modifier + .padding(padding) + ) { + ScheduleDaySelector( + currentPage = currentPage, + daysOfWeek = daysOfWeek, + onDaySelected = { screenModel.selectDay(it) }, + indicatorColor = AppTheme.colors.black + ) + HorizontalPager(state = pagerState) { page -> + val day = page + 1 + ScheduleLessonList( + lessons = state.lessonsList.filter { it.dayOfWeek == day } + .sortedBy { it.lessonNumber }, + onLessonClick = { lesson -> + screenModel.selectLesson(lesson) + navigator.push(AttendanceScreen(lesson)) + } + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenModel.kt new file mode 100644 index 0000000..f6d2281 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenModel.kt @@ -0,0 +1,57 @@ +package org.example.presenceapp.ui.feature.schedule + +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.Week + +data class ScheduleScreenModel( + private val lessons: List, + private val week: Week + +): ScreenModel { + val state = MutableStateFlow(ScheduleScreenState()) + + private val _currentDayIndex = MutableStateFlow(0) + val currentDayIndex: StateFlow = _currentDayIndex.asStateFlow() + + init { + getGroup(lessons) + } + + fun setSwipeState(swiping: Boolean) { + state.update { + it.copy( + isUserSwiping = swiping + ) + } + } + + fun selectDay(index: Int) { + if (!state.value.isUserSwiping) { + _currentDayIndex.value = index + } + } + + fun selectLesson(lesson: Schedule) { + state.update { + it.copy( + schedule = lesson + ) + } + } + fun getGroup(lesson: List){ + screenModelScope.launch { + state.update { + it.copy( + lessonsList = lesson + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenState.kt new file mode 100644 index 0000000..61b2988 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/ScheduleScreenState.kt @@ -0,0 +1,17 @@ +package org.example.presenceapp.ui.feature.schedule + +import kotlinx.datetime.LocalDate +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.Student +import org.example.presenceapp.domain.entities.Week + +data class ScheduleScreenState( + var currentWeek: Week? = null, + val selectedDate: LocalDate? = null, + var schedule: Schedule? = null, + var isUserSwiping: Boolean = false, + var lessonsList: List = emptyList(), + var groupList: List = emptyList(), + var groupPresence: List = emptyList() +) diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/EmptyScheduleLessonsItem.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/EmptyScheduleLessonsItem.kt new file mode 100644 index 0000000..701520d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/EmptyScheduleLessonsItem.kt @@ -0,0 +1,67 @@ +package org.example.presenceapp.ui.feature.schedule.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.common.SampleData.lessonTimes +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun EmptyScheduleLessonsItem( + lessonNumber: Int +) { + Card( + colors = CardDefaults.cardColors(containerColor = Color.Transparent), + border = BorderStroke(1.dp, AppTheme.colors.gray), + modifier = Modifier + .fillMaxWidth() + .background(AppTheme.colors.white) + ) { + Row( + modifier = Modifier + .padding(16.dp) + ) { + Text( + text = "$lessonNumber", + color = AppTheme.colors.black.copy(alpha = 0.5f), + style = AppTheme.typography.name, + modifier = Modifier + .padding(start = 5.dp, end = 21.dp) + .align(Alignment.CenterVertically) + ) + Column( + verticalArrangement = Arrangement.spacedBy(5.dp), + modifier = Modifier.weight(1f) + ) { + Text( + text = "-", + color = AppTheme.colors.black.copy(alpha = 0.5f), + style = AppTheme.typography.name + ) + Text( + text = "Кабинет: —", + color = AppTheme.colors.black.copy(alpha = 0.5f), + style = AppTheme.typography.data + ) + } + Text( + text = lessonTimes.getOrNull(lessonNumber - 1) ?: "", + style = AppTheme.typography.data, + color = AppTheme.colors.black.copy(alpha = 0.5f), + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDaySelector.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDaySelector.kt new file mode 100644 index 0000000..da036c3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDaySelector.kt @@ -0,0 +1,77 @@ +package org.example.presenceapp.ui.feature.schedule.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun ScheduleDaySelector( + currentPage: Float, + daysOfWeek: List, + onDaySelected: (Int) -> Unit, + indicatorColor: Color +) { + val density = LocalDensity.current + var rowWidth by remember { mutableStateOf(0) } + val itemWidthPx = if (rowWidth > 0) rowWidth.toFloat() / daysOfWeek.size else 0f + + Box( + modifier = Modifier + .background(AppTheme.colors.white) + .padding(horizontal = 16.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .onGloballyPositioned { rowWidth = it.size.width } + .height(IntrinsicSize.Min) + ) { + if (itemWidthPx > 0) { + Box( + modifier = Modifier + .offset { IntOffset((currentPage * itemWidthPx).toInt(), 0) } + .width(with(density) { itemWidthPx.toDp() }) + .fillMaxHeight() + .clip(RoundedCornerShape(8.dp)) + .background(indicatorColor) + ) + } + + Row( + horizontalArrangement = Arrangement.SpaceAround, + modifier = Modifier.fillMaxWidth() + ) { + daysOfWeek.forEachIndexed { index, day -> + ScheduleDayText( + index = index, + currentPage = currentPage, + daysOfWeek = daysOfWeek, + onDaySelected = onDaySelected + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDayText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDayText.kt new file mode 100644 index 0000000..d692a13 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleDayText.kt @@ -0,0 +1,50 @@ +package org.example.presenceapp.ui.feature.schedule.components + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.graphics.lerp +import org.example.presenceapp.ui.theme.AppTheme +import kotlin.math.abs + +@Composable +fun ScheduleDayText( + index: Int, + currentPage: Float, + daysOfWeek: List, + onDaySelected: (Int) -> Unit +) { + val day = daysOfWeek[index] + val progress by animateFloatAsState( + targetValue = 1f - minOf( + abs(currentPage - index), + 1f + ).coerceIn(0f, 1f), + ) + val textColor by animateColorAsState( + targetValue = lerp( + AppTheme.colors.black, + AppTheme.colors.white, + progress + ) + ) + + Text( + text = day, + color = textColor, + style = AppTheme.typography.message, + modifier = Modifier + .clickable { onDaySelected(index) } + .padding(horizontal = 12.dp, vertical = 8.dp) + .wrapContentWidth(Alignment.CenterHorizontally) + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonItem.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonItem.kt new file mode 100644 index 0000000..f23df75 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonItem.kt @@ -0,0 +1,66 @@ +package org.example.presenceapp.ui.feature.schedule.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.common.SampleData.lessonTimes +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun ScheduleLessonItem( + lesson: Schedule, + index: Int, + onLessonClick: (Schedule) -> Unit +) { + Card( + colors = CardDefaults.cardColors(containerColor = Color.Transparent), + border = BorderStroke(1.dp, AppTheme.colors.gray), + modifier = Modifier + .fillMaxWidth() + .background(AppTheme.colors.white) + .clickable { onLessonClick(lesson) } + ) { + Row( + modifier = Modifier + .padding(16.dp) + ) { + Text( + text = "$index", + color = AppTheme.colors.black, + style = AppTheme.typography.name, + modifier = Modifier + .padding(start = 5.dp, end = 21.dp) + .align(Alignment.CenterVertically) + ) + Column( + verticalArrangement = Arrangement.spacedBy(5.dp), + modifier = Modifier + .weight(1f) + ) { + Text(text = lesson.subject.name, color = AppTheme.colors.black, style = AppTheme.typography.name) + Text(text = "Кабинет: ${lesson.audience}", color = AppTheme.colors.black, style = AppTheme.typography.data) + } + Text( + text = lessonTimes.getOrNull(index - 1) ?: "", + style = AppTheme.typography.data, + color = AppTheme.colors.black, + modifier = Modifier + .align(Alignment.CenterVertically) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonList.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonList.kt new file mode 100644 index 0000000..9d94a00 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/schedule/components/ScheduleLessonList.kt @@ -0,0 +1,54 @@ +package org.example.presenceapp.ui.feature.schedule.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun ScheduleLessonList(lessons: List, + onLessonClick: (Schedule) -> Unit) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier + .fillMaxSize() + .background(AppTheme.colors.white) + .padding(horizontal = 16.dp, vertical = 16.dp) + ) { + if (lessons.isEmpty()) { + item { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text("Нет занятий") + } + } + } else { + items(8) { index -> + val lessonNumber = index + 1 + val lesson = lessons.firstOrNull{it.lessonNumber == lessonNumber} + if (lesson != null) { + ScheduleLessonItem( + lesson = lesson, + index = lessonNumber, + onLessonClick = onLessonClick + ) + } else { + EmptyScheduleLessonsItem( + lessonNumber + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/Preset.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/Preset.kt new file mode 100644 index 0000000..b1104c7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/Preset.kt @@ -0,0 +1,70 @@ +package org.example.presenceapp.ui.feature.settings + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.ui.feature.commons.CommonButton +import org.example.presenceapp.ui.feature.commons.CommonMediumText +import org.example.presenceapp.ui.feature.commons.CommonRegularText +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun Preset( + onDismiss: () -> Unit, + onStatusSelected: (AttendanceTypeView) -> Unit +) { + Dialog( + onDismissRequest = onDismiss + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .background(AppTheme.colors.textField, RoundedCornerShape(16.dp)) + .wrapContentHeight() + .padding(25.dp) + ) { + CommonMediumText( + text = "Выберите статус по умолчанию", + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(20.dp)) + + AttendanceTypeView.entries.forEach { type -> + CommonButton( + onClick = { + onStatusSelected(type) + onDismiss() + }, + content = { + CommonRegularText( + text = when (type) { + AttendanceTypeView.PRESENT -> "Присутствует" + AttendanceTypeView.ABSENT -> "Отсутствует" + }, + color = AppTheme.colors.white, + modifier = Modifier + ) + }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.kt new file mode 100644 index 0000000..d31a322 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.kt @@ -0,0 +1,12 @@ +package org.example.presenceapp.ui.feature.settings + +import androidx.compose.runtime.Composable +import org.example.presenceapp.PlatformContext + +expect class SettingsManager(platformContext: PlatformContext) { + fun isDialogShown(): Boolean + fun setDialogShown() +} + +@Composable +expect fun getSettingsManager(): SettingsManager \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreen.kt new file mode 100644 index 0000000..f20f06d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreen.kt @@ -0,0 +1,51 @@ +package org.example.presenceapp.ui.feature.settings + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import cafe.adriel.voyager.core.screen.Screen +import org.example.presenceapp.data.local.storage.SettingsStorage +import org.example.presenceapp.data.repository.settings.SettingsRepositoryImpl +import org.example.presenceapp.getPlatformContext +import org.example.presenceapp.ui.feature.commons.CommonBottomBar +import org.example.presenceapp.ui.feature.settings.components.SettingsReasonOption +import org.example.presenceapp.ui.theme.AppTheme + +class SettingsScreen: Screen { + @Composable + override fun Content() { + val platformContext = getPlatformContext() + val settingsRepository = SettingsRepositoryImpl(settingsStorage = SettingsStorage(platformContext)) + val settingsScreenModel = remember { SettingsScreenModel(settingsRepository = settingsRepository) } + + Settings(settingsScreenModel) + } +} + +@Composable +fun Settings(settingsScreenModel: SettingsScreenModel) { + Box( + modifier = Modifier + .fillMaxSize() + .background(AppTheme.colors.white) + ) { + Scaffold( + modifier = Modifier.fillMaxSize(), + bottomBar = { CommonBottomBar() } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + ) { + SettingsReasonOption( + onReasonSelected = { } + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreenModel.kt new file mode 100644 index 0000000..e2f626f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsScreenModel.kt @@ -0,0 +1,38 @@ +package org.example.presenceapp.ui.feature.settings + +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import mu.KLogger +import org.example.presenceapp.domain.entities.AttendanceTypeView +import org.example.presenceapp.data.repository.settings.SettingsRepository + +class SettingsScreenModel( + private val settingsRepository: SettingsRepository +): ScreenModel { + private val logger: KLogger = mu.KotlinLogging.logger {} + + private val _defaultStatus = MutableStateFlow(AttendanceTypeView.ABSENT) + val defaultStatus: StateFlow = _defaultStatus.asStateFlow() + + init { + loadSettings() + } + + private fun loadSettings() { + screenModelScope.launch { + _defaultStatus.value = settingsRepository.getDefaultAttendanceStatus() + } + } + + fun updateDefaultStatus(type: AttendanceTypeView) { + screenModelScope.launch { + logger.debug { "Saving default status: $type" } + settingsRepository.setDefaultAttendanceStatus(type) + _defaultStatus.value = type + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/components/SettingsReasonOption.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/components/SettingsReasonOption.kt new file mode 100644 index 0000000..3ad204f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/settings/components/SettingsReasonOption.kt @@ -0,0 +1,92 @@ +package org.example.presenceapp.ui.feature.settings.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.feature.commons.CommonLabel +import org.example.presenceapp.ui.theme.AppTheme +import org.example.presenceapp.ui.types.AbsenceType + +@Composable +fun SettingsReasonOption( + onReasonSelected: (AbsenceType) -> Unit +) { + val reasons = listOf( + AbsenceType.SICK, + AbsenceType.COMPETITION + ) + + Column( + modifier = Modifier + .fillMaxSize() + .background(AppTheme.colors.white) + ) { + CommonLabel( + text = "Длительные причины отсутствия", + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .wrapContentWidth(Alignment.CenterHorizontally) + ) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + items(reasons) { reason -> + SettingsReasonItem( + absenceType = reason, + onClick = { onReasonSelected(reason) } + ) + } + } + } +} + +@Composable +fun SettingsReasonItem( + absenceType: AbsenceType, + onClick: () -> Unit +) { + Card( + onClick = onClick, + colors = CardDefaults.cardColors(containerColor = Color.Transparent), + border = BorderStroke(1.dp, AppTheme.colors.gray), + modifier = Modifier + .fillMaxWidth() + .background(AppTheme.colors.white) + ) { + Row( + modifier = Modifier + .padding(16.dp) + ) { + Text( + text = absenceType.reason, + color = AppTheme.colors.black, + style = AppTheme.typography.message, + modifier = Modifier + .padding(start = 5.dp, end = 21.dp) + .align(Alignment.CenterVertically) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreen.kt new file mode 100644 index 0000000..44a565e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreen.kt @@ -0,0 +1,107 @@ +package org.example.project.ui.weeks + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.ui.feature.commons.CommonBottomBar +import org.example.presenceapp.ui.feature.commons.CommonMainText +import org.example.presenceapp.ui.feature.commons.CommonTopBar +import org.example.presenceapp.ui.feature.schedule.ScheduleScreen +import org.example.presenceapp.ui.feature.weeks.components.MonthHeader +import org.example.presenceapp.ui.feature.weeks.components.ScheduleCard +import org.example.presenceapp.ui.theme.AppTheme +import org.example.presenceapp.ui.types.ScreenType +import org.example.project.domain.models.formatWeek + +data class WeeksScreen( + private val lessons: List, +): Screen { + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val viewModel = rememberScreenModel { WeeksViewModel(lessons) } + Scaffold( + topBar = { + CommonTopBar( + screenType = ScreenType.WEEKS, + text = "" + ) + }, + bottomBar = { CommonBottomBar() }, + ) { padding -> + Weeks( + viewModel, + navigator, + modifier = Modifier + .fillMaxSize() + .background(AppTheme.colors.white) + .padding(padding) + ) + } + } + + + @Composable + fun Weeks(viewModel: WeeksViewModel, + navigator: Navigator, + modifier: Modifier + ){ + val state = viewModel.state.collectAsState().value + + Column( + modifier = modifier + ) { + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ){ + CommonMainText( + text = "Расписание", + modifier = Modifier.padding(top = 10.dp) + ) + } + LazyColumn( + modifier = Modifier + .padding(top = 43.dp) + .padding(horizontal = 16.dp) + ) { + state.data.forEach { (month, year, weeks) -> + item { + MonthHeader(month = month) + Spacer(Modifier.height(10.dp)) + } + items(weeks.sortedBy { it.startDate }) { week -> + ScheduleCard(text = week.formatWeek(), onClick = { + navigator.push(ScheduleScreen(state.lessonsList, week)) + }) + Spacer(Modifier.height(10.dp)) + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreenState.kt new file mode 100644 index 0000000..3b98d8e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksScreenState.kt @@ -0,0 +1,17 @@ +package org.example.presenceapp.ui.feature.weeks + +import org.example.presenceapp.domain.entities.AttendanceView +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.UserResponse +import org.example.presenceapp.domain.entities.MonthWithWeeks +import org.example.presenceapp.domain.entities.Student + +data class WeeksScreenState( + var error: String? = null, + var success: Boolean = false, + var data: List = emptyList(), + var userInfo: UserResponse? = null, + var lessonsList: List = emptyList(), + var groupList: List = emptyList(), + var groupPresence: List = emptyList() +) diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksViewModel.kt new file mode 100644 index 0000000..51f93d4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/WeeksViewModel.kt @@ -0,0 +1,104 @@ +package org.example.project.ui.weeks + +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.minus +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn +import org.example.presenceapp.domain.entities.Schedule +import org.example.presenceapp.domain.entities.MonthWithWeeks +import org.example.presenceapp.domain.entities.Week +import org.example.presenceapp.ui.feature.weeks.WeeksScreenState + +class WeeksViewModel( + private val lessons: List, +): ScreenModel { + val state = MutableStateFlow(WeeksScreenState()) + + init { + loadWeeks() + getGroup(lessons) + } + + private fun loadWeeks() { + val weeks = getWeeksInCurrentMonth().map { (start, end) -> + Week( + startDate = start, + endDate = end + ) + } + + val currentMonth = Clock.System.todayIn(TimeZone.currentSystemDefault()).month + val currentYear = Clock.System.todayIn(TimeZone.currentSystemDefault()).year + + state.update { + it.copy( + data = listOf( + MonthWithWeeks( + month = currentMonth, + year = currentYear, + weeks = weeks + ) + ) + ) + } + } + + fun resetError(){ + state.update { + it.copy( + error = null + ) + } + } + + fun getWeeksInCurrentMonth(): List> { + val today = Clock.System.todayIn(TimeZone.currentSystemDefault()) + val firstDayOfMonth = LocalDate(today.year, today.month, 1) + val lastDayOfMonth = firstDayOfMonth.plus(1, DateTimeUnit.MONTH).minus(1, DateTimeUnit.DAY) + + val weeks = mutableListOf>() + var currentWeekStart = firstDayOfMonth.findPreviousMonday() + + while (currentWeekStart <= lastDayOfMonth || currentWeekStart.plus(6, DateTimeUnit.DAY) <= lastDayOfMonth) { + val weekEnd = currentWeekStart.plus(6, DateTimeUnit.DAY) + + if (currentWeekStart <= lastDayOfMonth || weekEnd >= firstDayOfMonth) { + weeks.add(currentWeekStart to weekEnd) + } + + currentWeekStart = currentWeekStart.plus(7, DateTimeUnit.DAY) + } + + return weeks.filter { (start, end) -> + start <= lastDayOfMonth || end >= firstDayOfMonth + } + } + + private fun LocalDate.findPreviousMonday(): LocalDate { + var date = this + while (date.dayOfWeek != DayOfWeek.MONDAY) { + date = date.minus(1, DateTimeUnit.DAY) + } + return date + } + + fun getGroup(lesson: List){ + screenModelScope.launch { + state.update { + it.copy( + + lessonsList = lesson + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/MonthHeader.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/MonthHeader.kt new file mode 100644 index 0000000..0ac5e28 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/MonthHeader.kt @@ -0,0 +1,20 @@ +package org.example.presenceapp.ui.feature.weeks.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import kotlinx.datetime.Month +import org.example.presenceapp.ui.theme.AppTheme +import org.example.project.domain.models.toRussianName + +@Composable +fun MonthHeader(month: Month) { + Text( + text = month.toRussianName(), + modifier = Modifier + .fillMaxWidth(), + style = AppTheme.typography.name, + color = AppTheme.colors.black + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/ScheduleCard.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/ScheduleCard.kt new file mode 100644 index 0000000..0d95315 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/feature/weeks/components/ScheduleCard.kt @@ -0,0 +1,43 @@ +package org.example.presenceapp.ui.feature.weeks.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.example.presenceapp.ui.theme.AppTheme + +@Composable +fun ScheduleCard( + text: String, + onClick: ()-> Unit +){ + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + modifier = Modifier + .fillMaxWidth() + .height(50.dp) + .border(1.dp, shape = RoundedCornerShape(10.dp), color = AppTheme.colors.black) + .clickable{ + onClick() + } + ) { + Text( + text, + color = AppTheme.colors.black, + modifier = Modifier.padding(start = 17.19.dp), + style = AppTheme.typography.message + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreen.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreenModel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/InfoScreenState.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/components/InfoCard.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/info/components/InfoCard.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginScreen.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginScreenState.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/LoginViewModel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginButton.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginButton.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginCheckBox.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginCheckBox.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginTextField.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/login/components/LoginTextField.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreen.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreenModel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/ScheduleScreenState.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/EmptyScheduleLessonsItem.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/EmptyScheduleLessonsItem.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleDaySelector.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleDaySelector.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleDayText.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleDayText.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleLessonItem.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleLessonItem.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleLessonList.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/schedule/components/ScheduleLessonList.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/Preset.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/Preset.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsScreen.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsScreenModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/SettingsScreenModel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/components/SettingsReasonOption.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/settings/components/SettingsReasonOption.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/Theme.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/Theme.kt index e69de29..2e325eb 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/Theme.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/Theme.kt @@ -0,0 +1,123 @@ +package org.example.presenceapp.ui.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import org.example.presenceapp.ui.theme.themeManager.ThemeState +import org.example.presenceapp.ui.theme.themeManager.ThemeTypes + +val LocalAppTheme = compositionLocalOf { ThemeTypes.SYSTEM } + +@Composable +expect fun rememberThemeState(): ThemeState + +@Immutable +data class AppColors( + val white: Color, + val black: Color, + val gray: Color, + val textField: Color, + val red: Color, + val accent: Color, + val accentText: Color, + val mic: Color +) { + companion object { + fun light() = AppColors( + white = Color(0xFFFFFFFF), + black = Color(0xFF2C2C2C), + gray = Color(0xFFD9D9D9), + textField = Color(0xFFF6F6F6), + red = Color(0xFFFF7F7F), + accent = Color(0xFFD0E2EC), + accentText = Color(0xFF7B99AB), + mic = Color(0xFF01FFFF) + ) + fun dark() = AppColors( + white = Color(0xFF2C2C2C), + black = Color(0xFFFFFFFF), + gray = Color(0xFF828282), + textField = Color(0xFF2D2D2D), + red = Color(0xFFFF7F7F), + accent = Color(0xFFD0E2EC), + accentText = Color(0xFF7B99AB), + mic = Color(0xFF01FFFF) + ) + } +} + +@Immutable +data class AppTextStyle( + val main: TextStyle, + val name: TextStyle, + val messageFrag: TextStyle, + val message: TextStyle, + val data: TextStyle, + val schedule: TextStyle, + val bottomBar: TextStyle +) + +val LocalAppColors = staticCompositionLocalOf { + AppColors( + white = Color.Unspecified, + black = Color.Unspecified, + gray = Color.Unspecified, + textField = Color.Unspecified, + red = Color.Unspecified, + accent = Color.Unspecified, + accentText = Color.Unspecified, + mic = Color.Unspecified + ) +} + +val LocalAppTypography = staticCompositionLocalOf { + AppTextStyle( + main = TextStyle.Default, + name = TextStyle.Default, + messageFrag = TextStyle.Default, + message = TextStyle.Default, + data = TextStyle.Default, + schedule = TextStyle.Default, + bottomBar = TextStyle.Default + ) +} + +@Composable +fun AppTheme(content: @Composable () -> Unit) { + val themeState = rememberThemeState() + + val appColors = if (themeState.isDarkMode) { AppColors.dark() } else { AppColors.light() } + + val appTypography = AppTextStyle( + main = TextStyle(fontWeight = FontWeight.Medium, fontSize = 26.sp), + name = TextStyle(fontWeight = FontWeight.Medium, fontSize = 18.sp), + messageFrag = TextStyle(fontWeight = FontWeight.Medium, fontSize = 16.sp), + message = TextStyle(fontWeight = FontWeight.Normal, fontSize = 16.sp), + data = TextStyle(fontWeight = FontWeight.Normal, fontSize = 14.sp), + schedule = TextStyle(fontWeight = FontWeight.Normal, fontSize = 18.sp), + bottomBar = TextStyle(fontWeight = FontWeight.Normal, fontSize = 12.sp) + ) + + CompositionLocalProvider( + LocalAppTheme provides if (themeState.isDarkMode) ThemeTypes.DARK else ThemeTypes.LIGHT, + LocalAppColors provides appColors, + LocalAppTypography provides appTypography, + content = content + ) +} + +object AppTheme { + val colors: AppColors + @Composable + get() = LocalAppColors.current + + val typography: AppTextStyle + @Composable + get() = LocalAppTypography.current +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeManager.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeManager.kt index e69de29..7de8484 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeManager.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeManager.kt @@ -0,0 +1,6 @@ +package org.example.presenceapp.ui.theme.themeManager + +interface ThemeManager { + fun getCurrentTheme(): ThemeTypes + fun setTheme(theme: ThemeTypes) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeState.kt index e69de29..d2df1a7 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeState.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeState.kt @@ -0,0 +1,26 @@ +package org.example.presenceapp.ui.theme.themeManager + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +class ThemeState( + private val themeManager: ThemeManager, + private val isSystemDark: Boolean +) { + var currentTheme by mutableStateOf(themeManager.getCurrentTheme()) + private set + + val isDarkMode: Boolean + @Composable get() = when(currentTheme) { + ThemeTypes.LIGHT -> false + ThemeTypes.DARK -> true + ThemeTypes.SYSTEM -> isSystemDark + } + + fun setTheme(theme: ThemeTypes) { + themeManager.setTheme(theme) + currentTheme = theme + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeTypes.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeTypes.kt index e69de29..16c5081 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeTypes.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/theme/themeManager/ThemeTypes.kt @@ -0,0 +1,7 @@ +package org.example.presenceapp.ui.theme.themeManager + +enum class ThemeTypes { + LIGHT, + DARK, + SYSTEM +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/AbsenceType.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/AbsenceType.kt new file mode 100644 index 0000000..cffabfa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/AbsenceType.kt @@ -0,0 +1,27 @@ +package org.example.presenceapp.ui.types + +enum class AbsenceType( + val reason: String, + val requiresDocumentation: Boolean +) { + SICK( + reason = "Болезнь", + requiresDocumentation = true + ), + COMPETITION( + reason = "Участие в соревнованиях", + requiresDocumentation = false + ), + ACADEMIC_LEAVE( + reason = "Академический отпуск", + requiresDocumentation = false + ), + FAMILY_REASONS( + reason = "Семейные обстоятельства", + requiresDocumentation = true + ), + INDIVIDUAL_PLAN( + reason = "Индивидуальный план", + requiresDocumentation = false + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ButtonType.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ButtonType.kt index e69de29..af0e18f 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ButtonType.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ButtonType.kt @@ -0,0 +1,6 @@ +package org.example.presenceapp.ui.types + +enum class ButtonType { + SWITCHABLE, + notSWITCHABLE +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ScreenType.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ScreenType.kt index e69de29..09b42d4 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ScreenType.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/types/ScreenType.kt @@ -0,0 +1,17 @@ +package org.example.presenceapp.ui.types + +enum class ScreenType { + WEEKS, + CALENDAR, + SCHEDULE, + GROUP, +} + +fun getScreenType(screenType: ScreenType): String { + return when (screenType) { + ScreenType.WEEKS -> "Расписание" + ScreenType.CALENDAR -> "Календарь" + ScreenType.SCHEDULE -> "01.01" + ScreenType.GROUP -> "" + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksScreen.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksScreenState.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksScreenState.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/WeeksViewModel.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/components/MonthHeader.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/components/MonthHeader.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/components/ScheduleCard.kt b/composeApp/src/commonMain/kotlin/org/example/presenceapp/ui/weeks/components/ScheduleCard.kt deleted file mode 100644 index e69de29..0000000 diff --git a/composeApp/src/iosMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.ios.kt b/composeApp/src/iosMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.ios.kt similarity index 93% rename from composeApp/src/iosMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.ios.kt rename to composeApp/src/iosMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.ios.kt index 87feb9e..1a99932 100644 --- a/composeApp/src/iosMain/kotlin/org/example/presenceapp/ui/settings/SettingsManager.ios.kt +++ b/composeApp/src/iosMain/kotlin/org/example/presenceapp/ui/feature/settings/SettingsManager.ios.kt @@ -1,4 +1,4 @@ -package org.example.presenceapp.ui.settings +package org.example.presenceapp.ui.feature.settings import androidx.compose.runtime.Composable import org.example.presenceapp.PlatformContext diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d08a6ff..f3e802f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ androidx-material = "1.12.0" androidx-test-junit = "1.2.1" compose-multiplatform = "1.7.3" datastorePreferences = "1.1.4" +jetmviCore = "0.2.1" junit = "4.13.2" kermit = "2.0.0" kotlin = "2.1.10" @@ -46,6 +47,7 @@ foundationLayoutAndroid = "1.7.8" androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } androidx-datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "datastorePreferences" } androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" } +jetmvi-core = { module = "com.adriel.cafe:jetmvi-core", version.ref = "jetmviCore" } kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } kotlin-logging = { module = "io.github.microutils:kotlin-logging", version.ref = "kotlinLogging" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }