From 234db4c43d34d63f78fff2316a5e566f4e331229 Mon Sep 17 00:00:00 2001 From: extractive-mana-pulse Date: Fri, 16 Jan 2026 10:08:05 +0500 Subject: [PATCH] step 2 of configuration of modularization is done --- .idea/kotlinc.xml | 2 +- .idea/misc.xml | 1 - .idea/studiobot.xml | 6 + .kotlin/errors/errors-1768372341802.log | 114 +++ .kotlin/errors/errors-1768372341807.log | 69 ++ .kotlin/errors/errors-1768372341810.log | 76 ++ .kotlin/errors/errors-1768372341816.log | 76 ++ .kotlin/errors/errors-1768372402078.log | 114 +++ .kotlin/errors/errors-1768372402083.log | 69 ++ .kotlin/errors/errors-1768372402085.log | 76 ++ .kotlin/errors/errors-1768372402089.log | 76 ++ .kotlin/errors/errors-1768372523043.log | 114 +++ .kotlin/errors/errors-1768372523046.log | 69 ++ .kotlin/errors/errors-1768372523047.log | 76 ++ .kotlin/errors/errors-1768372523050.log | 76 ++ .kotlin/errors/errors-1768372649411.log | 114 +++ .kotlin/errors/errors-1768372649416.log | 76 ++ .kotlin/errors/errors-1768372649421.log | 76 ++ .kotlin/errors/errors-1768372886661.log | 114 +++ .kotlin/errors/errors-1768372886667.log | 76 ++ .kotlin/errors/errors-1768372895329.log | 114 +++ .kotlin/errors/errors-1768372895334.log | 76 ++ app/build.gradle.kts | 141 ++-- .../java/com/example/notemark/MainActivity.kt | 13 +- .../auth/data/remote/api/LoginService.kt | 10 - .../auth/data/remote/api/LogoutService.kt | 9 - .../data/remote/api/RegistrationService.kt | 9 - .../com/example/notemark/auth/domain/Error.kt | 3 - .../notemark/auth/domain/UserDataValidator.kt | 83 --- .../notemark/auth/domain/model/LoginModel.kt | 9 - .../notemark/auth/presentation/asUiText.kt | 47 -- .../presentation/login/screens/LoginScreen.kt | 292 -------- .../screens/RegistrationScreen.kt | 319 --------- .../presentation/util/DeviceConfiguration.kt | 34 - .../auth/presentation/util/isValidEmail.kt | 3 - .../example/notemark/core/di/AuthModule.kt | 44 -- .../notemark/core/manager/SessionManager.kt | 60 -- .../notemark/main/data/local/NoteDatabase.kt | 14 - .../notemark/main/data/local/SyncDao.kt | 18 - .../notemark/main/data/remote/NoteMappers.kt | 51 -- .../main/data/remote/api/NoteService.kt | 25 - .../notemark/main/domain/model/Note.kt | 19 - .../notemark/main/domain/model/NoteRequest.kt | 15 - .../main/domain/repository/NotesRepository.kt | 28 - .../main/presentation/screens/Profile.kt | 24 - .../main/presentation/screens/Settings.kt | 408 ----------- .../main/presentation/screens/note/Details.kt | 600 ---------------- .../main/presentation/screens/note/Home.kt | 656 ------------------ .../presentation/screens/note/SyncRecord.kt | 22 - .../notemark/navigation/navs/AppNavigation.kt | 53 +- .../notemark/navigation/navs/authNavGraph.kt | 59 +- .../notemark/navigation/navs/homeNavGraph.kt | 86 ++- .../navigation/screens/AuthScreens.kt | 8 +- .../navigation/screens/HomeScreens.kt | 15 +- .../com/example/notemark/ui/theme/Type.kt | 2 +- app/src/main/res/values/strings.xml | 26 - auth/data/build.gradle.kts | 23 +- .../java/com/example/data/di/AuthModule.kt | 66 ++ .../example/data/remote}/LoginServiceImpl.kt | 11 +- .../example/data/remote}/LogoutServiceImpl.kt | 11 +- .../data/remote}/RegistrationServiceImpl.kt | 8 +- auth/domain/build.gradle.kts | 10 +- .../java/com/example/domain/LoginService.kt | 10 + .../java/com/example/domain/LogoutService.kt | 9 + .../com/example/domain/RegistrationService.kt | 9 + .../domain/model/AccessTokenRequest.kt | 6 + .../domain/model/AccessTokenResponse.kt | 6 + .../com/example/domain/model/LoginModel.kt | 6 + .../example}/domain/model/LoginResponse.kt | 5 +- .../domain/model/RegistrationModel.kt | 4 +- .../example}/domain/model/SuccessfulLogin.kt | 2 +- auth/presentation/build.gradle.kts | 22 +- .../landing/screens/LandingScreen.kt | 47 +- .../example/presentation/login/LoginScreen.kt | 136 ++++ .../presentation/login}/LoginViewModel.kt | 16 +- .../login/components/LoginHeaderSection.kt | 28 + .../login/components/LoginSheet.kt | 129 ++++ .../registration/RegistrationScreen.kt | 125 ++++ .../registration}/RegistrationViewModel.kt | 6 +- .../components/RegistrationHeader.kt | 34 + .../components/RegistrationSheet.kt | 169 +++++ .../example}/presentation/vm/AuthViewModel.kt | 10 +- .../src/main/res/drawable/image.png | Bin .../.kotlin/errors/errors-1768472990915.log | 73 ++ build-logic/convention/build.gradle.kts | 4 + .../main/java/AndroidAppConventionPlugin.kt | 4 +- .../java/AndroidLibComposeConventionPlugin.kt | 2 +- .../main/java/KtorClientConventionPlugin.kt | 27 + .../java/com/example/convention/BuildType.kt | 5 + build.gradle.kts | 9 +- core/data/build.gradle.kts | 9 +- core/data/src/main/AndroidManifest.xml | 1 + .../data}/AndroidConnectivityObserver.kt | 4 +- .../main/java/com/example/data/AppModule.kt | 21 + .../java/com/example/data}/ClientFactory.kt | 24 +- .../java/com/example/data}/NetworkModule.kt | 4 +- .../java/com/example/data/SessionManager.kt | 169 +++++ .../java/com/example/data/SessionModule.kt | 22 + core/domain/build.gradle.kts | 2 +- .../example/domain}/ConnectivityObserver.kt | 2 +- .../java/com/example}/domain/DataError.kt | 2 +- .../src/main/java/com/example/domain/Error.kt | 3 + .../java/com/example/domain}/HttpRoutes.kt | 2 +- .../src/main/java/com/example/domain/Note.kt | 9 + .../java/com/example/domain/NoteRequest.kt | 9 + .../main/java/com/example}/domain/Resource.kt | 2 +- .../java/com/example}/domain/RootError.kt | 2 +- .../com/example/domain/SessionRepository.kt | 22 + core/presentation/build.gradle.kts | 2 +- .../presentation}/ConnectivityViewModel.kt | 3 +- .../example/presentation}/DateFormatter.kt | 2 +- .../presentation/DeviceConfiguration.kt | 29 + .../example/presentation}/FormValidation.kt | 2 +- .../presentation/NoteMarkDefaultScreen.kt | 48 ++ .../java/com/example}/presentation/UiText.kt | 2 +- .../java/com/example/presentation/asUiText.kt | 44 ++ .../components/NoteMarkButton.kt | 2 +- .../presentation}/components/NoteMarkLink.kt | 2 +- .../components/NoteMarkTextField.kt | 4 +- .../presentation/getFormattedCreatedAt.kt | 6 + .../com/example/presentation}/getInitials.kt | 4 +- .../com/example/presentation/isValidEmail.kt | 5 + .../example/presentation}/truncateAtWord.kt | 2 +- .../src/main/res/drawable/book_open.xml | 0 .../src/main/res/drawable/check.xml | 0 .../src/main/res/drawable/chevron_right.xml | 0 .../src/main/res/drawable/clock.xml | 0 .../src/main/res/drawable/cloud_off.xml | 0 .../src/main/res/drawable/copy.xml | 0 .../src/main/res/drawable/edit.xml | 0 .../src/main/res/drawable/eye.xml | 0 .../src/main/res/drawable/eye_off.xml | 0 .../src/main/res/drawable/image.png | Bin 0 -> 122562 bytes .../src/main/res/drawable/ios_arrow_left.xml | 0 .../src/main/res/drawable/logo.xml | 0 .../src/main/res/drawable/logout.xml | 0 .../drawable/outline_delete_outline_24.xml | 5 + .../src/main/res/drawable/plus.xml | 0 .../src/main/res/drawable/settings.xml | 0 .../src/main/res/drawable/sync_icon.xml | 0 .../presentation}/src/main/res/drawable/x.xml | 0 .../src/main/res/font/inter_18pt_medium.ttf | Bin .../src/main/res/font/inter_18pt_regular.ttf | Bin .../main/res/font/space_grotesk_regular.ttf | Bin .../src/main/res/raw/success.json | 0 .../src/main/res/values/strings.xml | 30 + gradle/libs.versions.toml | 63 +- note/data/build.gradle.kts | 9 +- .../java/com/example/data/di/NoteModule.kt | 51 +- .../data/local/LocalNoteDataSourceImpl.kt | 17 + .../java/com/example}/data/local/NoteDao.kt | 5 +- .../com/example/data/local/NoteDatabase.kt | 12 + .../com/example}/data/local/NoteEntity.kt | 2 +- .../java/com/example}/data/remote/NoteDTO.kt | 2 +- .../com/example/data/remote/NoteMappers.kt | 101 +++ .../data/remote/NoteRemoteMediator.kt | 22 +- .../com/example/data/remote/NoteRequestDTO.kt | 12 + .../example/data/remote}/NoteServiceImpl.kt | 78 +-- .../com/example/data/remote}/NotesResponse.kt | 3 +- note/domain/build.gradle.kts | 3 +- .../com/example/domain/LocalNoteDataSource.kt | 6 + .../java/com/example/domain/NoteEntity.kt | 9 + .../java/com/example/domain/NoteService.kt | 21 + .../java/com/example/domain/NotesResponse.kt | 8 + note/presentation/build.gradle.kts | 8 +- .../example/presentation}/NoteViewModel.kt | 190 +++-- .../com/example/presentation}/NotesUiState.kt | 4 +- .../com/example/presentation/SyncInterval.kt | 8 + .../presentation/edit_note}/CreateNote.kt | 49 +- .../presentation/edit_note}/EditNote.kt | 48 +- .../com/example/presentation/home/Home.kt | 340 +++++++++ .../home/components/ActionContent.kt | 106 +++ .../home/components/EmptyState.kt | 38 + .../home/components/NoteActionSheet.kt | 78 +++ .../presentation/home/components/NoteItem.kt | 96 +++ .../home/components/SuccessContent.kt | 74 ++ .../note_details}/DetailsViewModel.kt | 2 +- .../presentation/note_details/NoteDetails.kt | 71 ++ .../note_details/components/DetailsContent.kt | 266 +++++++ .../components/ExtendedFabFSheet.kt | 69 ++ .../LandscapeDetailsScreenContent.kt | 267 +++++++ .../presentation/settings/SettingsScreen.kt | 40 ++ .../settings/components/SettingsContent.kt | 111 +++ .../settings/components/SettingsItem.kt | 148 ++++ .../settings/components/SingleItem.kt | 131 ++++ releases/data/build.gradle.kts | 11 +- .../java/com/example/data/ReleasesService.kt | 6 + .../com/example/data/ReleasesServiceImpl.kt | 22 + releases/presentation/build.gradle.kts | 2 +- .../com/example/presentation/UpdateScreen.kt | 17 + 190 files changed, 5593 insertions(+), 3431 deletions(-) create mode 100644 .idea/studiobot.xml create mode 100644 .kotlin/errors/errors-1768372341802.log create mode 100644 .kotlin/errors/errors-1768372341807.log create mode 100644 .kotlin/errors/errors-1768372341810.log create mode 100644 .kotlin/errors/errors-1768372341816.log create mode 100644 .kotlin/errors/errors-1768372402078.log create mode 100644 .kotlin/errors/errors-1768372402083.log create mode 100644 .kotlin/errors/errors-1768372402085.log create mode 100644 .kotlin/errors/errors-1768372402089.log create mode 100644 .kotlin/errors/errors-1768372523043.log create mode 100644 .kotlin/errors/errors-1768372523046.log create mode 100644 .kotlin/errors/errors-1768372523047.log create mode 100644 .kotlin/errors/errors-1768372523050.log create mode 100644 .kotlin/errors/errors-1768372649411.log create mode 100644 .kotlin/errors/errors-1768372649416.log create mode 100644 .kotlin/errors/errors-1768372649421.log create mode 100644 .kotlin/errors/errors-1768372886661.log create mode 100644 .kotlin/errors/errors-1768372886667.log create mode 100644 .kotlin/errors/errors-1768372895329.log create mode 100644 .kotlin/errors/errors-1768372895334.log delete mode 100644 app/src/main/java/com/example/notemark/auth/data/remote/api/LoginService.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/data/remote/api/LogoutService.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/data/remote/api/RegistrationService.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/domain/Error.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/domain/UserDataValidator.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/domain/model/LoginModel.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/presentation/asUiText.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/presentation/login/screens/LoginScreen.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/presentation/registration/screens/RegistrationScreen.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/presentation/util/DeviceConfiguration.kt delete mode 100644 app/src/main/java/com/example/notemark/auth/presentation/util/isValidEmail.kt delete mode 100644 app/src/main/java/com/example/notemark/core/di/AuthModule.kt delete mode 100644 app/src/main/java/com/example/notemark/core/manager/SessionManager.kt delete mode 100644 app/src/main/java/com/example/notemark/main/data/local/NoteDatabase.kt delete mode 100644 app/src/main/java/com/example/notemark/main/data/local/SyncDao.kt delete mode 100644 app/src/main/java/com/example/notemark/main/data/remote/NoteMappers.kt delete mode 100644 app/src/main/java/com/example/notemark/main/data/remote/api/NoteService.kt delete mode 100644 app/src/main/java/com/example/notemark/main/domain/model/Note.kt delete mode 100644 app/src/main/java/com/example/notemark/main/domain/model/NoteRequest.kt delete mode 100644 app/src/main/java/com/example/notemark/main/domain/repository/NotesRepository.kt delete mode 100644 app/src/main/java/com/example/notemark/main/presentation/screens/Profile.kt delete mode 100644 app/src/main/java/com/example/notemark/main/presentation/screens/Settings.kt delete mode 100644 app/src/main/java/com/example/notemark/main/presentation/screens/note/Details.kt delete mode 100644 app/src/main/java/com/example/notemark/main/presentation/screens/note/Home.kt delete mode 100644 app/src/main/java/com/example/notemark/main/presentation/screens/note/SyncRecord.kt create mode 100644 auth/data/src/main/java/com/example/data/di/AuthModule.kt rename {app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl => auth/data/src/main/java/com/example/data/remote}/LoginServiceImpl.kt (76%) rename {app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl => auth/data/src/main/java/com/example/data/remote}/LogoutServiceImpl.kt (76%) rename {app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl => auth/data/src/main/java/com/example/data/remote}/RegistrationServiceImpl.kt (86%) create mode 100644 auth/domain/src/main/java/com/example/domain/LoginService.kt create mode 100644 auth/domain/src/main/java/com/example/domain/LogoutService.kt create mode 100644 auth/domain/src/main/java/com/example/domain/RegistrationService.kt create mode 100644 auth/domain/src/main/java/com/example/domain/model/AccessTokenRequest.kt create mode 100644 auth/domain/src/main/java/com/example/domain/model/AccessTokenResponse.kt create mode 100644 auth/domain/src/main/java/com/example/domain/model/LoginModel.kt rename {app/src/main/java/com/example/notemark/auth => auth/domain/src/main/java/com/example}/domain/model/LoginResponse.kt (52%) rename {app/src/main/java/com/example/notemark/auth => auth/domain/src/main/java/com/example}/domain/model/RegistrationModel.kt (51%) rename {app/src/main/java/com/example/notemark/auth => auth/domain/src/main/java/com/example}/domain/model/SuccessfulLogin.kt (65%) rename {app/src/main/java/com/example/notemark/auth => auth/presentation/src/main/java/com/example}/presentation/landing/screens/LandingScreen.kt (80%) create mode 100644 auth/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt rename {app/src/main/java/com/example/notemark/auth/presentation/login/vm => auth/presentation/src/main/java/com/example/presentation/login}/LoginViewModel.kt (87%) create mode 100644 auth/presentation/src/main/java/com/example/presentation/login/components/LoginHeaderSection.kt create mode 100644 auth/presentation/src/main/java/com/example/presentation/login/components/LoginSheet.kt create mode 100644 auth/presentation/src/main/java/com/example/presentation/registration/RegistrationScreen.kt rename {app/src/main/java/com/example/notemark/auth/presentation/registration/vm => auth/presentation/src/main/java/com/example/presentation/registration}/RegistrationViewModel.kt (90%) create mode 100644 auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationHeader.kt create mode 100644 auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationSheet.kt rename {app/src/main/java/com/example/notemark/auth => auth/presentation/src/main/java/com/example}/presentation/vm/AuthViewModel.kt (81%) rename {app => auth/presentation}/src/main/res/drawable/image.png (100%) create mode 100644 build-logic/.kotlin/errors/errors-1768472990915.log create mode 100644 build-logic/convention/src/main/java/KtorClientConventionPlugin.kt rename {app/src/main/java/com/example/notemark => core/data/src/main/java/com/example/data}/AndroidConnectivityObserver.kt (96%) create mode 100644 core/data/src/main/java/com/example/data/AppModule.kt rename {app/src/main/java/com/example/notemark/core/factory => core/data/src/main/java/com/example/data}/ClientFactory.kt (91%) rename {app/src/main/java/com/example/notemark/core/di => core/data/src/main/java/com/example/data}/NetworkModule.kt (79%) create mode 100644 core/data/src/main/java/com/example/data/SessionManager.kt create mode 100644 core/data/src/main/java/com/example/data/SessionModule.kt rename {app/src/main/java/com/example/notemark => core/domain/src/main/java/com/example/domain}/ConnectivityObserver.kt (93%) rename {app/src/main/java/com/example/notemark/auth => core/domain/src/main/java/com/example}/domain/DataError.kt (91%) create mode 100644 core/domain/src/main/java/com/example/domain/Error.kt rename {app/src/main/java/com/example/notemark/core => core/domain/src/main/java/com/example/domain}/HttpRoutes.kt (92%) create mode 100644 core/domain/src/main/java/com/example/domain/Note.kt create mode 100644 core/domain/src/main/java/com/example/domain/NoteRequest.kt rename {app/src/main/java/com/example/notemark/auth => core/domain/src/main/java/com/example}/domain/Resource.kt (83%) rename {app/src/main/java/com/example/notemark/auth => core/domain/src/main/java/com/example}/domain/RootError.kt (84%) create mode 100644 core/domain/src/main/java/com/example/domain/SessionRepository.kt rename {app/src/main/java/com/example/notemark => core/presentation/src/main/java/com/example/presentation}/ConnectivityViewModel.kt (85%) rename {app/src/main/java/com/example/notemark/main => core/presentation/src/main/java/com/example/presentation}/DateFormatter.kt (98%) create mode 100644 core/presentation/src/main/java/com/example/presentation/DeviceConfiguration.kt rename {app/src/main/java/com/example/notemark/auth/presentation/util => core/presentation/src/main/java/com/example/presentation}/FormValidation.kt (97%) create mode 100644 core/presentation/src/main/java/com/example/presentation/NoteMarkDefaultScreen.kt rename {app/src/main/java/com/example/notemark/auth => core/presentation/src/main/java/com/example}/presentation/UiText.kt (94%) create mode 100644 core/presentation/src/main/java/com/example/presentation/asUiText.kt rename {app/src/main/java/com/example/notemark/core => core/presentation/src/main/java/com/example/presentation}/components/NoteMarkButton.kt (96%) rename {app/src/main/java/com/example/notemark/core => core/presentation/src/main/java/com/example/presentation}/components/NoteMarkLink.kt (93%) rename {app/src/main/java/com/example/notemark/core => core/presentation/src/main/java/com/example/presentation}/components/NoteMarkTextField.kt (98%) create mode 100644 core/presentation/src/main/java/com/example/presentation/getFormattedCreatedAt.kt rename {app/src/main/java/com/example/notemark/core => core/presentation/src/main/java/com/example/presentation}/getInitials.kt (85%) create mode 100644 core/presentation/src/main/java/com/example/presentation/isValidEmail.kt rename {app/src/main/java/com/example/notemark/core => core/presentation/src/main/java/com/example/presentation}/truncateAtWord.kt (90%) rename {app => core/presentation}/src/main/res/drawable/book_open.xml (100%) rename {app => core/presentation}/src/main/res/drawable/check.xml (100%) rename {app => core/presentation}/src/main/res/drawable/chevron_right.xml (100%) rename {app => core/presentation}/src/main/res/drawable/clock.xml (100%) rename {app => core/presentation}/src/main/res/drawable/cloud_off.xml (100%) rename {app => core/presentation}/src/main/res/drawable/copy.xml (100%) rename {app => core/presentation}/src/main/res/drawable/edit.xml (100%) rename {app => core/presentation}/src/main/res/drawable/eye.xml (100%) rename {app => core/presentation}/src/main/res/drawable/eye_off.xml (100%) create mode 100644 core/presentation/src/main/res/drawable/image.png rename {app => core/presentation}/src/main/res/drawable/ios_arrow_left.xml (100%) rename {app => core/presentation}/src/main/res/drawable/logo.xml (100%) rename {app => core/presentation}/src/main/res/drawable/logout.xml (100%) create mode 100644 core/presentation/src/main/res/drawable/outline_delete_outline_24.xml rename {app => core/presentation}/src/main/res/drawable/plus.xml (100%) rename {app => core/presentation}/src/main/res/drawable/settings.xml (100%) rename {app => core/presentation}/src/main/res/drawable/sync_icon.xml (100%) rename {app => core/presentation}/src/main/res/drawable/x.xml (100%) rename {app => core/presentation}/src/main/res/font/inter_18pt_medium.ttf (100%) rename {app => core/presentation}/src/main/res/font/inter_18pt_regular.ttf (100%) rename {app => core/presentation}/src/main/res/font/space_grotesk_regular.ttf (100%) rename {app => core/presentation}/src/main/res/raw/success.json (100%) create mode 100644 core/presentation/src/main/res/values/strings.xml rename app/src/main/java/com/example/notemark/core/di/AppModule.kt => note/data/src/main/java/com/example/data/di/NoteModule.kt (65%) create mode 100644 note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt rename {app/src/main/java/com/example/notemark/main => note/data/src/main/java/com/example}/data/local/NoteDao.kt (76%) create mode 100644 note/data/src/main/java/com/example/data/local/NoteDatabase.kt rename {app/src/main/java/com/example/notemark/main => note/data/src/main/java/com/example}/data/local/NoteEntity.kt (89%) rename {app/src/main/java/com/example/notemark/main => note/data/src/main/java/com/example}/data/remote/NoteDTO.kt (85%) create mode 100644 note/data/src/main/java/com/example/data/remote/NoteMappers.kt rename {app/src/main/java/com/example/notemark/main => note/data/src/main/java/com/example}/data/remote/NoteRemoteMediator.kt (81%) create mode 100644 note/data/src/main/java/com/example/data/remote/NoteRequestDTO.kt rename {app/src/main/java/com/example/notemark/main/data/remote/repositoryImpl => note/data/src/main/java/com/example/data/remote}/NoteServiceImpl.kt (55%) rename {app/src/main/java/com/example/notemark/main/domain/model => note/data/src/main/java/com/example/data/remote}/NotesResponse.kt (68%) create mode 100644 note/domain/src/main/java/com/example/domain/LocalNoteDataSource.kt create mode 100644 note/domain/src/main/java/com/example/domain/NoteEntity.kt create mode 100644 note/domain/src/main/java/com/example/domain/NoteService.kt create mode 100644 note/domain/src/main/java/com/example/domain/NotesResponse.kt rename {app/src/main/java/com/example/notemark/main/presentation/vm => note/presentation/src/main/java/com/example/presentation}/NoteViewModel.kt (57%) rename {app/src/main/java/com/example/notemark/main/domain/model => note/presentation/src/main/java/com/example/presentation}/NotesUiState.kt (75%) create mode 100644 note/presentation/src/main/java/com/example/presentation/SyncInterval.kt rename {app/src/main/java/com/example/notemark/main/presentation/screens/note => note/presentation/src/main/java/com/example/presentation/edit_note}/CreateNote.kt (86%) rename {app/src/main/java/com/example/notemark/main/presentation/screens/note => note/presentation/src/main/java/com/example/presentation/edit_note}/EditNote.kt (90%) create mode 100644 note/presentation/src/main/java/com/example/presentation/home/Home.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/home/components/ActionContent.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/home/components/EmptyState.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/home/components/NoteActionSheet.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/home/components/NoteItem.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/home/components/SuccessContent.kt rename {app/src/main/java/com/example/notemark/main/presentation/vm => note/presentation/src/main/java/com/example/presentation/note_details}/DetailsViewModel.kt (98%) create mode 100644 note/presentation/src/main/java/com/example/presentation/note_details/NoteDetails.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/note_details/components/DetailsContent.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/note_details/components/ExtendedFabFSheet.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/note_details/components/LandscapeDetailsScreenContent.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/settings/SettingsScreen.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/settings/components/SettingsContent.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/settings/components/SettingsItem.kt create mode 100644 note/presentation/src/main/java/com/example/presentation/settings/components/SingleItem.kt create mode 100644 releases/data/src/main/java/com/example/data/ReleasesService.kt create mode 100644 releases/data/src/main/java/com/example/data/ReleasesServiceImpl.kt create mode 100644 releases/presentation/src/main/java/com/example/presentation/UpdateScreen.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 1afa7f2..254a1fc 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 7146cb1..d852bbf 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml new file mode 100644 index 0000000..539e3b8 --- /dev/null +++ b/.idea/studiobot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.kotlin/errors/errors-1768372341802.log b/.kotlin/errors/errors-1768372341802.log new file mode 100644 index 0000000..c35eeee --- /dev/null +++ b/.kotlin/errors/errors-1768372341802.log @@ -0,0 +1,114 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt:20:29: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirAnonymousFunctionImpl.acceptChildren(FirAnonymousFunctionImpl.kt:70) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunction(AbstractDiagnosticCollectorVisitor.kt:141) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:134) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression.accept(FirAnonymousFunctionExpression.kt:30) + at org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList.acceptChildren(FirResolvedArgumentList.kt:35) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirVisitor.visitArgumentList(FirVisitor.kt:81) + at org.jetbrains.kotlin.fir.expressions.FirArgumentList.accept(FirArgumentList.kt:25) + at org.jetbrains.kotlin.fir.expressions.impl.FirFunctionCallImpl.acceptChildren(FirFunctionCallImpl.kt:55) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:49) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithCallOrAssignment(AbstractDiagnosticCollectorVisitor.kt:346) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:255) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirFunctionCall.accept(FirFunctionCall.kt:41) + at org.jetbrains.kotlin.fir.expressions.impl.FirReturnExpressionImpl.acceptChildren(FirReturnExpressionImpl.kt:41) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitJump(FirDefaultVisitor.kt:54) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitReturnExpression(FirDefaultVisitor.kt:66) + at org.jetbrains.kotlin.fir.expressions.FirReturnExpression.accept(FirReturnExpression.kt:31) + at org.jetbrains.kotlin.fir.expressions.impl.FirBlockImpl.acceptChildren(FirBlockImpl.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:209) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirBlock.accept(FirBlock.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:68) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372341807.log b/.kotlin/errors/errors-1768372341807.log new file mode 100644 index 0000000..c53ba2e --- /dev/null +++ b/.kotlin/errors/errors-1768372341807.log @@ -0,0 +1,69 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/presentation/src/main/java/com/example/presentation/NoteMarkDefaultScreen.kt:16:1: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372341810.log b/.kotlin/errors/errors-1768372341810.log new file mode 100644 index 0000000..6b923b1 --- /dev/null +++ b/.kotlin/errors/errors-1768372341810.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt:10:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372341816.log b/.kotlin/errors/errors-1768372341816.log new file mode 100644 index 0000000..b01bbfd --- /dev/null +++ b/.kotlin/errors/errors-1768372341816.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/releases/data/src/main/java/com/example/data/ReleasesService.kt:5:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372402078.log b/.kotlin/errors/errors-1768372402078.log new file mode 100644 index 0000000..c35eeee --- /dev/null +++ b/.kotlin/errors/errors-1768372402078.log @@ -0,0 +1,114 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt:20:29: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirAnonymousFunctionImpl.acceptChildren(FirAnonymousFunctionImpl.kt:70) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunction(AbstractDiagnosticCollectorVisitor.kt:141) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:134) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression.accept(FirAnonymousFunctionExpression.kt:30) + at org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList.acceptChildren(FirResolvedArgumentList.kt:35) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirVisitor.visitArgumentList(FirVisitor.kt:81) + at org.jetbrains.kotlin.fir.expressions.FirArgumentList.accept(FirArgumentList.kt:25) + at org.jetbrains.kotlin.fir.expressions.impl.FirFunctionCallImpl.acceptChildren(FirFunctionCallImpl.kt:55) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:49) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithCallOrAssignment(AbstractDiagnosticCollectorVisitor.kt:346) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:255) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirFunctionCall.accept(FirFunctionCall.kt:41) + at org.jetbrains.kotlin.fir.expressions.impl.FirReturnExpressionImpl.acceptChildren(FirReturnExpressionImpl.kt:41) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitJump(FirDefaultVisitor.kt:54) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitReturnExpression(FirDefaultVisitor.kt:66) + at org.jetbrains.kotlin.fir.expressions.FirReturnExpression.accept(FirReturnExpression.kt:31) + at org.jetbrains.kotlin.fir.expressions.impl.FirBlockImpl.acceptChildren(FirBlockImpl.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:209) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirBlock.accept(FirBlock.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:68) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372402083.log b/.kotlin/errors/errors-1768372402083.log new file mode 100644 index 0000000..c53ba2e --- /dev/null +++ b/.kotlin/errors/errors-1768372402083.log @@ -0,0 +1,69 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/presentation/src/main/java/com/example/presentation/NoteMarkDefaultScreen.kt:16:1: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372402085.log b/.kotlin/errors/errors-1768372402085.log new file mode 100644 index 0000000..6b923b1 --- /dev/null +++ b/.kotlin/errors/errors-1768372402085.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt:10:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372402089.log b/.kotlin/errors/errors-1768372402089.log new file mode 100644 index 0000000..b01bbfd --- /dev/null +++ b/.kotlin/errors/errors-1768372402089.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/releases/data/src/main/java/com/example/data/ReleasesService.kt:5:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372523043.log b/.kotlin/errors/errors-1768372523043.log new file mode 100644 index 0000000..c35eeee --- /dev/null +++ b/.kotlin/errors/errors-1768372523043.log @@ -0,0 +1,114 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt:20:29: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirAnonymousFunctionImpl.acceptChildren(FirAnonymousFunctionImpl.kt:70) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunction(AbstractDiagnosticCollectorVisitor.kt:141) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:134) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression.accept(FirAnonymousFunctionExpression.kt:30) + at org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList.acceptChildren(FirResolvedArgumentList.kt:35) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirVisitor.visitArgumentList(FirVisitor.kt:81) + at org.jetbrains.kotlin.fir.expressions.FirArgumentList.accept(FirArgumentList.kt:25) + at org.jetbrains.kotlin.fir.expressions.impl.FirFunctionCallImpl.acceptChildren(FirFunctionCallImpl.kt:55) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:49) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithCallOrAssignment(AbstractDiagnosticCollectorVisitor.kt:346) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:255) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirFunctionCall.accept(FirFunctionCall.kt:41) + at org.jetbrains.kotlin.fir.expressions.impl.FirReturnExpressionImpl.acceptChildren(FirReturnExpressionImpl.kt:41) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitJump(FirDefaultVisitor.kt:54) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitReturnExpression(FirDefaultVisitor.kt:66) + at org.jetbrains.kotlin.fir.expressions.FirReturnExpression.accept(FirReturnExpression.kt:31) + at org.jetbrains.kotlin.fir.expressions.impl.FirBlockImpl.acceptChildren(FirBlockImpl.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:209) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirBlock.accept(FirBlock.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:68) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372523046.log b/.kotlin/errors/errors-1768372523046.log new file mode 100644 index 0000000..c53ba2e --- /dev/null +++ b/.kotlin/errors/errors-1768372523046.log @@ -0,0 +1,69 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/presentation/src/main/java/com/example/presentation/NoteMarkDefaultScreen.kt:16:1: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372523047.log b/.kotlin/errors/errors-1768372523047.log new file mode 100644 index 0000000..6b923b1 --- /dev/null +++ b/.kotlin/errors/errors-1768372523047.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt:10:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372523050.log b/.kotlin/errors/errors-1768372523050.log new file mode 100644 index 0000000..b01bbfd --- /dev/null +++ b/.kotlin/errors/errors-1768372523050.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/releases/data/src/main/java/com/example/data/ReleasesService.kt:5:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372649411.log b/.kotlin/errors/errors-1768372649411.log new file mode 100644 index 0000000..c35eeee --- /dev/null +++ b/.kotlin/errors/errors-1768372649411.log @@ -0,0 +1,114 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt:20:29: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirAnonymousFunctionImpl.acceptChildren(FirAnonymousFunctionImpl.kt:70) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunction(AbstractDiagnosticCollectorVisitor.kt:141) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:134) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression.accept(FirAnonymousFunctionExpression.kt:30) + at org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList.acceptChildren(FirResolvedArgumentList.kt:35) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirVisitor.visitArgumentList(FirVisitor.kt:81) + at org.jetbrains.kotlin.fir.expressions.FirArgumentList.accept(FirArgumentList.kt:25) + at org.jetbrains.kotlin.fir.expressions.impl.FirFunctionCallImpl.acceptChildren(FirFunctionCallImpl.kt:55) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:49) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithCallOrAssignment(AbstractDiagnosticCollectorVisitor.kt:346) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:255) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirFunctionCall.accept(FirFunctionCall.kt:41) + at org.jetbrains.kotlin.fir.expressions.impl.FirReturnExpressionImpl.acceptChildren(FirReturnExpressionImpl.kt:41) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitJump(FirDefaultVisitor.kt:54) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitReturnExpression(FirDefaultVisitor.kt:66) + at org.jetbrains.kotlin.fir.expressions.FirReturnExpression.accept(FirReturnExpression.kt:31) + at org.jetbrains.kotlin.fir.expressions.impl.FirBlockImpl.acceptChildren(FirBlockImpl.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:209) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirBlock.accept(FirBlock.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:68) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372649416.log b/.kotlin/errors/errors-1768372649416.log new file mode 100644 index 0000000..6b923b1 --- /dev/null +++ b/.kotlin/errors/errors-1768372649416.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt:10:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372649421.log b/.kotlin/errors/errors-1768372649421.log new file mode 100644 index 0000000..b01bbfd --- /dev/null +++ b/.kotlin/errors/errors-1768372649421.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/releases/data/src/main/java/com/example/data/ReleasesService.kt:5:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372886661.log b/.kotlin/errors/errors-1768372886661.log new file mode 100644 index 0000000..c35eeee --- /dev/null +++ b/.kotlin/errors/errors-1768372886661.log @@ -0,0 +1,114 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt:20:29: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirAnonymousFunctionImpl.acceptChildren(FirAnonymousFunctionImpl.kt:70) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunction(AbstractDiagnosticCollectorVisitor.kt:141) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:134) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression.accept(FirAnonymousFunctionExpression.kt:30) + at org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList.acceptChildren(FirResolvedArgumentList.kt:35) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirVisitor.visitArgumentList(FirVisitor.kt:81) + at org.jetbrains.kotlin.fir.expressions.FirArgumentList.accept(FirArgumentList.kt:25) + at org.jetbrains.kotlin.fir.expressions.impl.FirFunctionCallImpl.acceptChildren(FirFunctionCallImpl.kt:55) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:49) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithCallOrAssignment(AbstractDiagnosticCollectorVisitor.kt:346) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:255) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirFunctionCall.accept(FirFunctionCall.kt:41) + at org.jetbrains.kotlin.fir.expressions.impl.FirReturnExpressionImpl.acceptChildren(FirReturnExpressionImpl.kt:41) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitJump(FirDefaultVisitor.kt:54) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitReturnExpression(FirDefaultVisitor.kt:66) + at org.jetbrains.kotlin.fir.expressions.FirReturnExpression.accept(FirReturnExpression.kt:31) + at org.jetbrains.kotlin.fir.expressions.impl.FirBlockImpl.acceptChildren(FirBlockImpl.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:209) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirBlock.accept(FirBlock.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:68) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372886667.log b/.kotlin/errors/errors-1768372886667.log new file mode 100644 index 0000000..6b923b1 --- /dev/null +++ b/.kotlin/errors/errors-1768372886667.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt:10:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372895329.log b/.kotlin/errors/errors-1768372895329.log new file mode 100644 index 0000000..c35eeee --- /dev/null +++ b/.kotlin/errors/errors-1768372895329.log @@ -0,0 +1,114 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt:20:29: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirAnonymousFunctionImpl.acceptChildren(FirAnonymousFunctionImpl.kt:70) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunction(AbstractDiagnosticCollectorVisitor.kt:141) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:134) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnonymousFunctionExpression(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression.accept(FirAnonymousFunctionExpression.kt:30) + at org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList.acceptChildren(FirResolvedArgumentList.kt:35) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirVisitor.visitArgumentList(FirVisitor.kt:81) + at org.jetbrains.kotlin.fir.expressions.FirArgumentList.accept(FirArgumentList.kt:25) + at org.jetbrains.kotlin.fir.expressions.impl.FirFunctionCallImpl.acceptChildren(FirFunctionCallImpl.kt:55) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitElement(AbstractDiagnosticCollectorVisitor.kt:49) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithCallOrAssignment(AbstractDiagnosticCollectorVisitor.kt:346) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:255) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFunctionCall(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirFunctionCall.accept(FirFunctionCall.kt:41) + at org.jetbrains.kotlin.fir.expressions.impl.FirReturnExpressionImpl.acceptChildren(FirReturnExpressionImpl.kt:41) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitJump(FirDefaultVisitor.kt:54) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitReturnExpression(FirDefaultVisitor.kt:66) + at org.jetbrains.kotlin.fir.expressions.FirReturnExpression.accept(FirReturnExpression.kt:31) + at org.jetbrains.kotlin.fir.expressions.impl.FirBlockImpl.acceptChildren(FirBlockImpl.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:61) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitAnnotationContainer(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitStatement(FirDefaultVisitor.kt:33) + at org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor.visitExpression(FirDefaultVisitor.kt:36) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:209) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitBlock(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.expressions.FirBlock.accept(FirBlock.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:68) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/.kotlin/errors/errors-1768372895334.log b/.kotlin/errors/errors-1768372895334.log new file mode 100644 index 0000000..6b923b1 --- /dev/null +++ b/.kotlin/errors/errors-1768372895334.log @@ -0,0 +1,76 @@ +kotlin version: 2.0.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /Users/invoke/AndroidStudioProjects/NoteMark/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt:10:5: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319) + at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.lang.IllegalArgumentException: source must not be null + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39) + at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50) + at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42) + at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53) + at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30) + at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42) + at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34) + ... 33 more + + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 57efd81..e2c6014 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,68 +1,77 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.kotlin.compose) - id("com.google.devtools.ksp") version "2.2.10-2.0.2" - id("dagger.hilt.android.plugin") - id("kotlinx-serialization") + alias(libs.plugins.notemark.android.application) + id("com.google.dagger.hilt.android") + alias(libs.plugins.ksp) } android { namespace = "com.example.notemark" - compileSdk = 35 defaultConfig { - applicationId = "com.example.notemark" - minSdk = 30 - targetSdk = 35 - versionCode = 1 - versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } - - - - buildTypes { - debug { - buildConfigField ("String", "BASE_URL", "\"https://notemark.pl-coding.com/\"") - } - release { - isMinifyEnabled = false - buildConfigField ("String", "BASE_URL", "\"https://notemark.pl-coding.com/\"") - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - kotlin { - compilerOptions { - jvmTarget = JvmTarget.fromTarget("17") - } - } - buildFeatures { - compose = true - buildConfig = true - } } dependencies { - + // --- Android Core & Lifecycle --- implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.core.splashscreen) + + // --- Compose UI Layer --- implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.material3.adaptive) + implementation(libs.lottie.compose) + + // --- Internal Modules (Modularized Architecture) --- + // Auth + implementation(projects.auth.data) + implementation(projects.auth.domain) + implementation(projects.auth.presentation) + // Core + implementation(projects.core.data) + implementation(projects.core.domain) + implementation(projects.core.presentation) + // Note + implementation(projects.note.data) + implementation(projects.note.domain) + implementation(projects.note.presentation) + // Releases + implementation(projects.releases.data) + implementation(projects.releases.domain) + implementation(projects.releases.presentation) + + // --- Navigation --- + implementation(libs.navigation.compose) + implementation(libs.kotlinx.serialization.json) + + // --- Dependency Injection (Hilt) --- + implementation(libs.hilt.android) + implementation(libs.hilt.navigation.compose) + ksp(libs.hilt.android.compiler) + ksp(libs.hilt.compiler) + + // --- Networking (Ktor) --- + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.android) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization) + + // --- Paging & Persistence --- + implementation(libs.androidx.paging.compose) + implementation(libs.androidx.room.paging) + + // --- Testing --- testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) @@ -70,6 +79,7 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + with(projects) { with(auth) { implementation(data) @@ -92,45 +102,4 @@ dependencies { implementation(presentation) } } - - // splash screen - implementation(libs.androidx.core.splashscreen) - - // icons extension - implementation(libs.androidx.material.icons.extended) - - // type-safe navigation - implementation(libs.navigation.compose) - implementation(libs.kotlinx.serialization.json) - - // adaptive-layout - implementation(libs.material3.adaptive) - - // ktor - implementation (libs.ktor.client.core) - implementation (libs.ktor.client.android) - implementation (libs.ktor.serialization) - implementation(libs.ktor.client.auth) - implementation(libs.ktor.client.content.negotiation) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.cio) - implementation(libs.ktor.client.logging) - - // hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - ksp(libs.hilt.compiler) - - // paging - implementation(libs.androidx.paging.runtime.ktx) - implementation(libs.androidx.paging.compose) - - // room - implementation (libs.androidx.room.paging) - - // lottie files - implementation(libs.lottie.compose) - - implementation(libs.androidx.lifecycle.runtime.compose) - implementation(libs.androidx.lifecycle.viewmodel.compose) } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/MainActivity.kt b/app/src/main/java/com/example/notemark/MainActivity.kt index c6fbb72..d08073a 100644 --- a/app/src/main/java/com/example/notemark/MainActivity.kt +++ b/app/src/main/java/com/example/notemark/MainActivity.kt @@ -2,13 +2,8 @@ package com.example.notemark import android.os.Bundle import androidx.activity.ComponentActivity -import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.navigation.NavHostController @@ -24,13 +19,7 @@ class MainActivity : ComponentActivity() { installSplashScreen() super.onCreate(savedInstanceState) - enableEdgeToEdge( - statusBarStyle = SystemBarStyle.light( - Color.Black.hashCode(), - Color.Black.hashCode() - ) - ) - + enableEdgeToEdge() WindowCompat.setDecorFitsSystemWindows(window, false) setContent { diff --git a/app/src/main/java/com/example/notemark/auth/data/remote/api/LoginService.kt b/app/src/main/java/com/example/notemark/auth/data/remote/api/LoginService.kt deleted file mode 100644 index c6a075f..0000000 --- a/app/src/main/java/com/example/notemark/auth/data/remote/api/LoginService.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.notemark.auth.data.remote.api - -import com.example.notemark.auth.domain.model.LoginModel -import com.example.notemark.auth.domain.model.LoginResponse - -interface LoginService { - - suspend fun login(body: LoginModel): LoginResponse? - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/data/remote/api/LogoutService.kt b/app/src/main/java/com/example/notemark/auth/data/remote/api/LogoutService.kt deleted file mode 100644 index def0d23..0000000 --- a/app/src/main/java/com/example/notemark/auth/data/remote/api/LogoutService.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.notemark.auth.data.remote.api - -import com.example.notemark.core.factory.AccessTokenResponse - -interface LogoutService { - - suspend fun logout(refreshToken: String): AccessTokenResponse? - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/data/remote/api/RegistrationService.kt b/app/src/main/java/com/example/notemark/auth/data/remote/api/RegistrationService.kt deleted file mode 100644 index 64b43ee..0000000 --- a/app/src/main/java/com/example/notemark/auth/data/remote/api/RegistrationService.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.notemark.auth.data.remote.api - -import com.example.notemark.auth.domain.model.RegistrationModel - -interface RegistrationService { - - suspend fun register(body: RegistrationModel): RegistrationModel? - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/domain/Error.kt b/app/src/main/java/com/example/notemark/auth/domain/Error.kt deleted file mode 100644 index 9679a1f..0000000 --- a/app/src/main/java/com/example/notemark/auth/domain/Error.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.example.notemark.auth.domain - -sealed interface Error \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/domain/UserDataValidator.kt b/app/src/main/java/com/example/notemark/auth/domain/UserDataValidator.kt deleted file mode 100644 index 2ae252e..0000000 --- a/app/src/main/java/com/example/notemark/auth/domain/UserDataValidator.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.example.notemark.auth.domain - -import android.util.Patterns -import com.example.notemark.auth.presentation.util.FormValidation -import com.example.notemark.auth.presentation.util.ValidationResult - -class UserDataValidator { - - fun validatePassword(password: String): Result { - if(password.length < 9) { - return Result.Error(PasswordError.TOO_SHORT) - } - - val hasUppercaseChar = password.any { it.isUpperCase() } - if(!hasUppercaseChar) { - return Result.Error(PasswordError.NO_UPPERCASE) - } - - val hasDigit = password.any { it.isDigit() } - if(!hasDigit) { - return Result.Error(PasswordError.NO_DIGIT) - } - - return Result.Success(Unit) - } - - fun validateEmail(email: String): Result { - val emailPattern = Patterns.EMAIL_ADDRESS.matcher(email).matches() - return if (emailPattern) { - Result.Success(Unit) - } else { - Result.Error(EmailError.INVALID_FORMAT) - } - } - - fun validateUsername(username: String): Result { - if(username.length < 3) { - return Result.Error(UsernameError.TOO_SHORT) - } - if(username.length > 20) { - return Result.Error(UsernameError.TOO_LONG) - } - return Result.Success(Unit) - } - - fun validateRepeatPassword(password: String, repeatPassword: String): Result { - if(password != repeatPassword) { - return Result.Error(PasswordMatchError.PASSWORDS_DO_NOT_MATCH) - } - return Result.Success(Unit) - } - - fun isFormValid( - username: String, - email: String, - password: String, - repeatPassword: String - ): Boolean { - return FormValidation.validateUsername(username) is ValidationResult.Valid && - FormValidation.validateEmail(email) is ValidationResult.Valid && - FormValidation.validatePassword(password) is ValidationResult.Valid && - FormValidation.validateRepeatPassword(password, repeatPassword) is ValidationResult.Valid - } - - enum class PasswordError: Error { - TOO_SHORT, - NO_UPPERCASE, - NO_DIGIT - } - - enum class EmailError: Error { - INVALID_FORMAT, - } - - enum class UsernameError: Error { - TOO_SHORT, - TOO_LONG - } - - enum class PasswordMatchError : Error { - PASSWORDS_DO_NOT_MATCH - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/domain/model/LoginModel.kt b/app/src/main/java/com/example/notemark/auth/domain/model/LoginModel.kt deleted file mode 100644 index 4e9c51c..0000000 --- a/app/src/main/java/com/example/notemark/auth/domain/model/LoginModel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.notemark.auth.domain.model - -import kotlinx.serialization.Serializable - -@Serializable -data class LoginModel( - val email: String, - val password: String -) \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/asUiText.kt b/app/src/main/java/com/example/notemark/auth/presentation/asUiText.kt deleted file mode 100644 index a4f4382..0000000 --- a/app/src/main/java/com/example/notemark/auth/presentation/asUiText.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.example.notemark.auth.presentation - -import androidx.annotation.StringRes -import com.example.notemark.R -import com.example.notemark.auth.domain.DataError -import com.example.notemark.auth.domain.Result -import com.example.notemark.auth.presentation.UiText.StringResource - -fun DataError.asUiText(): UiText { - return when (this) { - DataError.Network.REQUEST_TIMEOUT -> StringResource( - R.string.the_request_timed_out - ) - - DataError.Network.TOO_MANY_REQUESTS -> StringResource( - R.string.youve_hit_your_rate_limit - ) - - DataError.Network.NO_INTERNET -> StringResource( - R.string.no_internet - ) - - DataError.Network.SERVER_ERROR -> StringResource( - R.string.server_error - ) - - DataError.Network.UNKNOWN -> StringResource( - R.string.unknown_error - ) - - DataError.Local.DISK_FULL -> StringResource( - R.string.disk_full - ) - - DataError.ValidationErrors.INVALID_FORMAT -> StringResource( - R.string.invalid_format - ) - - DataError.Note.NOTE_IS_NOT_DELETED -> StringResource( - R.string.note_is_not_deleted - ) - } -} - -fun Result.Error<*, DataError>.asErrorUiText(): UiText { - return error.asUiText() -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/login/screens/LoginScreen.kt b/app/src/main/java/com/example/notemark/auth/presentation/login/screens/LoginScreen.kt deleted file mode 100644 index 8f4dda7..0000000 --- a/app/src/main/java/com/example/notemark/auth/presentation/login/screens/LoginScreen.kt +++ /dev/null @@ -1,292 +0,0 @@ -package com.example.notemark.auth.presentation.login.screens - -import android.util.Log -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.WindowInsets -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.Text -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -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.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusDirection -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import com.example.notemark.auth.presentation.login.vm.LoginState -import com.example.notemark.auth.presentation.login.vm.LoginViewModel -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.auth.presentation.util.isValidEmail -import com.example.notemark.auth.presentation.vm.AuthViewModel -import com.example.notemark.core.components.NoteMarkButton -import com.example.notemark.core.components.NoteMarkLink -import com.example.notemark.core.components.NoteMarkTextField -import com.example.notemark.core.manager.SessionManager -import com.example.notemark.navigation.screens.AuthScreens -import com.example.notemark.navigation.screens.HomeScreens -import kotlinx.coroutines.launch - -@Composable -fun LoginScreen( - navController: NavHostController = rememberNavController(), -) { - val snackbarHostState = remember { SnackbarHostState() } - - Scaffold( - snackbarHost = { - SnackbarHost(hostState = snackbarHostState) - }, - modifier = Modifier.fillMaxSize(), - contentWindowInsets = WindowInsets.statusBars - ) { innerPadding -> - - val rootModifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .clip( - RoundedCornerShape( - topStart = 15.dp, - topEnd = 15.dp - ) - ) - .background(MaterialTheme.colorScheme.surfaceContainerLowest) - .padding( - horizontal = 16.dp, - vertical = 24.dp - ) - .consumeWindowInsets(WindowInsets.navigationBars) - - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) - - when(deviceConfiguration) { - DeviceConfiguration.MOBILE_PORTRAIT -> { - Column( - modifier = rootModifier, - ) { - LoginHeaderSection( - modifier = Modifier.fillMaxWidth() - ) - LoginSheet( - navController = navController, - modifier = Modifier - .fillMaxWidth() - .padding(top = 32.dp), - snackbarHostState = snackbarHostState - ) - } - } - DeviceConfiguration.MOBILE_LANDSCAPE -> { - Row( - modifier = rootModifier - .windowInsetsPadding(WindowInsets.displayCutout) - .padding( - horizontal = 32.dp - ), - horizontalArrangement = Arrangement.spacedBy(32.dp) - ) { - LoginHeaderSection( - modifier = Modifier - .weight(1f) - ) - LoginSheet( - navController = navController, - modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()), - snackbarHostState = snackbarHostState - ) - } - } - DeviceConfiguration.TABLET_PORTRAIT, - DeviceConfiguration.TABLET_LANDSCAPE, - DeviceConfiguration.DESKTOP -> { - Column( - modifier = rootModifier - .verticalScroll(rememberScrollState()) - .padding(top = 48.dp), - verticalArrangement = Arrangement.spacedBy(32.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - LoginHeaderSection( - modifier = Modifier - .widthIn(max = 540.dp), - alignment = Alignment.CenterHorizontally - ) - LoginSheet( - navController = navController, - modifier = Modifier - .widthIn(max = 540.dp), - snackbarHostState = snackbarHostState - ) - } - } - } - } -} - -@Composable -fun LoginHeaderSection( - alignment: Alignment.Horizontal = Alignment.Start, - modifier: Modifier -) { - Column( - modifier = modifier, - horizontalAlignment = alignment - ) { - Text( - text = "Log In", - style = MaterialTheme.typography.titleLarge - ) - Text( - text = "Capture your thoughts and ideas.", - style = MaterialTheme.typography.bodyLarge - ) - } -} - -@Composable -fun LoginSheet( - navController: NavHostController, - modifier: Modifier = Modifier, - snackbarHostState : SnackbarHostState -) { - - val context = LocalContext.current - val sessionManager = SessionManager(context) - val scope = rememberCoroutineScope() - val loginViewModel: LoginViewModel = hiltViewModel() - val authViewModel: AuthViewModel = hiltViewModel() - val state by loginViewModel.loginState.collectAsStateWithLifecycle() - - LaunchedEffect(state) { - when (state) { - is LoginState.Success -> { - authViewModel.refreshAuthState() - navController.navigate(HomeScreens.Home.route) { - popUpTo(0) { - inclusive = true - } - } - loginViewModel.clearState() - } - is LoginState.Error -> { - scope.launch { - val result = snackbarHostState - .showSnackbar( - message = "Invalid login credentials", - actionLabel = "", - duration = SnackbarDuration.Short - ) - when (result) { - SnackbarResult.ActionPerformed -> {} - SnackbarResult.Dismissed -> {} - } - } - } - is LoginState.Idle -> {} - is LoginState.Loading -> {} - } - } - - var email by remember { mutableStateOf("") } - var password by remember { mutableStateOf("") } - var isDisabled by remember { mutableStateOf(true) } - val focusManager = LocalFocusManager.current - - isDisabled = email.isBlank() || password.isBlank() || !isValidEmail(email) - LaunchedEffect(Unit) { - Log.d("sessionManager login", "access token: ${sessionManager.getAccessToken()}") - Log.d("sessionManager login", "refresh token: ${sessionManager.getRefreshToken()}") - } - - Column( - modifier = modifier - ) { - NoteMarkTextField( - text = email, - onValueChange = { email = it }, - label = "Email", - hint = "john.doe@example.com", - isInputSecret = false, - modifier = Modifier.fillMaxWidth(), - focusManager = focusManager, - focusDirection = FocusDirection.Down, - imeAction = ImeAction.Next, - ) - - Spacer(modifier = Modifier.height(16.dp)) - - NoteMarkTextField( - text = password, - onValueChange = { password = it }, - label = "Password", - hint = "Password", - isInputSecret = true, - modifier = Modifier.fillMaxWidth(), - focusManager = focusManager, - focusDirection = FocusDirection.Down, - imeAction = ImeAction.Done, - ) - - Spacer(modifier = Modifier.height(24.dp)) - - NoteMarkButton( - text = if (state is LoginState.Loading) "" else "Log in", - onClick = { - loginViewModel.loginUser(email, password) - }, - modifier = Modifier.fillMaxWidth(), - enabled = !isDisabled, - isLoading = state is LoginState.Loading - ) - - Spacer(modifier = Modifier.height(24.dp)) - - NoteMarkLink( - text = "Don't have an account?", - onClick = { - navController.navigate(AuthScreens.Registration.route) { - popUpTo(AuthScreens.LogIn.route) { - inclusive = true - } - } - }, - modifier = Modifier.fillMaxWidth() - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/registration/screens/RegistrationScreen.kt b/app/src/main/java/com/example/notemark/auth/presentation/registration/screens/RegistrationScreen.kt deleted file mode 100644 index 02d0afc..0000000 --- a/app/src/main/java/com/example/notemark/auth/presentation/registration/screens/RegistrationScreen.kt +++ /dev/null @@ -1,319 +0,0 @@ -package com.example.notemark.auth.presentation.registration.screens - -import android.util.Log -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.WindowInsets -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.Text -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -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.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusDirection -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import com.example.notemark.auth.presentation.registration.vm.RegistrationState -import com.example.notemark.auth.presentation.registration.vm.RegistrationViewModel -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.auth.presentation.util.FormValidation -import com.example.notemark.auth.presentation.util.ValidationResult -import com.example.notemark.core.components.NoteMarkButton -import com.example.notemark.core.components.NoteMarkLink -import com.example.notemark.core.components.NoteMarkTextField -import com.example.notemark.navigation.screens.AuthScreens -import kotlinx.coroutines.launch - -@Composable -fun RegistrationScreen( - navController: NavHostController = rememberNavController(), -) { - val snackbarHostState = remember { SnackbarHostState() } - - Scaffold( - modifier = Modifier.fillMaxSize(), - contentWindowInsets = WindowInsets.statusBars, - snackbarHost = { - SnackbarHost(hostState = snackbarHostState) - }, - ) { innerPadding -> - - val rootModifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .background( - color = MaterialTheme.colorScheme.surfaceContainerLowest, - shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) - ) - .padding(horizontal = 16.dp, vertical = 24.dp) - .consumeWindowInsets(WindowInsets.navigationBars) - - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) - - when(deviceConfiguration) { - DeviceConfiguration.MOBILE_PORTRAIT -> { - Column( - modifier = rootModifier, - ) { - RegistrationHeader( - modifier = Modifier.fillMaxWidth() - ) - RegistrationSheet( - navController = navController, - modifier = Modifier - .fillMaxWidth() - .padding(top = 32.dp), - snackbarHostState = snackbarHostState - ) - } - } - DeviceConfiguration.MOBILE_LANDSCAPE -> { - Row( - modifier = rootModifier - .windowInsetsPadding(WindowInsets.displayCutout) - .padding( - horizontal = 32.dp - ), - horizontalArrangement = Arrangement.spacedBy(32.dp) - ) { - RegistrationHeader( - modifier = Modifier - .weight(1f) - ) - RegistrationSheet( - navController = navController, - modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()), - snackbarHostState = snackbarHostState - ) - } - } - DeviceConfiguration.TABLET_PORTRAIT, - DeviceConfiguration.TABLET_LANDSCAPE, - DeviceConfiguration.DESKTOP -> { - Column( - modifier = rootModifier - .verticalScroll(rememberScrollState()) - .padding(top = 48.dp), - verticalArrangement = Arrangement.spacedBy(32.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - RegistrationHeader( - modifier = Modifier - .widthIn(max = 540.dp), - alignment = Alignment.CenterHorizontally - ) - RegistrationSheet( - navController = navController, - modifier = Modifier - .widthIn(max = 540.dp), - snackbarHostState = snackbarHostState - ) - } - } - } - } -} - -@Composable -private fun RegistrationHeader( - modifier: Modifier = Modifier, - alignment: Alignment.Horizontal = Alignment.Start -) { - Column( - modifier = modifier, - horizontalAlignment = alignment - ) { - Text( - text = "Create account", - style = MaterialTheme.typography.titleLarge - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = "Capture your thoughts and ideas.", - style = MaterialTheme.typography.bodyLarge - ) - } -} - -@Composable -fun RegistrationSheet( - navController: NavHostController, - modifier: Modifier = Modifier, - snackbarHostState : SnackbarHostState -) { - val scope = rememberCoroutineScope() - val registrationViewModel: RegistrationViewModel = hiltViewModel() - val registrationState by registrationViewModel.registrationState - - var username by remember { mutableStateOf("") } - var email by remember { mutableStateOf("") } - var password by remember { mutableStateOf("") } - var repeatPassword by remember { mutableStateOf("") } - val focusManager = LocalFocusManager.current - - LaunchedEffect(registrationState) { - when(registrationState) { - is RegistrationState.Success -> { - navController.navigate(AuthScreens.LogIn.route) { - popUpTo(AuthScreens.Registration.route) { - inclusive = true - } - } - registrationViewModel.clearState() - } - is RegistrationState.Error -> { - Log.e("RegistrationScreen", "Registration failed: ${(registrationState as RegistrationState.Error).message}") - scope.launch { - val result = snackbarHostState - .showSnackbar( - message = "Invalid registration credentials: ${(registrationState as RegistrationState.Error).message}", - actionLabel = "", - duration = SnackbarDuration.Short - ) - when (result) { - SnackbarResult.ActionPerformed -> {} - SnackbarResult.Dismissed -> {} - } - } - } - RegistrationState.Idle -> {} - RegistrationState.Loading -> {} - } - } - - - - val usernameValidation = FormValidation.validateUsername(username) - val emailValidation = FormValidation.validateEmail(email) - val passwordValidation = FormValidation.validatePassword(password) - val repeatPasswordValidation = FormValidation.validateRepeatPassword(password, repeatPassword) - - val isFormValid = FormValidation.isFormValid(username, email, password, repeatPassword) - - Column( - modifier = modifier - .fillMaxSize() - .padding(top = 24.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - NoteMarkTextField( - text = username, - onValueChange = { username = it }, - label = "Username", - hint = "Enter username", - isInputSecret = false, - focusManager = focusManager, - focusDirection = FocusDirection.Down, - imeAction = ImeAction.Next, - errorText = if (usernameValidation is ValidationResult.Error) usernameValidation.message else null, - isError = usernameValidation is ValidationResult.Error, - showFocusText = "Use between 3 and 20 characters for your username." - ) - - NoteMarkTextField( - text = email, - onValueChange = { email = it }, - label = "Email", - hint = "Enter email", - isInputSecret = false, - focusManager = focusManager, - focusDirection = FocusDirection.Down, - imeAction = ImeAction.Next, - errorText = if (emailValidation is ValidationResult.Error) emailValidation.message else null, - isError = emailValidation is ValidationResult.Error - ) - - NoteMarkTextField( - text = password, - onValueChange = { password = it }, - label = "Password", - hint = "Enter password", - isInputSecret = true, - focusManager = focusManager, - focusDirection = FocusDirection.Down, - imeAction = ImeAction.Next, - errorText = if (passwordValidation is ValidationResult.Error) passwordValidation.message else null, - isError = passwordValidation is ValidationResult.Error, - showFocusText = "Use 8+ characters with a number or symbol for better security." - ) - - NoteMarkTextField( - text = repeatPassword, - onValueChange = { repeatPassword = it }, - label = "Repeat Password", - hint = "Repeat password", - isInputSecret = true, - focusManager = focusManager, - focusDirection = FocusDirection.Down, - imeAction = ImeAction.Done, - errorText = if (repeatPasswordValidation is ValidationResult.Error) repeatPasswordValidation.message else null, - isError = repeatPasswordValidation is ValidationResult.Error - ) - - Spacer(modifier = Modifier.height(16.dp)) - - NoteMarkButton( - text = if (registrationState is RegistrationState.Loading) "" else "Create account", - onClick = { - registrationViewModel.registerUser( - username = username, - email = email, - password = password - ) - }, - enabled = isFormValid, - modifier = Modifier.fillMaxWidth(), - isLoading = registrationState is RegistrationState.Loading - ) - - Spacer(modifier = Modifier.height(8.dp)) - - NoteMarkLink( - text = "Already have an account?", - onClick = { - navController.navigate(AuthScreens.LogIn.route) { - popUpTo(AuthScreens.Registration.route) { - inclusive = true - } - } - }, - modifier = Modifier.fillMaxWidth() - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/util/DeviceConfiguration.kt b/app/src/main/java/com/example/notemark/auth/presentation/util/DeviceConfiguration.kt deleted file mode 100644 index 3ce6688..0000000 --- a/app/src/main/java/com/example/notemark/auth/presentation/util/DeviceConfiguration.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.notemark.auth.presentation.util - -import androidx.window.core.layout.WindowHeightSizeClass -import androidx.window.core.layout.WindowSizeClass -import androidx.window.core.layout.WindowWidthSizeClass - -enum class DeviceConfiguration { - MOBILE_PORTRAIT, - MOBILE_LANDSCAPE, - TABLET_PORTRAIT, - TABLET_LANDSCAPE, - DESKTOP; - - companion object { - fun fromWindowSizeClass(windowSizeClass: WindowSizeClass): DeviceConfiguration { - val widthClass = windowSizeClass.windowWidthSizeClass - val heightClass = windowSizeClass.windowHeightSizeClass - - return when { - widthClass == WindowWidthSizeClass.COMPACT && - heightClass == WindowHeightSizeClass.MEDIUM -> MOBILE_PORTRAIT - widthClass == WindowWidthSizeClass.COMPACT && - heightClass == WindowHeightSizeClass.EXPANDED -> MOBILE_PORTRAIT - widthClass == WindowWidthSizeClass.EXPANDED && - heightClass == WindowHeightSizeClass.COMPACT -> MOBILE_LANDSCAPE - widthClass == WindowWidthSizeClass.MEDIUM && - heightClass == WindowHeightSizeClass.EXPANDED -> TABLET_PORTRAIT - widthClass == WindowWidthSizeClass.EXPANDED && - heightClass == WindowHeightSizeClass.MEDIUM -> TABLET_LANDSCAPE - else -> DESKTOP - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/util/isValidEmail.kt b/app/src/main/java/com/example/notemark/auth/presentation/util/isValidEmail.kt deleted file mode 100644 index 224a495..0000000 --- a/app/src/main/java/com/example/notemark/auth/presentation/util/isValidEmail.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.example.notemark.auth.presentation.util - -fun isValidEmail(email: String): Boolean = android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/di/AuthModule.kt b/app/src/main/java/com/example/notemark/core/di/AuthModule.kt deleted file mode 100644 index d837689..0000000 --- a/app/src/main/java/com/example/notemark/core/di/AuthModule.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.example.notemark.core.di - -import android.content.Context -import com.example.notemark.auth.data.remote.api.LoginService -import com.example.notemark.auth.data.remote.api.LogoutService -import com.example.notemark.auth.data.remote.repositoryImpl.LogoutServiceImpl -import com.example.notemark.auth.data.remote.api.RegistrationService -import com.example.notemark.auth.data.remote.repositoryImpl.LoginServiceImpl -import com.example.notemark.auth.data.remote.repositoryImpl.RegistrationServiceImpl -import com.example.notemark.auth.domain.UserDataValidator -import com.example.notemark.core.manager.SessionManager -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import io.ktor.client.HttpClient -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object AuthModule { - - @Provides - @Singleton - fun provideSessionManager(@ApplicationContext context: Context): SessionManager = SessionManager(context) - - - @Provides - @Singleton - fun provideRegistrationService(client: HttpClient): RegistrationService = RegistrationServiceImpl(client) - - @Provides - @Singleton - fun provideLoginService(client: HttpClient): LoginService = LoginServiceImpl(client) - - @Provides - @Singleton - fun provideLogoutService(client: HttpClient): LogoutService = LogoutServiceImpl(client) - - @Provides - @Singleton - fun provideUserDataValidator(): UserDataValidator = UserDataValidator() -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/manager/SessionManager.kt b/app/src/main/java/com/example/notemark/core/manager/SessionManager.kt deleted file mode 100644 index 1343daf..0000000 --- a/app/src/main/java/com/example/notemark/core/manager/SessionManager.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.example.notemark.core.manager - -import android.content.Context -import android.content.SharedPreferences - -class SessionManager( - private val context: Context -) { - - companion object { - const val PREF_NAME = "NoteMarkPrefs" - const val ACCESS_TOKEN = "access_token" - const val REFRESH_TOKEN = "refresh_token" - const val USER_NAME = "user_name" - } - - private val sharedPrefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - private val editor: SharedPreferences.Editor = sharedPrefs.edit() - - - // we are not using commit() because it's synchronous, so we don't need to wait for the result - // we use apply() because it's asynchronous and saves the data asynchronously - fun saveTokens(accessToken: String, refreshToken: String) { - sharedPrefs.edit().apply { - putString(ACCESS_TOKEN, accessToken) - putString(REFRESH_TOKEN, refreshToken) - apply() - } - } - - fun saveUserName(userName: String) { - sharedPrefs.edit().apply { - putString(USER_NAME, userName) - apply() - } - } - - fun getUserName(): String? = sharedPrefs.getString(USER_NAME, null) - - fun clearUserName() { - editor.apply { - remove(USER_NAME) - apply() - } - } - - fun getAccessToken(): String? = sharedPrefs.getString(ACCESS_TOKEN, null) - - fun getRefreshToken(): String? = sharedPrefs.getString(REFRESH_TOKEN, null) - - fun clearTokens() { - sharedPrefs.edit().apply { - remove(ACCESS_TOKEN) - remove(REFRESH_TOKEN) - apply() - } - } - - fun isLoggedIn(): Boolean = !getAccessToken().isNullOrEmpty() -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/local/NoteDatabase.kt b/app/src/main/java/com/example/notemark/main/data/local/NoteDatabase.kt deleted file mode 100644 index 9a9b9c7..0000000 --- a/app/src/main/java/com/example/notemark/main/data/local/NoteDatabase.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.notemark.main.data.local - -import androidx.room.Database -import androidx.room.RoomDatabase -import com.example.notemark.main.presentation.screens.note.SyncRecord - -@Database( - entities = [NoteEntity::class, SyncRecord::class], - version = 3 -) -abstract class NoteDatabase: RoomDatabase() { - abstract val dao: NoteDao - abstract val syncDao: SyncDao -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/local/SyncDao.kt b/app/src/main/java/com/example/notemark/main/data/local/SyncDao.kt deleted file mode 100644 index a41c361..0000000 --- a/app/src/main/java/com/example/notemark/main/data/local/SyncDao.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.notemark.main.data.local - -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Upsert -import com.example.notemark.main.presentation.screens.note.SyncRecord - -@Dao -interface SyncDao { - @Upsert - suspend fun insertSyncRecord(record: SyncRecord) - - @Query("DELETE FROM syncrecord WHERE id = :id") - suspend fun deleteSyncRecord(id: String) - - @Query("SELECT * FROM syncrecord WHERE userId = :userId") - suspend fun getAllSyncRecords(userId: String): List -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/remote/NoteMappers.kt b/app/src/main/java/com/example/notemark/main/data/remote/NoteMappers.kt deleted file mode 100644 index 7d41674..0000000 --- a/app/src/main/java/com/example/notemark/main/data/remote/NoteMappers.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.example.notemark.main.data.remote - -import com.example.notemark.main.data.local.NoteEntity -import com.example.notemark.main.domain.model.Note -import com.example.notemark.main.domain.model.NoteRequest -import com.example.notemark.main.domain.model.NotesResponse - -fun NoteDTO.toNoteEntity(): NoteEntity { - return NoteEntity( - id = id, - title = title, - content = content, - createdAt = createdAt, - updatedAt = updatedAt - ) -} - - -fun NoteEntity.toNote(): Note { - return Note( - id = id, - title = title, - content = content, - createdAt = createdAt, - updatedAt = updatedAt - ) -} - -fun NoteEntity.toNoteDTO(): NoteDTO { - return NoteDTO( - id = id, - title = title, - content = content, - createdAt = createdAt, - updatedAt = updatedAt - ) -} - -fun NoteRequest.toNotEntity(): NoteEntity { - return NoteEntity( - id = id, - title = title, - content = content, - createdAt = createdAt, - updatedAt = updatedAt - ) -} - -fun NotesResponse.toNoteEntityList(): List { - return this.notes.map { it.toNoteEntity() } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/remote/api/NoteService.kt b/app/src/main/java/com/example/notemark/main/data/remote/api/NoteService.kt deleted file mode 100644 index f94071f..0000000 --- a/app/src/main/java/com/example/notemark/main/data/remote/api/NoteService.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.notemark.main.data.remote.api - -import com.example.notemark.main.data.remote.NoteDTO -import com.example.notemark.main.domain.model.NoteRequest -import com.example.notemark.main.domain.model.NotesResponse - -interface NoteService { - - suspend fun getNotes( - page: Int, - size: Int, - ): NotesResponse - - suspend fun createNote( - body: NoteRequest - ): Result - - suspend fun updateNote( - body: NoteRequest - ): Result - - suspend fun deleteNote( - id: String - ): Result -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/domain/model/Note.kt b/app/src/main/java/com/example/notemark/main/domain/model/Note.kt deleted file mode 100644 index f098b7d..0000000 --- a/app/src/main/java/com/example/notemark/main/domain/model/Note.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.notemark.main.domain.model - -import com.example.notemark.main.formatAsDetailsDate -import com.example.notemark.main.formatAsNoteDate -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class Note( - val id: String, - val title: String, - val content: String, - val createdAt: String, - @SerialName("lastEditedAt") - val updatedAt: String? = "" -) - -fun Note.getFormattedCreatedAt(): String = createdAt.formatAsNoteDate() -fun Note.getFormattedUpdatedAt(): String = updatedAt?.formatAsDetailsDate() ?: "" \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/domain/model/NoteRequest.kt b/app/src/main/java/com/example/notemark/main/domain/model/NoteRequest.kt deleted file mode 100644 index d5b51ad..0000000 --- a/app/src/main/java/com/example/notemark/main/domain/model/NoteRequest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.notemark.main.domain.model - -import com.example.notemark.main.DateFormatter -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class NoteRequest( - val id: String, - val title: String, - val content: String, - val createdAt: String = DateFormatter.getCurrentIsoString(), - @SerialName("lastEditedAt") - val updatedAt: String? = DateFormatter.getCurrentIsoString() -) \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/domain/repository/NotesRepository.kt b/app/src/main/java/com/example/notemark/main/domain/repository/NotesRepository.kt deleted file mode 100644 index f3b8dc4..0000000 --- a/app/src/main/java/com/example/notemark/main/domain/repository/NotesRepository.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.notemark.main.domain.repository - -import com.example.notemark.main.data.remote.NoteDTO -import com.example.notemark.main.data.remote.repositoryImpl.NoteServiceImpl -import com.example.notemark.main.domain.model.NoteRequest -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -class NotesRepository( - private val api: NoteServiceImpl -) { - - fun createNote( - body: NoteRequest - ) : Flow> = flow { - emit(api.createNote(body)) - } - - fun updateNote( - body: NoteRequest - ) : Flow> = flow { - emit(api.updateNote(body)) - } - - suspend fun deleteNote(noteId: String): Result { - return api.deleteNote(noteId) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/presentation/screens/Profile.kt b/app/src/main/java/com/example/notemark/main/presentation/screens/Profile.kt deleted file mode 100644 index 66aaee8..0000000 --- a/app/src/main/java/com/example/notemark/main/presentation/screens/Profile.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.notemark.main.presentation.screens - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController - -@Composable -fun ProfileScreen( - navController: NavHostController = rememberNavController() -) { - Scaffold( - topBar = {} - ) { innerPadding -> - Column( - modifier = Modifier.padding(innerPadding) - ) { - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/presentation/screens/Settings.kt b/app/src/main/java/com/example/notemark/main/presentation/screens/Settings.kt deleted file mode 100644 index 90e77da..0000000 --- a/app/src/main/java/com/example/notemark/main/presentation/screens/Settings.kt +++ /dev/null @@ -1,408 +0,0 @@ -package com.example.notemark.main.presentation.screens - -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -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.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import com.example.notemark.R -import com.example.notemark.auth.presentation.login.vm.LoginViewModel -import com.example.notemark.auth.presentation.login.vm.LogoutState -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.core.manager.SessionManager -import com.example.notemark.main.presentation.vm.NotesViewModel -import com.example.notemark.navigation.screens.AuthScreens - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SettingsScreen( - navController: NavHostController = rememberNavController() -) { - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) - - when(deviceConfiguration) { - DeviceConfiguration.MOBILE_PORTRAIT -> { - SettingsItem( - navController, - - ) - } - DeviceConfiguration.MOBILE_LANDSCAPE -> { - SettingsItem( - navController, - - ) - } - DeviceConfiguration.TABLET_PORTRAIT, - DeviceConfiguration.TABLET_LANDSCAPE, - DeviceConfiguration.DESKTOP -> { - SettingsItem( - navController, - ) - } - } -} - -@Composable -@OptIn(ExperimentalMaterial3Api::class) -private fun SettingsItem( - navController: NavHostController, -) { - val context = LocalContext.current - val sessionManager = SessionManager(context) - val refreshToken = sessionManager.getRefreshToken() - val loginViewModel: LoginViewModel = hiltViewModel() - val notesViewModel: NotesViewModel = hiltViewModel() - var showSyncIntervalDropdown by remember { mutableStateOf(false) } - val logoutState by loginViewModel.logoutState.collectAsStateWithLifecycle() - var selectedSyncInterval by remember { mutableStateOf(SyncInterval.MANUAL_ONLY) } - - LaunchedEffect(logoutState) { - when (logoutState) { - is LogoutState.Error -> { - Toast.makeText( - context, - (logoutState as LogoutState.Error).message, - Toast.LENGTH_SHORT - ).show() - loginViewModel.clearState() - } - - is LogoutState.Success -> { - notesViewModel.clear() - navController.navigate(AuthScreens.Landing.route) { - popUpTo(0) { - inclusive = true - } - } - loginViewModel.clearState() - } - LogoutState.Loading, - LogoutState.Idle -> {} - } - } - - Scaffold( - topBar = { - TopAppBar( - title = { - Text( - text = stringResource(R.string.settings).uppercase(), - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 1.0.sp, - fontFamily = FontFamily(Font(R.font.space_grotesk_regular)), - ) - }, - navigationIcon = { - IconButton( - onClick = { navController.navigateUp() } - ) { - Icon( - tint = MaterialTheme.colorScheme.onSurfaceVariant, - imageVector = ImageVector.vectorResource(R.drawable.ios_arrow_left), - contentDescription = "Navigate up" - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface - ) - ) - } - ) { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.surface) - .padding(innerPadding) - ) { - - if (logoutState is LogoutState.Loading) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } else { - SettingsContent( - onLogoutClick = { - loginViewModel.logoutUser(refreshToken = refreshToken ?: "") - }, - onSyncClick = { - Toast.makeText( - context, - "This page is not available yet!", - Toast.LENGTH_SHORT - ).show() - }, - onSyncIntervalClick = { - showSyncIntervalDropdown = true - }, - showSyncIntervalDropdown = showSyncIntervalDropdown, - onDismissSyncIntervalDropdown = { - showSyncIntervalDropdown = false - }, - selectedSyncInterval = selectedSyncInterval, - onSyncIntervalSelected = { interval -> - selectedSyncInterval = interval - showSyncIntervalDropdown = false - } - ) - } - } - } -} - -// Enum for sync intervals -enum class SyncInterval(val displayName: String) { - MANUAL_ONLY("Manual Only"), - EVERY_15_MINUTES("15 minutes"), - EVERY_30_MINUTES("30 minutes"), - EVERY_HOUR("1 hour") -} - -@Composable -private fun SettingsContent( - onLogoutClick: () -> Unit, - onSyncClick: () -> Unit, - onSyncIntervalClick: () -> Unit, - showSyncIntervalDropdown: Boolean, - onDismissSyncIntervalDropdown: () -> Unit, - selectedSyncInterval: SyncInterval, - onSyncIntervalSelected: (SyncInterval) -> Unit -) { - SingleItem( - onEndIconAndTextClick = onSyncIntervalClick, - supportingText = null, - headline = stringResource(R.string.sync_interval), - headlineTextColor = MaterialTheme.colorScheme.onSurface, - leadingIcon = ImageVector.vectorResource(R.drawable.clock), - iconTint = MaterialTheme.colorScheme.onSurfaceVariant, - endIcon = ImageVector.vectorResource(R.drawable.chevron_right), - endIconTint = MaterialTheme.colorScheme.onSurfaceVariant, - endText = selectedSyncInterval.displayName, - endTextColor = MaterialTheme.colorScheme.onSurfaceVariant, - showDropdown = showSyncIntervalDropdown, - onDismissDropdown = onDismissSyncIntervalDropdown, - dropdownContent = { - SyncInterval.entries.forEach { interval -> - DropdownMenuItem( - modifier = Modifier - .width(190.dp) - .background( - color = MaterialTheme.colorScheme.surface, - shape = RoundedCornerShape(16.dp) - ), - text = { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = interval.displayName - ) - - if (interval == selectedSyncInterval) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.check), - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(20.dp) - ) - } - } - }, - onClick = { - onSyncIntervalSelected(interval) - } - ) - } - } - ) - - SingleItem( - onClick = onSyncClick, - supportingText = stringResource(R.string.last_sync), - headline = stringResource(R.string.sync_data), - headlineTextColor = MaterialTheme.colorScheme.onSurface, - supportingTextColor = MaterialTheme.colorScheme.onSurfaceVariant, - leadingIcon = ImageVector.vectorResource(R.drawable.sync_icon), - iconTint = MaterialTheme.colorScheme.onSurfaceVariant, - endIcon = null, - endIconTint = null, - endText = null, - endTextColor = null - ) - - SingleItem( - onClick = onLogoutClick, - headline = stringResource(R.string.logout), - supportingText = null, - headlineTextColor = MaterialTheme.colorScheme.error, - supportingTextColor = null, - leadingIcon = ImageVector.vectorResource(R.drawable.logout), - iconTint = MaterialTheme.colorScheme.error, - endIcon = null, - endIconTint = null, - endText = null, - endTextColor = null - ) -} - -@Composable -private fun SingleItem( - modifier: Modifier = Modifier, - onEndIconAndTextClick: (() -> Unit)? = null, - onClick: (() -> Unit)? = null, - supportingText: String? = null, - supportingTextColor: Color? = null, - headline: String, - headlineTextColor: Color, - leadingIcon: ImageVector? = null, - iconTint: Color? = null, - endIcon: ImageVector? = null, - endIconTint: Color? = null, - endText: String? = null, - endTextColor: Color? = null, - showDropdown: Boolean = false, - onDismissDropdown: (() -> Unit)? = null, - dropdownContent: (@Composable ColumnScope.() -> Unit)? = null, -) { - Row( - modifier = modifier - .fillMaxWidth() - .heightIn(min = 56.dp) - .padding(horizontal = 16.dp, vertical = 8.dp) - .then( - if (onClick != null) { - Modifier.clickable { onClick() } - } else { - Modifier - } - ), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - - leadingIcon?.let { icon -> - Icon( - imageVector = icon, - contentDescription = null, - tint = iconTint ?: MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.Start - ) { - Text( - text = headline, - style = MaterialTheme.typography.titleSmall.copy( - color = headlineTextColor - ) - ) - - if (!supportingText.isNullOrBlank()) { - Text( - text = supportingText, - style = MaterialTheme.typography.bodySmall.copy( - color = supportingTextColor ?: MaterialTheme.colorScheme.onSurfaceVariant - ) - ) - } - } - - val hasEndContent = !endText.isNullOrBlank() || endIcon != null - if (hasEndContent) { - Box { - Row( - modifier = Modifier.then( - if (onEndIconAndTextClick != null) { - Modifier.clickable { onEndIconAndTextClick() } - } else { - Modifier - } - ), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - if (!endText.isNullOrBlank()) { - Text( - text = endText, - style = MaterialTheme.typography.bodySmall.copy( - color = endTextColor ?: MaterialTheme.colorScheme.onSurfaceVariant - ) - ) - } - - endIcon?.let { icon -> - Icon( - imageVector = icon, - contentDescription = null, - tint = endIconTint ?: MaterialTheme.colorScheme.onSurfaceVariant - ) - } - } - - if (dropdownContent != null) { - DropdownMenu( - expanded = showDropdown, - onDismissRequest = onDismissDropdown ?: {} - ) { - dropdownContent() - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/presentation/screens/note/Details.kt b/app/src/main/java/com/example/notemark/main/presentation/screens/note/Details.kt deleted file mode 100644 index a3b0578..0000000 --- a/app/src/main/java/com/example/notemark/main/presentation/screens/note/Details.kt +++ /dev/null @@ -1,600 +0,0 @@ -package com.example.notemark.main.presentation.screens.note - -import android.app.Activity -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import androidx.paging.compose.LazyPagingItems -import com.example.notemark.R -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.main.domain.model.Note -import com.example.notemark.main.domain.model.getFormattedUpdatedAt -import com.example.notemark.main.presentation.vm.DetailsViewModel -import com.example.notemark.navigation.screens.HomeScreens - - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun DetailsScreen( - navController: NavHostController = rememberNavController(), - noteId: String? = null, - notes: LazyPagingItems -) { - val note = notes.itemSnapshotList.items.find { it.id == noteId } - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) - - when(deviceConfiguration) { - DeviceConfiguration.MOBILE_PORTRAIT -> { - DetailsContent( - modifier = Modifier, - navController = navController, - note = note, - noteId = noteId, - ) - } - DeviceConfiguration.MOBILE_LANDSCAPE -> { - LandscapeDetailsScreenContent( - modifier = Modifier - .windowInsetsPadding(WindowInsets.displayCutout) - .consumeWindowInsets(WindowInsets.navigationBars), - navController = navController, - note = note, - noteId = noteId, - ) - } - DeviceConfiguration.TABLET_PORTRAIT, - DeviceConfiguration.TABLET_LANDSCAPE -> { - LandscapeDetailsScreenContent( - modifier = Modifier - .windowInsetsPadding(WindowInsets.displayCutout) - .consumeWindowInsets(WindowInsets.navigationBars), - navController = navController, - note = note, - noteId = noteId, - ) - } - DeviceConfiguration.DESKTOP -> { - DetailsContent( - modifier = Modifier, - navController = navController, - note = note, - noteId = noteId, - ) - } - } -} - -@Composable -fun LandscapeDetailsScreenContent( - modifier: Modifier, - navController: NavHostController, - note: Note?, - noteId: String? -) { - val context = LocalContext.current - val activity = context as Activity - val scrollState = rememberScrollState() - val viewModel: DetailsViewModel = viewModel() - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - LaunchedEffect(uiState.requestedOrientation) { - uiState.requestedOrientation?.let { orientation -> - activity.requestedOrientation = orientation - viewModel.onOrientationHandled() - } - } - - LaunchedEffect(Unit) { - viewModel.setOriginalOrientation(activity.requestedOrientation) - } - - BackHandler(enabled = uiState.isReaderMode) { - viewModel.toggleReaderMode() - } - - LaunchedEffect(scrollState.value) { - if (scrollState.isScrollInProgress) { - viewModel.onScrollDetected() - } - } - - Box( - modifier = modifier.fillMaxSize() - ) { - // Main content - this stays in place - Row( - modifier = Modifier - .fillMaxSize() - .padding(start = 80.dp) - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - viewModel.onScreenTap() - } - ) { - // Left spacer to account for back button (keep content centered) - Spacer(modifier = Modifier.width(64.dp)) // Adjust based on your back button width - - // Content area - Box( - modifier = Modifier - .fillMaxSize() - .padding(top = 24.dp, bottom = 80.dp) // Add bottom padding for FAB - ) { - if (note != null && note.id == noteId) { - Column( - modifier = Modifier.verticalScroll(scrollState) - ) { - Text( - text = note.title, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onSurface, - ), - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) - - HorizontalDivider() - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) { - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = stringResource(R.string.date_created), - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - ) - Text( - text = note.getFormattedUpdatedAt(), - style = MaterialTheme.typography.titleSmall.copy( - color = MaterialTheme.colorScheme.onSurface, - fontWeight = FontWeight.Bold - ) - ) - } - if (!note.getFormattedUpdatedAt().isEmpty()) { - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = stringResource(R.string.last_edited), - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - ) - Text( - text = note.getFormattedUpdatedAt(), - style = MaterialTheme.typography.titleSmall.copy( - color = MaterialTheme.colorScheme.onSurface, - fontWeight = FontWeight.Bold - ) - ) - } - } - } - - HorizontalDivider() - - Text( - text = note.content, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ), - modifier = Modifier - .fillMaxWidth() - .padding( - if (uiState.isReaderMode && !uiState.isUiVisible) { - PaddingValues(horizontal = 16.dp, vertical = 32.dp) - } else { - PaddingValues(horizontal = 16.dp, vertical = 32.dp) - } - ) - ) - } - } else { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } - } - } - - // Back button overlay - positioned absolutely at top-left - AnimatedVisibility( - visible = !uiState.isReaderMode || uiState.isUiVisible, - enter = fadeIn(animationSpec = tween(300)) + slideInVertically( - animationSpec = tween(300) - ) { -it }, - exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( - animationSpec = tween(300) - ) { -it }, - modifier = Modifier - .align(Alignment.TopStart) - .padding(top = 24.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - IconButton( - onClick = { - if (uiState.isReaderMode) viewModel.toggleReaderMode() else navController.popBackStack() - } - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ios_arrow_left), - contentDescription = "Navigate up", - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - - Spacer(modifier = Modifier.width(8.dp)) - - Text( - text = stringResource(R.string.all_notes).uppercase(), - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 1.0.sp, - fontFamily = FontFamily(Font(R.font.space_grotesk_regular)), - ) - } - } - - // FAB overlay - positioned absolutely at bottom-center - AnimatedVisibility( - visible = !uiState.isReaderMode || uiState.isUiVisible, - enter = fadeIn(animationSpec = tween(300)) + slideInVertically( - animationSpec = tween(300) - ) { it }, - exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( - animationSpec = tween(300) - ) { it }, - modifier = Modifier.align(Alignment.BottomCenter) - ) { - ExtendedFabFSheet( - onEditClick = { - navController.navigate( - HomeScreens.EditNote( - noteId = noteId ?: "" - ) - ) - }, - onReaderModeClick = { viewModel.toggleReaderMode() }, - isReaderMode = uiState.isReaderMode - ) - } - } -} - -@Composable -@OptIn(ExperimentalMaterial3Api::class) -private fun DetailsContent( - modifier: Modifier, - navController: NavHostController, - note: Note?, - noteId: String?, -) { - val context = LocalContext.current - val activity = context as Activity - - val scrollState = rememberScrollState() - val viewModel: DetailsViewModel = viewModel() - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - LaunchedEffect(uiState.requestedOrientation) { - uiState.requestedOrientation?.let { orientation -> - activity.requestedOrientation = orientation - viewModel.onOrientationHandled() - } - } - - LaunchedEffect(Unit) { - viewModel.setOriginalOrientation(activity.requestedOrientation) - } - - BackHandler(enabled = uiState.isReaderMode) { - viewModel.toggleReaderMode() - } - - LaunchedEffect(scrollState.value) { - if (scrollState.isScrollInProgress) { - viewModel.onScrollDetected() - } - } - - Scaffold( - topBar = { - AnimatedVisibility( - visible = !uiState.isReaderMode || uiState.isUiVisible, - enter = fadeIn(animationSpec = tween(300)) + slideInVertically( - animationSpec = tween(300) - ) { -it }, - exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( - animationSpec = tween(300) - ) { -it } - ) { - TopAppBar( - title = { - Text( - text = stringResource(R.string.all_notes).uppercase(), - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 1.0.sp, - fontFamily = FontFamily(Font(R.font.space_grotesk_regular)), - ) - }, - navigationIcon = { - IconButton( - onClick = { - if (uiState.isReaderMode) viewModel.toggleReaderMode() else navController.popBackStack() - } - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ios_arrow_left), - contentDescription = "Navigate up", - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.onPrimary - ) - ) - } - } - ) { innerPadding -> - Box( - modifier = modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.onPrimary) - .padding( - if (!uiState.isReaderMode || uiState.isUiVisible) innerPadding else PaddingValues( - 0.dp - ) - ) - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - viewModel.onScreenTap() - }, - ) { - if (note != null && note.id == noteId) { - Column( - modifier = Modifier.verticalScroll(scrollState) - ) { - Text( - text = note.title, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onSurface, - ), - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) - - HorizontalDivider() - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) { - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = stringResource(R.string.date_created), - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - ) - Text( - text = note.getFormattedUpdatedAt(), - style = MaterialTheme.typography.titleSmall.copy( - color = MaterialTheme.colorScheme.onSurface, - fontWeight = FontWeight.Bold - ) - ) - } - if (!note.getFormattedUpdatedAt().isEmpty()){ - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = stringResource(R.string.last_edited), - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - ) - Text( - text = note.getFormattedUpdatedAt(), - style = MaterialTheme.typography.titleSmall.copy( - color = MaterialTheme.colorScheme.onSurface, - fontWeight = FontWeight.Bold - ) - ) - } - } - } - - HorizontalDivider() - - Text( - text = note.content, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ), - modifier = Modifier - .fillMaxWidth() - .padding( - if (uiState.isReaderMode && !uiState.isUiVisible) { - PaddingValues(horizontal = 24.dp, vertical = 32.dp) - } else { - PaddingValues(16.dp) - } - ) - ) - } - } else { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } - - AnimatedVisibility( - visible = !uiState.isReaderMode || uiState.isUiVisible, - enter = fadeIn(animationSpec = tween(300)) + slideInVertically( - animationSpec = tween(300) - ) { it }, - exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( - animationSpec = tween(300) - ) { it }, - modifier = Modifier.align(Alignment.BottomCenter) - ) { - ExtendedFabFSheet( - onEditClick = { - // if we are in reader mode, first exit reader mode then handle the navigation - navController.navigate( - HomeScreens.EditNote( - noteId = noteId ?: "" - ) - ) - }, - onReaderModeClick = { - viewModel.toggleReaderMode() - }, - isReaderMode = uiState.isReaderMode - ) - } - } - } -} - -@Composable -internal fun ExtendedFabFSheet( - modifier: Modifier = Modifier, - onEditClick: () -> Unit, - onReaderModeClick: () -> Unit, - isReaderMode: Boolean = false -) { - Row( - modifier = modifier - .padding(16.dp) - .background( - MaterialTheme.colorScheme.surface, - shape = MaterialTheme.shapes.medium - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - IconButton( - onClick = onEditClick - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.edit), - contentDescription = null - ) - } - - IconButton( - onClick = onReaderModeClick, - modifier = Modifier - .then( - if (isReaderMode) { - Modifier - .padding(4.dp) - .background( - color = Color(0x1A5977F7), - shape = MaterialTheme.shapes.medium - ) - } else { - Modifier - } - ) - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.book_open), - contentDescription = null, - tint = if (isReaderMode) MaterialTheme.colorScheme.primary else LocalContentColor.current - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/presentation/screens/note/Home.kt b/app/src/main/java/com/example/notemark/main/presentation/screens/note/Home.kt deleted file mode 100644 index d546f1e..0000000 --- a/app/src/main/java/com/example/notemark/main/presentation/screens/note/Home.kt +++ /dev/null @@ -1,656 +0,0 @@ -package com.example.notemark.main.presentation.screens.note - -import android.util.Log -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid -import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells -import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan -import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.DeleteOutline -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.FloatingActionButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.ListItem -import androidx.compose.material3.ListItemDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import androidx.paging.LoadState -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.itemKey -import com.airbnb.lottie.compose.LottieAnimation -import com.airbnb.lottie.compose.LottieCompositionSpec -import com.airbnb.lottie.compose.rememberLottieAnimatable -import com.airbnb.lottie.compose.rememberLottieComposition -import com.example.notemark.R -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.core.getInitials -import com.example.notemark.core.manager.SessionManager -import com.example.notemark.core.truncateAtWord -import com.example.notemark.main.DateFormatter -import com.example.notemark.main.domain.model.Note -import com.example.notemark.main.domain.model.NoteRequest -import com.example.notemark.main.domain.model.getFormattedCreatedAt -import com.example.notemark.main.presentation.vm.DeleteNoteUiState -import com.example.notemark.main.presentation.vm.NoteUiState -import com.example.notemark.main.presentation.vm.NotesViewModel -import com.example.notemark.navigation.screens.AuthScreens -import com.example.notemark.navigation.screens.HomeScreens -import kotlinx.coroutines.delay -import java.util.UUID - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun HomeScreen( - navController: NavHostController = rememberNavController(), - connectivityState: State, - notesList: LazyPagingItems, - noteUiState: State, - -) { - val context = LocalContext.current - val sessionManager = SessionManager(context) - val username = sessionManager.getUserName() - val accessToken = sessionManager.getAccessToken() - val refreshToken = sessionManager.getRefreshToken() - - LaunchedEffect(Unit) { - Log.d("HomeScreen", "Username: ${sessionManager.getUserName()}") - Log.d("HomeScreen", "Access Token: ${sessionManager.getAccessToken()}") - Log.d("HomeScreen", "Refresh Token: ${sessionManager.getRefreshToken()}") - } - - LaunchedEffect(Unit) { - if (accessToken.isNullOrEmpty() && refreshToken.isNullOrEmpty()) { - navController.navigate(AuthScreens.LogIn.route) { - popUpTo(0) { - inclusive - } - } - } - } - - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) - - when(deviceConfiguration) { - DeviceConfiguration.MOBILE_PORTRAIT -> { - MainContent( - modifier = Modifier, - navController = navController, - username = username, - notes = notesList, - connectivityState = connectivityState, - columnCount = 2 - ) - } - DeviceConfiguration.MOBILE_LANDSCAPE -> { - MainContent( - modifier = Modifier - .background( - MaterialTheme.colorScheme.surface - ) - .windowInsetsPadding(WindowInsets.displayCutout) - .fillMaxSize() - .consumeWindowInsets(WindowInsets.navigationBars), - navController = navController, - username = username, - notes = notesList, - connectivityState = connectivityState, - columnCount = 3 - ) - } - DeviceConfiguration.TABLET_PORTRAIT, - DeviceConfiguration.TABLET_LANDSCAPE, - DeviceConfiguration.DESKTOP -> { - MainContent( - modifier = Modifier, - navController = navController, - username = username, - notes = notesList, - connectivityState = connectivityState, - columnCount = 3 - ) - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun MainContent( - modifier: Modifier, - navController: NavHostController, - username: String?, - notes: LazyPagingItems, - connectivityState: State, - columnCount: Int, -) { - val context = LocalContext.current - val uuid = remember { UUID.randomUUID() } - val noteViewModel = hiltViewModel() - val noteState by noteViewModel.createNoteState.collectAsStateWithLifecycle() - val creationTime by remember { mutableStateOf(DateFormatter.getCurrentIsoString()) } - - LaunchedEffect(notes.loadState) { - when { - notes.loadState.refresh is LoadState.Error -> { - val error = (notes.loadState.refresh as LoadState.Error).error - Log.e("Home", "Refresh error: ${error.message}") - if (notes.itemCount == 0) { - Toast.makeText( - context, - "Unable to load notes. Showing cached data.", - Toast.LENGTH_SHORT - ).show() - } - } - notes.loadState.append is LoadState.Error -> { - val error = (notes.loadState.append as LoadState.Error).error - Log.e("Home", "Append error: ${error.message}") - } - } - } - - val initials = getInitials(username ?: "U") - - LaunchedEffect(noteState) { - if (noteState.isSuccess) { - navController.navigate(HomeScreens.Home.route) { - popUpTo(HomeScreens.Home.route) { - inclusive = true - } - } - } - } - - LaunchedEffect(noteState.error) { - noteState.error?.let { error -> - Toast.makeText(context, error, Toast.LENGTH_LONG).show() - Log.d( - "CreateNoteScreen", - "CreateNoteScreen: ${noteState.error}" - ) - } - } - - Scaffold( - topBar = { - TopAppBar( - title = { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(end = 4.dp) - ) - if (!connectivityState.value) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.cloud_off), - contentDescription = "Offline", - tint = Color(0x66535364), - ) - } - } - }, - actions = { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.settings), - contentDescription = "Settings", - modifier = Modifier - .clickable { - navController.navigate(HomeScreens.Settings.route) - } - .padding(end = 16.dp) - ) - Text( - modifier = Modifier - .padding(end = 16.dp) - .size(40.dp) - .clip(RoundedCornerShape(12.dp)) - .background(MaterialTheme.colorScheme.primary) - .padding(10.dp), - text = initials, - style = MaterialTheme.typography.titleSmall.copy( - color = MaterialTheme.colorScheme.onPrimary, - textAlign = TextAlign.Center - ) - ) - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.onPrimary - ) - ) - }, - - floatingActionButton = { - FloatingActionButton( - onClick = { - noteViewModel.createNote( - NoteRequest( - id = uuid.toString(), - title = "Note Title", - content = "", - createdAt = creationTime, - updatedAt = creationTime, - ) - ) - navController.navigate(HomeScreens.CreateNote.route) - }, - modifier = Modifier - .size(64.dp) - .background( - brush = Brush.verticalGradient( - colors = listOf( - Color(0xFF58A1F8), - Color(0xFF5A4CF7) - ) - ), - shape = RoundedCornerShape(20.dp) - ), - containerColor = Color.Transparent, - elevation = FloatingActionButtonDefaults.elevation(0.dp) - ) { - Icon( - tint = MaterialTheme.colorScheme.onPrimary, - imageVector = Icons.Default.Add, - contentDescription = "Add icon" - ) - } - }, - contentWindowInsets = WindowInsets.statusBars - ) { innerPadding -> - Column( - modifier = modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.surface) - .padding(innerPadding) - ) { - if (notes.loadState.refresh is LoadState.Loading) { - CircularProgressIndicator( - modifier = Modifier - .size(24.dp) - .align(Alignment.CenterHorizontally) - ) - } else { - LazyVerticalStaggeredGrid( - columns = StaggeredGridCells.Fixed(columnCount), - state = rememberLazyStaggeredGridState(), - modifier = Modifier.weight(1f), - contentPadding = PaddingValues(16.dp), - verticalItemSpacing = 8.dp, - horizontalArrangement = Arrangement.spacedBy(8.dp), - content = { - items( - notes.itemCount, - key = notes.itemKey { it.id } - ) { index -> - notes[index]?.let { - NoteItem( - modifier = Modifier, - navController = navController, - note = it, - notes = notes - ) - } - } - if (notes.loadState.append is LoadState.Loading) { - item(span = StaggeredGridItemSpan.FullLine) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp) - ) - } - } - } - } - ) - } - EmptyState(notes) - } - } -} - -@Composable -private fun EmptyState(notes: LazyPagingItems) { - - if (notes.loadState.refresh !is LoadState.Loading && notes.itemSnapshotList.items.isEmpty() && !notes.loadState.hasError) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(top = 48.dp), - contentAlignment = Alignment.TopCenter - ) { - Text( - text = stringResource(R.string.empty_container_text), - style = MaterialTheme.typography.titleSmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center - ) - ) - } - } -} - -@Composable -fun NoteItem( - modifier: Modifier, - navController: NavHostController = rememberNavController(), - note: Note, - notes: LazyPagingItems -) { - - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) - val isPhone = deviceConfiguration == DeviceConfiguration.MOBILE_PORTRAIT - - var contextMenuNoteId by rememberSaveable { mutableStateOf(null) } - val haptics = LocalHapticFeedback.current - - Column( - modifier = modifier - .combinedClickable( - onClick = { - navController.navigate( - route = HomeScreens.Details( - noteId = note.id - ) - ) - }, - onLongClick = { - haptics.performHapticFeedback(HapticFeedbackType.LongPress) - contextMenuNoteId = note.id - }, - ) - .background( - MaterialTheme.colorScheme.surfaceContainerLowest, - RoundedCornerShape(16.dp) - ) - .padding(16.dp), - horizontalAlignment = Alignment.Start, - ) { - - Text( - text = note.getFormattedCreatedAt(), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = note.title, - style = MaterialTheme.typography.titleMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - - Text( - text = note.content.truncateAtWord(if (isPhone) 150 else 250), - style = MaterialTheme.typography.bodySmall, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - if (contextMenuNoteId != null) { - NoteActionSheet( - isVisible = note.id == contextMenuNoteId, - onDismissSheet = { contextMenuNoteId = null }, - - noteId = contextMenuNoteId ?: "", - notes = notes - ) - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun NoteActionSheet( - isVisible: Boolean, - onDismissSheet: () -> Unit, - noteId: String, - notes: LazyPagingItems -) { - val context = LocalContext.current - val viewModel: NotesViewModel = hiltViewModel() - val deleteNoteState by viewModel.deleteNoteState.collectAsStateWithLifecycle() - val onDeleteClick = { viewModel.deleteNote(noteId = noteId) } - - LaunchedEffect(deleteNoteState) { - when (deleteNoteState) { - is DeleteNoteUiState.Success -> { - delay(2_000L) - notes.refresh() - viewModel.resetDeleteState() - onDismissSheet() - } - is DeleteNoteUiState.Error -> { - Toast.makeText( - context, - (deleteNoteState as DeleteNoteUiState.Error).message, - Toast.LENGTH_SHORT - ).show() - viewModel.resetDeleteState() - } - else -> {} - } - } - - if (isVisible) { - ModalBottomSheet( - onDismissRequest = { - onDismissSheet() - }, - containerColor = MaterialTheme.colorScheme.surfaceContainerLowest, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - ActionContent( - deleteNoteState = deleteNoteState, - onDeleteClick = onDeleteClick, - ) - } - } - } -} - -@Composable -private fun SuccessContent() { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val animationState = rememberLottieAnimatable() - val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.success)) - - - LaunchedEffect(composition) { - composition?.let { - animationState.animate( - composition = it, - iterations = 1 - ) - } - } - - Box( - modifier = Modifier.size(120.dp), - contentAlignment = Alignment.Center - ) { - if (composition != null) { - LottieAnimation( - composition = composition, - progress = { animationState.progress }, - modifier = Modifier.size(120.dp) - ) - } else { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - strokeWidth = 2.dp - ) - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Note deleted successfully!", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurface, - textAlign = TextAlign.Center - ) - } -} - -@Composable -private fun ActionContent( - deleteNoteState: DeleteNoteUiState, - onDeleteClick: () -> Unit, -) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - if (deleteNoteState is DeleteNoteUiState.Success) { - SuccessContent() - } else { - Card( - modifier = Modifier - .fillMaxWidth() - .clip(MaterialTheme.shapes.medium) - .clickable( - enabled = deleteNoteState !is DeleteNoteUiState.Loading - ) { - onDeleteClick() - }, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface - ), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - ListItem( - colors = ListItemDefaults.colors( - containerColor = Color.Transparent - ), - headlineContent = { - Text( - text = when (deleteNoteState) { - is DeleteNoteUiState.Loading -> "Deleting..." - else -> "Delete Note?" - }, - style = MaterialTheme.typography.bodyLarge.copy( - color = if (deleteNoteState is DeleteNoteUiState.Loading) { - MaterialTheme.colorScheme.onSurfaceVariant - } else { - MaterialTheme.colorScheme.error - } - ) - ) - }, - supportingContent = { - Text( - text = "Are you sure you want to delete this note? This action cannot be undone.", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - }, - leadingContent = { - Box( - modifier = Modifier.size(40.dp), - contentAlignment = Alignment.Center - ) { - if (deleteNoteState is DeleteNoteUiState.Loading) { - CircularProgressIndicator( - modifier = Modifier.size(20.dp), - strokeWidth = 2.dp, - color = MaterialTheme.colorScheme.primary - ) - } else { - Icon( - imageVector = Icons.Default.DeleteOutline, - contentDescription = "Delete", - tint = MaterialTheme.colorScheme.error, - modifier = Modifier.size(24.dp) - ) - } - } - } - ) - } - Spacer(modifier = Modifier.height(16.dp)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/presentation/screens/note/SyncRecord.kt b/app/src/main/java/com/example/notemark/main/presentation/screens/note/SyncRecord.kt deleted file mode 100644 index 4699caf..0000000 --- a/app/src/main/java/com/example/notemark/main/presentation/screens/note/SyncRecord.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.notemark.main.presentation.screens.note - -import androidx.room.Entity -import androidx.room.PrimaryKey -import java.util.UUID - -@Entity -data class SyncRecord( - @PrimaryKey - val id: UUID, - val userId: String, - val noteId: String, - val operation: SyncOperation, - val payload: String, - val timeStamp: String -) - -enum class SyncOperation { - CREATE, - UPDATE, - DELETE -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/navigation/navs/AppNavigation.kt b/app/src/main/java/com/example/notemark/navigation/navs/AppNavigation.kt index a060e91..cbedd14 100644 --- a/app/src/main/java/com/example/notemark/navigation/navs/AppNavigation.kt +++ b/app/src/main/java/com/example/notemark/navigation/navs/AppNavigation.kt @@ -3,6 +3,7 @@ package com.example.notemark.navigation.navs import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -12,45 +13,55 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController -import com.example.notemark.auth.presentation.vm.AuthState -import com.example.notemark.auth.presentation.vm.AuthViewModel import com.example.notemark.navigation.graphs.Graph +import com.example.presentation.NoteMarkDefaultScreen +import com.example.presentation.vm.AuthState +import com.example.presentation.vm.AuthViewModel @Composable fun AppNavigation( navController: NavHostController = rememberNavController(), ) { - - val authViewModel: AuthViewModel = hiltViewModel() + val authViewModel: AuthViewModel = androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel() val authState by authViewModel.authState.collectAsStateWithLifecycle() when (authState) { AuthState.Loading -> { - Box( + NoteMarkDefaultScreen( modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } + containerColor = MaterialTheme.colorScheme.background, + content = { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + ) } AuthState.Authenticated -> { - NavHost( - navController = navController, - startDestination = Graph.HOME - ) { - homeNavGraph(navController = navController) - authNavGraph(navController = navController) + NoteMarkDefaultScreen { + NavHost( + navController = navController, + startDestination = Graph.HOME + ) { + homeNavGraph(navController = navController) + authNavGraph(navController = navController) + } } } AuthState.Unauthenticated -> { - NavHost( - navController = navController, - startDestination = Graph.AUTH - ) { - authNavGraph(navController = navController) - homeNavGraph(navController = navController) + NoteMarkDefaultScreen { + NavHost( + navController = navController, + startDestination = Graph.AUTH + ) { + authNavGraph(navController = navController) + homeNavGraph(navController = navController) + } } } } diff --git a/app/src/main/java/com/example/notemark/navigation/navs/authNavGraph.kt b/app/src/main/java/com/example/notemark/navigation/navs/authNavGraph.kt index fa69cc3..fb5c1b2 100644 --- a/app/src/main/java/com/example/notemark/navigation/navs/authNavGraph.kt +++ b/app/src/main/java/com/example/notemark/navigation/navs/authNavGraph.kt @@ -4,38 +4,65 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.navigation -import com.example.notemark.auth.presentation.landing.screens.LandingScreen -import com.example.notemark.auth.presentation.login.screens.LoginScreen -import com.example.notemark.auth.presentation.registration.screens.RegistrationScreen import com.example.notemark.navigation.graphs.Graph import com.example.notemark.navigation.screens.AuthScreens +import com.example.notemark.navigation.screens.HomeScreens +import com.example.presentation.landing.screens.LandingScreen +import com.example.presentation.login.LoginScreen +import com.example.presentation.registration.RegistrationScreen internal fun NavGraphBuilder.authNavGraph( navController: NavHostController, ) { navigation( route = Graph.AUTH, - startDestination = AuthScreens.Landing.route + startDestination = AuthScreens.Landing::class.qualifiedName ?: "" ) { - composable( - route = AuthScreens.Landing.route - ) { + composable { LandingScreen( - navController = navController, + onNavigateToRegistration = { + navController.navigate(AuthScreens.Registration) { + popUpTo(AuthScreens.Landing) { + inclusive = true + } + } + }, + onNavigateToLogIn = { + navController.navigate(AuthScreens.LogIn){ + popUpTo(AuthScreens.Landing) { + inclusive = true + } + } + } ) } - composable( - route = AuthScreens.LogIn.route - ) { + composable { LoginScreen( - navController = navController, + onNavigateToRegistration = { + navController.navigate(AuthScreens.Registration) { + popUpTo(AuthScreens.LogIn) { + inclusive = true + } + } + }, + onNavigateToHome = { + navController.navigate(HomeScreens.Home) { + popUpTo(0) { + inclusive = true + } + } + } ) } - composable( - route = AuthScreens.Registration.route - ) { + composable { RegistrationScreen( - navController = navController, + onNavigateToLogIn = { + navController.navigate(AuthScreens.LogIn) { + popUpTo(AuthScreens.Registration) { + inclusive = true + } + } + }, ) } } diff --git a/app/src/main/java/com/example/notemark/navigation/navs/homeNavGraph.kt b/app/src/main/java/com/example/notemark/navigation/navs/homeNavGraph.kt index 7c0897b..fbcd1e4 100644 --- a/app/src/main/java/com/example/notemark/navigation/navs/homeNavGraph.kt +++ b/app/src/main/java/com/example/notemark/navigation/navs/homeNavGraph.kt @@ -10,17 +10,17 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import androidx.navigation.toRoute import androidx.paging.compose.collectAsLazyPagingItems -import com.example.notemark.AndroidConnectivityObserver -import com.example.notemark.ConnectivityViewModel -import com.example.notemark.main.presentation.screens.ProfileScreen -import com.example.notemark.main.presentation.screens.SettingsScreen -import com.example.notemark.main.presentation.screens.note.CreateNoteScreen -import com.example.notemark.main.presentation.screens.note.DetailsScreen -import com.example.notemark.main.presentation.screens.note.EditNoteScreen -import com.example.notemark.main.presentation.screens.note.HomeScreen -import com.example.notemark.main.presentation.vm.NotesViewModel +import com.example.data.AndroidConnectivityObserver import com.example.notemark.navigation.graphs.Graph +import com.example.notemark.navigation.screens.AuthScreens import com.example.notemark.navigation.screens.HomeScreens +import com.example.presentation.ConnectivityViewModel +import com.example.presentation.NotesViewModel +import com.example.presentation.edit_note.CreateNoteScreen +import com.example.presentation.edit_note.EditNoteScreen +import com.example.presentation.home.HomeScreen +import com.example.presentation.note_details.DetailsScreen +import com.example.presentation.settings.SettingsScreen internal fun NavGraphBuilder.homeNavGraph( navController: NavHostController @@ -28,10 +28,10 @@ internal fun NavGraphBuilder.homeNavGraph( navigation( route = Graph.HOME, - startDestination = HomeScreens.Home.route + startDestination = HomeScreens.Home::class.qualifiedName ?: "" ) { - composable(route = HomeScreens.Home.route) { + composable { val context = LocalContext.current val connectivityViewModel = viewModel { ConnectivityViewModel( @@ -41,14 +41,42 @@ internal fun NavGraphBuilder.homeNavGraph( ) } val viewModel: NotesViewModel = hiltViewModel() + val username = viewModel.username.collectAsStateWithLifecycle() + val accessToken = viewModel.accessToken.collectAsStateWithLifecycle() + val refreshToken = viewModel.refreshToken.collectAsStateWithLifecycle() val notesList = viewModel.notePagingFlow.collectAsLazyPagingItems() val noteUiState = viewModel.createNoteState.collectAsStateWithLifecycle() val connectivityState = connectivityViewModel.isConnected.collectAsStateWithLifecycle() HomeScreen( - navController = navController, + onNavigateToHome = { + navController.navigate(HomeScreens.Home) { + popUpTo(HomeScreens.Home) { + inclusive = true + } + } + }, + onNavigateToLogin = { + navController.navigate(AuthScreens.LogIn) { + popUpTo(0) { + inclusive + } + } + }, + onNavigateToSettings = { navController.navigate(HomeScreens.Settings) }, + onNavigateToCreateNote = { navController.navigate(HomeScreens.CreateNote) }, + onNavigateToDetailsWithId = { + navController.navigate( + HomeScreens.Details( + noteId = it + ) + ) + }, connectivityState = connectivityState, notesList = notesList, - noteUiState = noteUiState + noteUiState = noteUiState, + username = username.value, + accessToken = accessToken.value, + refreshToken = refreshToken.value ) } @@ -66,21 +94,37 @@ internal fun NavGraphBuilder.homeNavGraph( composable { val argument = it.toRoute() EditNoteScreen( - navController = navController, + onNavigateHome = { + navController.navigate(HomeScreens.Home) { + popUpTo(HomeScreens.Home) { + inclusive = true + } + } + }, + onNavigateUp = { + navController.navigateUp() + }, noteId = argument.noteId, ) } - composable(route = HomeScreens.CreateNote.route) { + composable { CreateNoteScreen(navController = navController) } - composable(route = HomeScreens.Profile.route) { - ProfileScreen(navController = navController) - } - - composable(route = HomeScreens.Settings.route) { - SettingsScreen(navController = navController) + composable { + SettingsScreen( + onNavigateToLanding = { + navController.navigate(AuthScreens.Landing) { + popUpTo(0) { + inclusive = true + } + } + }, + onNavigateUp = { + navController.navigateUp() + } + ) } } } diff --git a/app/src/main/java/com/example/notemark/navigation/screens/AuthScreens.kt b/app/src/main/java/com/example/notemark/navigation/screens/AuthScreens.kt index 6b788d7..3311659 100644 --- a/app/src/main/java/com/example/notemark/navigation/screens/AuthScreens.kt +++ b/app/src/main/java/com/example/notemark/navigation/screens/AuthScreens.kt @@ -3,15 +3,15 @@ package com.example.notemark.navigation.screens import kotlinx.serialization.Serializable @Serializable -sealed class AuthScreens(val route: String) { +sealed interface AuthScreens { @Serializable - object Landing: AuthScreens("landing_screen") + object Landing: AuthScreens @Serializable - object LogIn : AuthScreens("sign_in") + object LogIn : AuthScreens @Serializable - object Registration : AuthScreens("sign_up") + object Registration : AuthScreens } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/navigation/screens/HomeScreens.kt b/app/src/main/java/com/example/notemark/navigation/screens/HomeScreens.kt index 8511db9..0e90457 100644 --- a/app/src/main/java/com/example/notemark/navigation/screens/HomeScreens.kt +++ b/app/src/main/java/com/example/notemark/navigation/screens/HomeScreens.kt @@ -3,28 +3,27 @@ package com.example.notemark.navigation.screens import kotlinx.serialization.Serializable @Serializable -sealed class HomeScreens(val route: String) { +sealed interface HomeScreens { @Serializable - object Home : HomeScreens("home") + data object Home : HomeScreens @Serializable - object Profile : HomeScreens("profile") + object Profile : HomeScreens @Serializable - object CreateNote : HomeScreens("create_note") + object CreateNote : HomeScreens @Serializable data class EditNote( val noteId: String - ): HomeScreens("edit_note") + ): HomeScreens @Serializable - object Settings : HomeScreens("settings") + object Settings : HomeScreens @Serializable data class Details( val noteId: String - ): HomeScreens("details") - + ): HomeScreens } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/ui/theme/Type.kt b/app/src/main/java/com/example/notemark/ui/theme/Type.kt index 8b66275..64a611b 100644 --- a/app/src/main/java/com/example/notemark/ui/theme/Type.kt +++ b/app/src/main/java/com/example/notemark/ui/theme/Type.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp -import com.example.notemark.R +import com.example.notemark.core.presentation.R val Typography = Typography( titleLarge = TextStyle( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79d4a8e..c5b85e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,29 +1,3 @@ NoteMark - Request time out - Too many request - no internet - Server error occur. - Unknown error, please reach the support center. - Disk full, please clean the cache. - Invalid email - For some reason note is not deleted, please try again or contact support! - Log out - Last sync: - Sync Data - Sync interval - settings - Manual Only - 15 minutes - 30 minutes - 1 hour - You’ve got an empty board,\n let’s place your first note on it! - all notes - Date created - Last edited - update note - Discard Changes? - You have unsaved changes. If you discard now, all changes will be lost. - Discard - Keep Editing \ No newline at end of file diff --git a/auth/data/build.gradle.kts b/auth/data/build.gradle.kts index af42db5..f910a75 100644 --- a/auth/data/build.gradle.kts +++ b/auth/data/build.gradle.kts @@ -1,17 +1,22 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) + alias(libs.plugins.notemark.android.library) + alias(libs.plugins.ktor.client.convention) + alias(libs.plugins.compose.compiler) + id("com.google.dagger.hilt.android") // ✅ ADD THIS + alias(libs.plugins.ksp) // ✅ ADD THIS } android { - namespace = "com.example.data" + namespace = "com.example.notemark.auth.data" } dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + // ✅ ADD HILT DEPENDENCIES + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) + with(projects) { + implementation(auth.domain) + implementation(core.data) + implementation(core.domain) + } } \ No newline at end of file diff --git a/auth/data/src/main/java/com/example/data/di/AuthModule.kt b/auth/data/src/main/java/com/example/data/di/AuthModule.kt new file mode 100644 index 0000000..0e36a77 --- /dev/null +++ b/auth/data/src/main/java/com/example/data/di/AuthModule.kt @@ -0,0 +1,66 @@ +package com.example.data.di + +import com.example.data.SessionManager +import com.example.data.remote.LoginServiceImpl +import com.example.data.remote.LogoutServiceImpl +import com.example.data.remote.RegistrationServiceImpl +import com.example.domain.LoginService +import com.example.domain.LogoutService +import com.example.domain.RegistrationService +import com.example.domain.SessionRepository +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import io.ktor.client.HttpClient +import io.ktor.client.engine.android.Android +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class AuthModule { + + @Binds + @Singleton + abstract fun bindSessionRepository( + sessionManager: SessionManager + ): SessionRepository + + companion object { + + @Provides + @Singleton + fun provideHttpClient(): HttpClient = HttpClient(Android) { + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + }) + } + install(Logging) { + level = LogLevel.ALL + } + } + + @Provides + @Singleton + fun provideRegistrationService(client: HttpClient): RegistrationService = + RegistrationServiceImpl(client) + + @Provides + @Singleton + fun provideLoginService(client: HttpClient): LoginService = + LoginServiceImpl(client) + + @Provides + @Singleton + fun provideLogoutService(client: HttpClient): LogoutService = + LogoutServiceImpl(client) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/LoginServiceImpl.kt b/auth/data/src/main/java/com/example/data/remote/LoginServiceImpl.kt similarity index 76% rename from app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/LoginServiceImpl.kt rename to auth/data/src/main/java/com/example/data/remote/LoginServiceImpl.kt index ce6f42e..c48e629 100644 --- a/app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/LoginServiceImpl.kt +++ b/auth/data/src/main/java/com/example/data/remote/LoginServiceImpl.kt @@ -1,9 +1,9 @@ -package com.example.notemark.auth.data.remote.repositoryImpl +package com.example.data.remote -import com.example.notemark.core.HttpRoutes -import com.example.notemark.auth.data.remote.api.LoginService -import com.example.notemark.auth.domain.model.LoginModel -import com.example.notemark.auth.domain.model.LoginResponse +import com.example.domain.HttpRoutes +import com.example.domain.LoginService +import com.example.domain.model.LoginModel +import com.example.domain.model.LoginResponse import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.header @@ -33,6 +33,7 @@ class LoginServiceImpl( null } } catch (e: Exception) { + e.printStackTrace() null } } diff --git a/app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/LogoutServiceImpl.kt b/auth/data/src/main/java/com/example/data/remote/LogoutServiceImpl.kt similarity index 76% rename from app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/LogoutServiceImpl.kt rename to auth/data/src/main/java/com/example/data/remote/LogoutServiceImpl.kt index cdb55fc..a1d9a13 100644 --- a/app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/LogoutServiceImpl.kt +++ b/auth/data/src/main/java/com/example/data/remote/LogoutServiceImpl.kt @@ -1,8 +1,8 @@ -package com.example.notemark.auth.data.remote.repositoryImpl +package com.example.data.remote -import com.example.notemark.auth.data.remote.api.LogoutService -import com.example.notemark.core.HttpRoutes -import com.example.notemark.core.factory.AccessTokenResponse +import com.example.domain.HttpRoutes +import com.example.domain.LogoutService +import com.example.domain.model.AccessTokenResponse import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.header @@ -28,7 +28,8 @@ class LogoutServiceImpl( } else { null } - } catch (e: Exception){ + } catch (e: Exception) { + e.printStackTrace() null } } diff --git a/app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/RegistrationServiceImpl.kt b/auth/data/src/main/java/com/example/data/remote/RegistrationServiceImpl.kt similarity index 86% rename from app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/RegistrationServiceImpl.kt rename to auth/data/src/main/java/com/example/data/remote/RegistrationServiceImpl.kt index c14a890..cafa52a 100644 --- a/app/src/main/java/com/example/notemark/auth/data/remote/repositoryImpl/RegistrationServiceImpl.kt +++ b/auth/data/src/main/java/com/example/data/remote/RegistrationServiceImpl.kt @@ -1,8 +1,8 @@ -package com.example.notemark.auth.data.remote.repositoryImpl +package com.example.data.remote -import com.example.notemark.auth.data.remote.api.RegistrationService -import com.example.notemark.auth.domain.model.RegistrationModel -import com.example.notemark.core.HttpRoutes +import com.example.domain.model.RegistrationModel +import com.example.domain.RegistrationService +import com.example.domain.HttpRoutes import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.header diff --git a/auth/domain/build.gradle.kts b/auth/domain/build.gradle.kts index 1870010..28db768 100644 --- a/auth/domain/build.gradle.kts +++ b/auth/domain/build.gradle.kts @@ -1,13 +1,7 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) + alias(libs.plugins.notemark.jvm.library) } dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + } \ No newline at end of file diff --git a/auth/domain/src/main/java/com/example/domain/LoginService.kt b/auth/domain/src/main/java/com/example/domain/LoginService.kt new file mode 100644 index 0000000..1c6a3ac --- /dev/null +++ b/auth/domain/src/main/java/com/example/domain/LoginService.kt @@ -0,0 +1,10 @@ +package com.example.domain + +import com.example.domain.model.LoginModel +import com.example.domain.model.LoginResponse + +interface LoginService { + + suspend fun login(body: LoginModel): LoginResponse? + +} \ No newline at end of file diff --git a/auth/domain/src/main/java/com/example/domain/LogoutService.kt b/auth/domain/src/main/java/com/example/domain/LogoutService.kt new file mode 100644 index 0000000..9d9c031 --- /dev/null +++ b/auth/domain/src/main/java/com/example/domain/LogoutService.kt @@ -0,0 +1,9 @@ +package com.example.domain + +import com.example.domain.model.AccessTokenResponse + +interface LogoutService { + + suspend fun logout(refreshToken: String): AccessTokenResponse? + +} \ No newline at end of file diff --git a/auth/domain/src/main/java/com/example/domain/RegistrationService.kt b/auth/domain/src/main/java/com/example/domain/RegistrationService.kt new file mode 100644 index 0000000..cf4c454 --- /dev/null +++ b/auth/domain/src/main/java/com/example/domain/RegistrationService.kt @@ -0,0 +1,9 @@ +package com.example.domain + +import com.example.domain.model.RegistrationModel + +interface RegistrationService { + + suspend fun register(body: RegistrationModel): RegistrationModel? + +} \ No newline at end of file diff --git a/auth/domain/src/main/java/com/example/domain/model/AccessTokenRequest.kt b/auth/domain/src/main/java/com/example/domain/model/AccessTokenRequest.kt new file mode 100644 index 0000000..997f46a --- /dev/null +++ b/auth/domain/src/main/java/com/example/domain/model/AccessTokenRequest.kt @@ -0,0 +1,6 @@ +package com.example.domain.model + +data class AccessTokenRequest( + val refreshToken: String +) + diff --git a/auth/domain/src/main/java/com/example/domain/model/AccessTokenResponse.kt b/auth/domain/src/main/java/com/example/domain/model/AccessTokenResponse.kt new file mode 100644 index 0000000..49a1cb2 --- /dev/null +++ b/auth/domain/src/main/java/com/example/domain/model/AccessTokenResponse.kt @@ -0,0 +1,6 @@ +package com.example.domain.model + +data class AccessTokenResponse( + val accessToken: String, + val refreshToken: String, +) \ No newline at end of file diff --git a/auth/domain/src/main/java/com/example/domain/model/LoginModel.kt b/auth/domain/src/main/java/com/example/domain/model/LoginModel.kt new file mode 100644 index 0000000..627d2f0 --- /dev/null +++ b/auth/domain/src/main/java/com/example/domain/model/LoginModel.kt @@ -0,0 +1,6 @@ +package com.example.domain.model + +data class LoginModel( + val email: String, + val password: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/domain/model/LoginResponse.kt b/auth/domain/src/main/java/com/example/domain/model/LoginResponse.kt similarity index 52% rename from app/src/main/java/com/example/notemark/auth/domain/model/LoginResponse.kt rename to auth/domain/src/main/java/com/example/domain/model/LoginResponse.kt index ac2b8c4..f231fd4 100644 --- a/app/src/main/java/com/example/notemark/auth/domain/model/LoginResponse.kt +++ b/auth/domain/src/main/java/com/example/domain/model/LoginResponse.kt @@ -1,8 +1,5 @@ -package com.example.notemark.auth.domain.model +package com.example.domain.model -import kotlinx.serialization.Serializable - -@Serializable data class LoginResponse( val accessToken: String, val refreshToken: String, diff --git a/app/src/main/java/com/example/notemark/auth/domain/model/RegistrationModel.kt b/auth/domain/src/main/java/com/example/domain/model/RegistrationModel.kt similarity index 51% rename from app/src/main/java/com/example/notemark/auth/domain/model/RegistrationModel.kt rename to auth/domain/src/main/java/com/example/domain/model/RegistrationModel.kt index e6908cd..d3d8d6e 100644 --- a/app/src/main/java/com/example/notemark/auth/domain/model/RegistrationModel.kt +++ b/auth/domain/src/main/java/com/example/domain/model/RegistrationModel.kt @@ -1,8 +1,6 @@ -package com.example.notemark.auth.domain.model +package com.example.domain.model -import kotlinx.serialization.Serializable -@Serializable data class RegistrationModel( val username: String, val email: String, diff --git a/app/src/main/java/com/example/notemark/auth/domain/model/SuccessfulLogin.kt b/auth/domain/src/main/java/com/example/domain/model/SuccessfulLogin.kt similarity index 65% rename from app/src/main/java/com/example/notemark/auth/domain/model/SuccessfulLogin.kt rename to auth/domain/src/main/java/com/example/domain/model/SuccessfulLogin.kt index 386a0db..1f0b1eb 100644 --- a/app/src/main/java/com/example/notemark/auth/domain/model/SuccessfulLogin.kt +++ b/auth/domain/src/main/java/com/example/domain/model/SuccessfulLogin.kt @@ -1,4 +1,4 @@ -package com.example.notemark.auth.domain.model +package com.example.domain.model data class SuccessfulLogin( val accessToken: String, diff --git a/auth/presentation/build.gradle.kts b/auth/presentation/build.gradle.kts index fc0e43d..e5b6d50 100644 --- a/auth/presentation/build.gradle.kts +++ b/auth/presentation/build.gradle.kts @@ -1,17 +1,21 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) + alias(libs.plugins.notemark.android.library.compose) + id("com.google.dagger.hilt.android") + alias(libs.plugins.ksp) } android { - namespace = "com.example.presentation" + namespace = "com.example.notemark.auth.presentation" } dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + with(projects) { + implementation(core.domain) + implementation(auth.domain) + implementation(auth.data) + implementation(core.presentation) + } + + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/landing/screens/LandingScreen.kt b/auth/presentation/src/main/java/com/example/presentation/landing/screens/LandingScreen.kt similarity index 80% rename from app/src/main/java/com/example/notemark/auth/presentation/landing/screens/LandingScreen.kt rename to auth/presentation/src/main/java/com/example/presentation/landing/screens/LandingScreen.kt index 4a66722..3451007 100644 --- a/app/src/main/java/com/example/notemark/auth/presentation/landing/screens/LandingScreen.kt +++ b/auth/presentation/src/main/java/com/example/presentation/landing/screens/LandingScreen.kt @@ -1,20 +1,16 @@ -package com.example.notemark.auth.presentation.landing.screens +package com.example.presentation.landing.screens import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets 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.layout.statusBars -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll @@ -22,7 +18,6 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable @@ -33,15 +28,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import com.example.notemark.R -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.navigation.screens.AuthScreens +import com.example.notemark.core.presentation.R +import com.example.presentation.DeviceConfiguration @Composable fun LandingScreen( - navController: NavHostController = rememberNavController(), + onNavigateToRegistration: () -> Unit = {}, + onNavigateToLogIn: () -> Unit = {}, ) { val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) @@ -60,7 +53,8 @@ fun LandingScreen( ) LandingSheet( - navController = navController, + onNavigateToLogIn = onNavigateToLogIn, + onNavigateToRegistration = onNavigateToRegistration, modifier = Modifier .align(Alignment.BottomCenter) .clip(shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) @@ -83,7 +77,8 @@ fun LandingScreen( ) LandingSheet( - navController = navController, + onNavigateToLogIn = onNavigateToLogIn, + onNavigateToRegistration = onNavigateToRegistration, modifier = Modifier .weight(1.2f) .clip(shape = RoundedCornerShape(topStart = 24.dp, bottomStart = 24.dp)), @@ -107,7 +102,8 @@ fun LandingScreen( ) LandingSheet( - navController = navController, + onNavigateToLogIn = onNavigateToLogIn, + onNavigateToRegistration = onNavigateToRegistration, modifier = Modifier .padding(horizontal = 48.dp) .clip(shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) @@ -120,8 +116,9 @@ fun LandingScreen( @Composable fun LandingSheet( - navController: NavHostController, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onNavigateToLogIn: () -> Unit = {}, + onNavigateToRegistration: () -> Unit = {}, ) { Column( modifier = modifier @@ -146,13 +143,7 @@ fun LandingSheet( Spacer(modifier = Modifier.height(32.dp)) Button( - onClick = { - navController.navigate(AuthScreens.Registration.route) { - popUpTo(AuthScreens.Landing.route) { - inclusive = true - } - } - }, + onClick = { onNavigateToRegistration() }, modifier = Modifier .fillMaxWidth() .height(56.dp), @@ -171,13 +162,7 @@ fun LandingSheet( Spacer(modifier = Modifier.height(8.dp)) OutlinedButton( - onClick = { - navController.navigate(AuthScreens.LogIn.route){ - popUpTo(AuthScreens.Landing.route) { - inclusive = true - } - } - }, + onClick = { onNavigateToLogIn() }, modifier = Modifier .fillMaxWidth() .height(56.dp), diff --git a/auth/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt b/auth/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt new file mode 100644 index 0000000..8e559e5 --- /dev/null +++ b/auth/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt @@ -0,0 +1,136 @@ +package com.example.presentation.login + +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.WindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +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.unit.dp +import com.example.presentation.login.components.LoginHeaderSection +import com.example.presentation.login.components.LoginSheet +import com.example.presentation.DeviceConfiguration + +@Composable +fun LoginScreen( + onNavigateToHome: () -> Unit = {}, + onNavigateToRegistration: () -> Unit = {}, +) { + val snackbarHostState = remember { SnackbarHostState() } + + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + modifier = Modifier.fillMaxSize(), + contentWindowInsets = WindowInsets.statusBars + ) { innerPadding -> + + val rootModifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .clip( + RoundedCornerShape( + topStart = 15.dp, + topEnd = 15.dp + ) + ) + .background(MaterialTheme.colorScheme.surfaceContainerLowest) + .padding( + horizontal = 16.dp, + vertical = 24.dp + ) + .consumeWindowInsets(WindowInsets.navigationBars) + + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) + + when(deviceConfiguration) { + DeviceConfiguration.MOBILE_PORTRAIT -> { + Column( + modifier = rootModifier, + ) { + LoginHeaderSection( + modifier = Modifier.fillMaxWidth() + ) + LoginSheet( + onNavigateToHome = onNavigateToHome, + onNavigateToRegistration = onNavigateToRegistration, + modifier = Modifier + .fillMaxWidth() + .padding(top = 32.dp), + snackbarHostState = snackbarHostState + ) + } + } + DeviceConfiguration.MOBILE_LANDSCAPE -> { + Row( + modifier = rootModifier + .windowInsetsPadding(WindowInsets.displayCutout) + .padding( + horizontal = 32.dp + ), + horizontalArrangement = Arrangement.spacedBy(32.dp) + ) { + LoginHeaderSection( + modifier = Modifier + .weight(1f) + ) + LoginSheet( + onNavigateToHome = onNavigateToHome, + onNavigateToRegistration = onNavigateToRegistration, + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + snackbarHostState = snackbarHostState + ) + } + } + DeviceConfiguration.TABLET_PORTRAIT, + DeviceConfiguration.TABLET_LANDSCAPE, + DeviceConfiguration.DESKTOP -> { + Column( + modifier = rootModifier + .verticalScroll(rememberScrollState()) + .padding(top = 48.dp), + verticalArrangement = Arrangement.spacedBy(32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + LoginHeaderSection( + modifier = Modifier + .widthIn(max = 540.dp), + alignment = Alignment.CenterHorizontally + ) + LoginSheet( + onNavigateToHome = onNavigateToHome, + onNavigateToRegistration = onNavigateToRegistration, + modifier = Modifier + .widthIn(max = 540.dp), + snackbarHostState = snackbarHostState + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/login/vm/LoginViewModel.kt b/auth/presentation/src/main/java/com/example/presentation/login/LoginViewModel.kt similarity index 87% rename from app/src/main/java/com/example/notemark/auth/presentation/login/vm/LoginViewModel.kt rename to auth/presentation/src/main/java/com/example/presentation/login/LoginViewModel.kt index c25c9da..3ed146f 100644 --- a/app/src/main/java/com/example/notemark/auth/presentation/login/vm/LoginViewModel.kt +++ b/auth/presentation/src/main/java/com/example/presentation/login/LoginViewModel.kt @@ -1,14 +1,14 @@ -package com.example.notemark.auth.presentation.login.vm +package com.example.presentation.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.notemark.auth.data.remote.api.LoginService -import com.example.notemark.auth.data.remote.api.LogoutService -import com.example.notemark.auth.domain.model.LoginModel -import com.example.notemark.auth.domain.model.LoginResponse -import com.example.notemark.core.manager.SessionManager +import com.example.domain.LoginService +import com.example.domain.LogoutService +import com.example.domain.SessionRepository +import com.example.domain.model.LoginModel +import com.example.domain.model.LoginResponse import dagger.hilt.android.lifecycle.HiltViewModel -import jakarta.inject.Inject +import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -19,7 +19,7 @@ import kotlinx.coroutines.launch class LoginViewModel @Inject constructor( private val loginService: LoginService, private val logoutService: LogoutService, - private val sessionManager: SessionManager + private val sessionManager: SessionRepository ): ViewModel() { private val _loginState = MutableStateFlow(LoginState.Idle) diff --git a/auth/presentation/src/main/java/com/example/presentation/login/components/LoginHeaderSection.kt b/auth/presentation/src/main/java/com/example/presentation/login/components/LoginHeaderSection.kt new file mode 100644 index 0000000..c4b0b56 --- /dev/null +++ b/auth/presentation/src/main/java/com/example/presentation/login/components/LoginHeaderSection.kt @@ -0,0 +1,28 @@ +package com.example.presentation.login.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +internal fun LoginHeaderSection( + alignment: Alignment.Horizontal = Alignment.Start, + modifier: Modifier +) { + Column( + modifier = modifier, + horizontalAlignment = alignment + ) { + Text( + text = "Log In", + style = MaterialTheme.typography.titleLarge + ) + Text( + text = "Capture your thoughts and ideas.", + style = MaterialTheme.typography.bodyLarge + ) + } +} \ No newline at end of file diff --git a/auth/presentation/src/main/java/com/example/presentation/login/components/LoginSheet.kt b/auth/presentation/src/main/java/com/example/presentation/login/components/LoginSheet.kt new file mode 100644 index 0000000..adba060 --- /dev/null +++ b/auth/presentation/src/main/java/com/example/presentation/login/components/LoginSheet.kt @@ -0,0 +1,129 @@ +package com.example.presentation.login.components + +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.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult +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.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.example.presentation.components.NoteMarkButton +import com.example.presentation.components.NoteMarkLink +import com.example.presentation.components.NoteMarkTextField +import com.example.presentation.login.LoginState +import com.example.presentation.login.LoginViewModel +import com.example.presentation.isValidEmail +import com.example.presentation.vm.AuthViewModel +import kotlinx.coroutines.launch + +@Composable +fun LoginSheet( + modifier: Modifier = Modifier, + onNavigateToHome: () -> Unit = {}, + snackbarHostState : SnackbarHostState, + onNavigateToRegistration: () -> Unit = {}, +) { + + val scope = rememberCoroutineScope() + val loginViewModel: LoginViewModel = hiltViewModel() + val authViewModel: AuthViewModel = hiltViewModel() + val state by loginViewModel.loginState.collectAsStateWithLifecycle() + + LaunchedEffect(state) { + when (state) { + is LoginState.Success -> { + authViewModel.refreshAuthState() + onNavigateToHome() + + loginViewModel.clearState() + } + is LoginState.Error -> { + scope.launch { + val result = snackbarHostState + .showSnackbar( + message = "Invalid login credentials", + actionLabel = "", + duration = SnackbarDuration.Short + ) + when (result) { + SnackbarResult.ActionPerformed -> {} + SnackbarResult.Dismissed -> {} + } + } + } + is LoginState.Idle -> {} + is LoginState.Loading -> {} + } + } + + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var isDisabled by remember { mutableStateOf(true) } + val focusManager = LocalFocusManager.current + + isDisabled = email.isBlank() || password.isBlank() || !isValidEmail(email) + + Column( + modifier = modifier + ) { + NoteMarkTextField( + text = email, + onValueChange = { email = it }, + label = "Email", + hint = "john.doe@example.com", + isInputSecret = false, + modifier = Modifier.fillMaxWidth(), + focusManager = focusManager, + focusDirection = FocusDirection.Down, + imeAction = ImeAction.Next, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + NoteMarkTextField( + text = password, + onValueChange = { password = it }, + label = "Password", + hint = "Password", + isInputSecret = true, + modifier = Modifier.fillMaxWidth(), + focusManager = focusManager, + focusDirection = FocusDirection.Down, + imeAction = ImeAction.Done, + ) + + Spacer(modifier = Modifier.height(24.dp)) + + NoteMarkButton( + text = if (state is LoginState.Loading) "" else "Log in", + onClick = { + loginViewModel.loginUser(email, password) + }, + modifier = Modifier.fillMaxWidth(), + enabled = !isDisabled, + isLoading = state is LoginState.Loading + ) + + Spacer(modifier = Modifier.height(24.dp)) + + NoteMarkLink( + text = "Don't have an account?", + onClick = { onNavigateToRegistration() }, + modifier = Modifier.fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/auth/presentation/src/main/java/com/example/presentation/registration/RegistrationScreen.kt b/auth/presentation/src/main/java/com/example/presentation/registration/RegistrationScreen.kt new file mode 100644 index 0000000..5d54d88 --- /dev/null +++ b/auth/presentation/src/main/java/com/example/presentation/registration/RegistrationScreen.kt @@ -0,0 +1,125 @@ +package com.example.presentation.registration + +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.WindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.presentation.registration.components.RegistrationHeader +import com.example.presentation.registration.components.RegistrationSheet +import com.example.presentation.DeviceConfiguration + +@Composable +fun RegistrationScreen( + onNavigateToLogIn: () -> Unit = {}, +) { + val snackbarHostState = remember { SnackbarHostState() } + + Scaffold( + modifier = Modifier.fillMaxSize(), + contentWindowInsets = WindowInsets.statusBars, + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + ) { innerPadding -> + + val rootModifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .background( + color = MaterialTheme.colorScheme.surfaceContainerLowest, + shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) + ) + .padding(horizontal = 16.dp, vertical = 24.dp) + .consumeWindowInsets(WindowInsets.navigationBars) + + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val deviceConfiguration = DeviceConfiguration.Companion.fromWindowSizeClass(windowSizeClass) + + when(deviceConfiguration) { + DeviceConfiguration.MOBILE_PORTRAIT -> { + Column( + modifier = rootModifier, + ) { + RegistrationHeader( + modifier = Modifier.fillMaxWidth() + ) + RegistrationSheet( + onNavigateToLogIn = onNavigateToLogIn, + modifier = Modifier + .fillMaxWidth() + .padding(top = 32.dp), + snackbarHostState = snackbarHostState + ) + } + } + DeviceConfiguration.MOBILE_LANDSCAPE -> { + Row( + modifier = rootModifier + .windowInsetsPadding(WindowInsets.displayCutout) + .padding( + horizontal = 32.dp + ), + horizontalArrangement = Arrangement.spacedBy(32.dp) + ) { + RegistrationHeader( + modifier = Modifier + .weight(1f) + ) + RegistrationSheet( + onNavigateToLogIn = onNavigateToLogIn, + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + snackbarHostState = snackbarHostState + ) + } + } + DeviceConfiguration.TABLET_PORTRAIT, + DeviceConfiguration.TABLET_LANDSCAPE, + DeviceConfiguration.DESKTOP -> { + Column( + modifier = rootModifier + .verticalScroll(rememberScrollState()) + .padding(top = 48.dp), + verticalArrangement = Arrangement.spacedBy(32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + RegistrationHeader( + modifier = Modifier + .widthIn(max = 540.dp), + alignment = Alignment.CenterHorizontally + ) + RegistrationSheet( + onNavigateToLogIn = onNavigateToLogIn, + modifier = Modifier + .widthIn(max = 540.dp), + snackbarHostState = snackbarHostState + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/registration/vm/RegistrationViewModel.kt b/auth/presentation/src/main/java/com/example/presentation/registration/RegistrationViewModel.kt similarity index 90% rename from app/src/main/java/com/example/notemark/auth/presentation/registration/vm/RegistrationViewModel.kt rename to auth/presentation/src/main/java/com/example/presentation/registration/RegistrationViewModel.kt index 37dbace..03c7f02 100644 --- a/app/src/main/java/com/example/notemark/auth/presentation/registration/vm/RegistrationViewModel.kt +++ b/auth/presentation/src/main/java/com/example/presentation/registration/RegistrationViewModel.kt @@ -1,11 +1,11 @@ -package com.example.notemark.auth.presentation.registration.vm +package com.example.presentation.registration import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.notemark.auth.data.remote.api.RegistrationService -import com.example.notemark.auth.domain.model.RegistrationModel +import com.example.domain.RegistrationService +import com.example.domain.model.RegistrationModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject diff --git a/auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationHeader.kt b/auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationHeader.kt new file mode 100644 index 0000000..268d0ca --- /dev/null +++ b/auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationHeader.kt @@ -0,0 +1,34 @@ +package com.example.presentation.registration.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +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 + +@Composable +internal fun RegistrationHeader( + modifier: Modifier = Modifier, + alignment: Alignment.Horizontal = Alignment.Start +) { + Column( + modifier = modifier, + horizontalAlignment = alignment + ) { + Text( + text = "Create account", + style = MaterialTheme.typography.titleLarge + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Capture your thoughts and ideas.", + style = MaterialTheme.typography.bodyLarge + ) + } +} \ No newline at end of file diff --git a/auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationSheet.kt b/auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationSheet.kt new file mode 100644 index 0000000..26d4af5 --- /dev/null +++ b/auth/presentation/src/main/java/com/example/presentation/registration/components/RegistrationSheet.kt @@ -0,0 +1,169 @@ +package com.example.presentation.registration.components + +import android.util.Log +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult +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.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.example.presentation.components.NoteMarkButton +import com.example.presentation.components.NoteMarkLink +import com.example.presentation.components.NoteMarkTextField +import com.example.presentation.registration.RegistrationState +import com.example.presentation.registration.RegistrationViewModel +import com.example.presentation.FormValidation +import com.example.presentation.ValidationResult +import kotlinx.coroutines.launch + +@Composable +internal fun RegistrationSheet( + modifier: Modifier = Modifier, + onNavigateToLogIn: () -> Unit = {}, + snackbarHostState : SnackbarHostState +) { + val scope = rememberCoroutineScope() + val registrationViewModel: RegistrationViewModel = hiltViewModel() + val registrationState by registrationViewModel.registrationState + + var username by remember { mutableStateOf("") } + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var repeatPassword by remember { mutableStateOf("") } + val focusManager = LocalFocusManager.current + + LaunchedEffect(registrationState) { + when(registrationState) { + is RegistrationState.Success -> { + onNavigateToLogIn() + registrationViewModel.clearState() + } + is RegistrationState.Error -> { + Log.e("RegistrationScreen", "Registration failed: ${(registrationState as RegistrationState.Error).message}") + scope.launch { + val result = snackbarHostState + .showSnackbar( + message = "Invalid registration credentials: ${(registrationState as RegistrationState.Error).message}", + actionLabel = "", + duration = SnackbarDuration.Short + ) + when (result) { + SnackbarResult.ActionPerformed -> {} + SnackbarResult.Dismissed -> {} + } + } + } + RegistrationState.Idle -> {} + RegistrationState.Loading -> {} + } + } + + val usernameValidation = FormValidation.validateUsername(username) + val emailValidation = FormValidation.validateEmail(email) + val passwordValidation = FormValidation.validatePassword(password) + val repeatPasswordValidation = FormValidation.validateRepeatPassword(password, repeatPassword) + + val isFormValid = FormValidation.isFormValid(username, email, password, repeatPassword) + + Column( + modifier = modifier + .fillMaxSize() + .padding(top = 24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + NoteMarkTextField( + text = username, + onValueChange = { username = it }, + label = "Username", + hint = "Enter username", + isInputSecret = false, + focusManager = focusManager, + focusDirection = FocusDirection.Down, + imeAction = ImeAction.Next, + errorText = if (usernameValidation is ValidationResult.Error) usernameValidation.message else null, + isError = usernameValidation is ValidationResult.Error, + showFocusText = "Use between 3 and 20 characters for your username." + ) + + NoteMarkTextField( + text = email, + onValueChange = { email = it }, + label = "Email", + hint = "Enter email", + isInputSecret = false, + focusManager = focusManager, + focusDirection = FocusDirection.Down, + imeAction = ImeAction.Next, + errorText = if (emailValidation is ValidationResult.Error) emailValidation.message else null, + isError = emailValidation is ValidationResult.Error + ) + + NoteMarkTextField( + text = password, + onValueChange = { password = it }, + label = "Password", + hint = "Enter password", + isInputSecret = true, + focusManager = focusManager, + focusDirection = FocusDirection.Down, + imeAction = ImeAction.Next, + errorText = if (passwordValidation is ValidationResult.Error) passwordValidation.message else null, + isError = passwordValidation is ValidationResult.Error, + showFocusText = "Use 8+ characters with a number or symbol for better security." + ) + + NoteMarkTextField( + text = repeatPassword, + onValueChange = { repeatPassword = it }, + label = "Repeat Password", + hint = "Repeat password", + isInputSecret = true, + focusManager = focusManager, + focusDirection = FocusDirection.Down, + imeAction = ImeAction.Done, + errorText = if (repeatPasswordValidation is ValidationResult.Error) repeatPasswordValidation.message else null, + isError = repeatPasswordValidation is ValidationResult.Error + ) + + Spacer(modifier = Modifier.height(16.dp)) + + NoteMarkButton( + text = if (registrationState is RegistrationState.Loading) "" else "Create account", + onClick = { + registrationViewModel.registerUser( + username = username, + email = email, + password = password + ) + }, + enabled = isFormValid, + modifier = Modifier.fillMaxWidth(), + isLoading = registrationState is RegistrationState.Loading + ) + + Spacer(modifier = Modifier.height(8.dp)) + + NoteMarkLink( + text = "Already have an account?", + onClick = { onNavigateToLogIn() }, + modifier = Modifier.fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/vm/AuthViewModel.kt b/auth/presentation/src/main/java/com/example/presentation/vm/AuthViewModel.kt similarity index 81% rename from app/src/main/java/com/example/notemark/auth/presentation/vm/AuthViewModel.kt rename to auth/presentation/src/main/java/com/example/presentation/vm/AuthViewModel.kt index 4b42dd9..abe40c0 100644 --- a/app/src/main/java/com/example/notemark/auth/presentation/vm/AuthViewModel.kt +++ b/auth/presentation/src/main/java/com/example/presentation/vm/AuthViewModel.kt @@ -1,7 +1,7 @@ -package com.example.notemark.auth.presentation.vm +package com.example.presentation.vm import androidx.lifecycle.ViewModel -import com.example.notemark.core.manager.SessionManager +import com.example.domain.SessionRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -10,15 +10,13 @@ import javax.inject.Inject @HiltViewModel class AuthViewModel @Inject constructor( - private val sessionManager: SessionManager + private val sessionManager: SessionRepository ) : ViewModel() { private val _authState = MutableStateFlow(AuthState.Loading) val authState: StateFlow = _authState.asStateFlow() - init { - checkAuthenticationStatus() - } + init { checkAuthenticationStatus() } private fun checkAuthenticationStatus() { diff --git a/app/src/main/res/drawable/image.png b/auth/presentation/src/main/res/drawable/image.png similarity index 100% rename from app/src/main/res/drawable/image.png rename to auth/presentation/src/main/res/drawable/image.png diff --git a/build-logic/.kotlin/errors/errors-1768472990915.log b/build-logic/.kotlin/errors/errors-1768472990915.log new file mode 100644 index 0000000..d05de87 --- /dev/null +++ b/build-logic/.kotlin/errors/errors-1768472990915.log @@ -0,0 +1,73 @@ +kotlin version: 2.0.21 +error message: Daemon compilation failed: null +java.lang.Exception + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69) + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(Unknown Source) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) + at java.base/java.util.concurrent.FutureTask.run(Unknown Source) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.io.FileNotFoundException: /Users/invoke/AndroidStudioProjects/NoteMark/build-logic/convention/build/kotlin/compileKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin (No such file or directory) + at java.base/java.io.FileOutputStream.open0(Native Method) + at java.base/java.io.FileOutputStream.open(Unknown Source) + at java.base/java.io.FileOutputStream.(Unknown Source) + at java.base/java.io.FileOutputStream.(Unknown Source) + at org.jetbrains.kotlin.incremental.storage.ExternalizersKt.saveToFile(externalizers.kt:178) + at org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinkerKt.shrinkAndSaveClasspathSnapshot(ClasspathSnapshotShrinker.kt:299) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.performWorkAfterCompilation(IncrementalJvmCompilerRunner.kt:476) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.performWorkAfterCompilation(IncrementalJvmCompilerRunner.kt:73) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:425) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + ... 3 more + + diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 67ee423..9cd3357 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -19,6 +19,10 @@ dependencies { gradlePlugin { plugins { + register("ktorClientConvention") { + id = "notemark.convention.ktor.client" + implementationClass = "KtorClientConventionPlugin" + } register("androidApp") { id = "notemark.android.application" implementationClass = "AndroidAppConventionPlugin" diff --git a/build-logic/convention/src/main/java/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidAppConventionPlugin.kt index 1334200..16aa792 100644 --- a/build-logic/convention/src/main/java/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/java/AndroidAppConventionPlugin.kt @@ -16,9 +16,9 @@ class AndroidAppConventionPlugin : Plugin { pluginManager.run { apply(libs.findPlugin("android.application").get().get().pluginId) apply(libs.findPlugin("kotlin.android").get().get().pluginId) - apply(libs.findPlugin("kotlin.compose").get().get().pluginId) apply(libs.findPlugin("kotlin.serialization").get().get().pluginId) - apply(libs.findPlugin("room").get().get().pluginId) + apply("androidx.room") + apply("org.jetbrains.kotlin.plugin.compose") } extensions.configure { diff --git a/build-logic/convention/src/main/java/AndroidLibComposeConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidLibComposeConventionPlugin.kt index 2542d0d..46a08b9 100644 --- a/build-logic/convention/src/main/java/AndroidLibComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/java/AndroidLibComposeConventionPlugin.kt @@ -11,7 +11,7 @@ class AndroidLibComposeConventionPlugin : Plugin { target.run { pluginManager.run { apply(libs.findPlugin("notemark.android.library").get().get().pluginId) - apply(libs.findPlugin("kotlin.compose").get().get().pluginId) + apply(libs.findPlugin("compose.compiler").get().get().pluginId) } val extension = extensions.getByType() diff --git a/build-logic/convention/src/main/java/KtorClientConventionPlugin.kt b/build-logic/convention/src/main/java/KtorClientConventionPlugin.kt new file mode 100644 index 0000000..9a6f142 --- /dev/null +++ b/build-logic/convention/src/main/java/KtorClientConventionPlugin.kt @@ -0,0 +1,27 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.getByType + +class KtorClientConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + // 1. Apply the serialization plugin required by Ktor for JSON processing + pluginManager.apply("org.jetbrains.kotlin.plugin.serialization") + + val libs = extensions.getByType().named("libs") + + // 2. Add Ktor libraries as dependencies + dependencies { + "implementation"(libs.findLibrary("ktor-client-auth").get()) + "implementation"(libs.findLibrary("ktor-client-core").get()) + "implementation"(libs.findLibrary("ktor-client-android").get()) + "implementation"(libs.findLibrary("ktor-client-cio").get()) + "implementation"(libs.findLibrary("ktor-client-content-negotiation").get()) + "implementation"(libs.findLibrary("ktor-serialization").get()) + "implementation"(libs.findLibrary("ktor-client-logging").get()) + } + } + } +} diff --git a/build-logic/convention/src/main/java/com/example/convention/BuildType.kt b/build-logic/convention/src/main/java/com/example/convention/BuildType.kt index fa8dbc3..cf883ee 100644 --- a/build-logic/convention/src/main/java/com/example/convention/BuildType.kt +++ b/build-logic/convention/src/main/java/com/example/convention/BuildType.kt @@ -19,6 +19,7 @@ internal fun Project.configureBuildTypes( commonExtension.run { buildFeatures { buildConfig = true + compose = true } when (extensionType) { @@ -26,6 +27,7 @@ internal fun Project.configureBuildTypes( extensions.configure { buildTypes { debug { + buildConfigField ("String", "BASE_URL", "\"https://notemark.pl-coding.com/\"") isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -33,6 +35,7 @@ internal fun Project.configureBuildTypes( ) } release { + buildConfigField ("String", "BASE_URL", "\"https://notemark.pl-coding.com/\"") isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -47,6 +50,7 @@ internal fun Project.configureBuildTypes( extensions.configure { buildTypes { debug { + buildConfigField ("String", "BASE_URL", "\"https://notemark.pl-coding.com/\"") isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -54,6 +58,7 @@ internal fun Project.configureBuildTypes( ) } release { + buildConfigField ("String", "BASE_URL", "\"https://notemark.pl-coding.com/\"") isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), diff --git a/build.gradle.kts b/build.gradle.kts index f80232c..dc71206 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,9 +2,10 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false - alias(libs.plugins.kotlin.compose) apply false - id("com.google.dagger.hilt.android") version "2.56.2" apply false - id("com.google.devtools.ksp") version "2.2.10-2.0.2" - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.0" apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.room) apply false + id("com.google.dagger.hilt.android") version "2.54" apply false + alias(libs.plugins.ksp) apply false + alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.android.library) apply false } \ No newline at end of file diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index a44a841..416ba4e 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -1,12 +1,15 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) + alias(libs.plugins.notemark.android.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.ktor.client.convention) } android { - namespace = "com.example.data" + namespace = "com.example.notemark.core.data" } dependencies { + implementation(libs.kotlinx.coroutines.core) implementation(projects.core.domain) + implementation(projects.auth.domain) } \ No newline at end of file diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml index a5918e6..8c4c982 100644 --- a/core/data/src/main/AndroidManifest.xml +++ b/core/data/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/AndroidConnectivityObserver.kt b/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt similarity index 96% rename from app/src/main/java/com/example/notemark/AndroidConnectivityObserver.kt rename to core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt index 6ed99e8..57f2a33 100644 --- a/app/src/main/java/com/example/notemark/AndroidConnectivityObserver.kt +++ b/core/data/src/main/java/com/example/data/AndroidConnectivityObserver.kt @@ -1,11 +1,11 @@ -package com.example.notemark +package com.example.data import android.content.Context import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities -import android.util.Log import androidx.core.content.getSystemService +import com.example.domain.ConnectivityObserver import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow diff --git a/core/data/src/main/java/com/example/data/AppModule.kt b/core/data/src/main/java/com/example/data/AppModule.kt new file mode 100644 index 0000000..6875364 --- /dev/null +++ b/core/data/src/main/java/com/example/data/AppModule.kt @@ -0,0 +1,21 @@ +package com.example.data + +import android.content.Context +import com.example.domain.ConnectivityObserver +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + + @Provides + @Singleton + fun provideConnectivityObserver( + @ApplicationContext context: Context + ): ConnectivityObserver = AndroidConnectivityObserver(context) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/factory/ClientFactory.kt b/core/data/src/main/java/com/example/data/ClientFactory.kt similarity index 91% rename from app/src/main/java/com/example/notemark/core/factory/ClientFactory.kt rename to core/data/src/main/java/com/example/data/ClientFactory.kt index 7951405..9e9c777 100644 --- a/app/src/main/java/com/example/notemark/core/factory/ClientFactory.kt +++ b/core/data/src/main/java/com/example/data/ClientFactory.kt @@ -1,7 +1,8 @@ -package com.example.notemark.core.factory +package com.example.data -import com.example.notemark.core.HttpRoutes -import com.example.notemark.core.manager.SessionManager +import com.example.domain.HttpRoutes +import com.example.domain.model.AccessTokenRequest +import com.example.domain.model.AccessTokenResponse import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.cio.CIO @@ -19,7 +20,7 @@ import io.ktor.http.ContentType import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json -import kotlinx.serialization.Serializable +import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.json.Json import javax.inject.Inject @@ -47,6 +48,7 @@ class HttpClientFactory @Inject constructor( } } + @OptIn(InternalSerializationApi::class) fun build(): HttpClient { return HttpClient(CIO) { install(ContentNegotiation) { @@ -107,6 +109,7 @@ class HttpClientFactory @Inject constructor( BearerTokens(accessToken = "", refreshToken = "") } } catch (e: Exception) { + e.printStackTrace() sessionManager.clearTokens() BearerTokens(accessToken = "", refreshToken = "") } @@ -115,15 +118,4 @@ class HttpClientFactory @Inject constructor( } } } -} - -@Serializable -data class AccessTokenRequest( - val refreshToken: String -) - -@Serializable -data class AccessTokenResponse( - val accessToken: String, - val refreshToken: String, -) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/di/NetworkModule.kt b/core/data/src/main/java/com/example/data/NetworkModule.kt similarity index 79% rename from app/src/main/java/com/example/notemark/core/di/NetworkModule.kt rename to core/data/src/main/java/com/example/data/NetworkModule.kt index 3e60ca4..4169a32 100644 --- a/app/src/main/java/com/example/notemark/core/di/NetworkModule.kt +++ b/core/data/src/main/java/com/example/data/NetworkModule.kt @@ -1,7 +1,5 @@ -package com.example.notemark.core.di +package com.example.data -import com.example.notemark.core.factory.HttpClientFactory -import com.example.notemark.core.manager.SessionManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/core/data/src/main/java/com/example/data/SessionManager.kt b/core/data/src/main/java/com/example/data/SessionManager.kt new file mode 100644 index 0000000..4b19ec4 --- /dev/null +++ b/core/data/src/main/java/com/example/data/SessionManager.kt @@ -0,0 +1,169 @@ +package com.example.data + +import android.content.Context +import android.content.SharedPreferences +import android.util.Log +import com.example.domain.SessionRepository +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class SessionManager @Inject constructor( + @ApplicationContext context: Context // ✅ Add @ApplicationContext +): SessionRepository { + + companion object { + private const val TAG = "SessionManager" + const val PREF_NAME = "NoteMarkPrefs" + const val ACCESS_TOKEN = "access_token" + const val REFRESH_TOKEN = "refresh_token" + const val USER_NAME = "user_name" + const val USER_EMAIL = "user_email" + const val IS_LOGGED_IN = "is_logged_in" + } + + private val sharedPrefs: SharedPreferences = + context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) + + override fun saveTokens(accessToken: String, refreshToken: String) { + try { + sharedPrefs.edit().apply { + putString(ACCESS_TOKEN, accessToken) + putString(REFRESH_TOKEN, refreshToken) + putBoolean(IS_LOGGED_IN, true) + apply() + } + Log.d(TAG, "Tokens saved successfully") + } catch (e: Exception) { + Log.e(TAG, "Error saving tokens: ${e.message}") + } + } + + override fun saveUserName(userName: String) { + try { + sharedPrefs.edit().apply { + putString(USER_NAME, userName) + apply() + } + Log.d(TAG, "Username saved successfully") + } catch (e: Exception) { + Log.e(TAG, "Error saving username: ${e.message}") + } + } + + fun saveUserEmail(email: String) { + try { + sharedPrefs.edit().apply { + putString(USER_EMAIL, email) + apply() + } + Log.d(TAG, "Email saved successfully") + } catch (e: Exception) { + Log.e(TAG, "Error saving email: ${e.message}") + } + } + + override fun getUserName(): String? { + return try { + sharedPrefs.getString(USER_NAME, null) + } catch (e: Exception) { + Log.e(TAG, "Error getting username: ${e.message}") + null + } + } + + fun getUserEmail(): String? { + return try { + sharedPrefs.getString(USER_EMAIL, null) + } catch (e: Exception) { + Log.e(TAG, "Error getting email: ${e.message}") + null + } + } + + override fun getAccessToken(): String? { + return try { + sharedPrefs.getString(ACCESS_TOKEN, null) + } catch (e: Exception) { + Log.e(TAG, "Error getting access token: ${e.message}") + null + } + } + + override fun getRefreshToken(): String? { + return try { + sharedPrefs.getString(REFRESH_TOKEN, null) + } catch (e: Exception) { + Log.e(TAG, "Error getting refresh token: ${e.message}") + null + } + } + + override fun clearUserName() { + try { + sharedPrefs.edit().apply { + remove(USER_NAME) + apply() + } + Log.d(TAG, "Username cleared successfully") + } catch (e: Exception) { + Log.e(TAG, "Error clearing username: ${e.message}") + } + } + + + fun clearUserEmail() { + try { + sharedPrefs.edit().apply { + remove(USER_EMAIL) + apply() + } + Log.d(TAG, "Email cleared successfully") + } catch (e: Exception) { + Log.e(TAG, "Error clearing email: ${e.message}") + } + } + + override fun clearTokens() { + try { + sharedPrefs.edit().apply { + remove(ACCESS_TOKEN) + remove(REFRESH_TOKEN) + putBoolean(IS_LOGGED_IN, false) + apply() + } + Log.d(TAG, "Tokens cleared successfully") + } catch (e: Exception) { + Log.e(TAG, "Error clearing tokens: ${e.message}") + } + } + + override fun clearAllData() { + try { + sharedPrefs.edit().clear().apply() + Log.d(TAG, "All session data cleared successfully") + } catch (e: Exception) { + Log.e(TAG, "Error clearing all data: ${e.message}") + } + } + + override fun isLoggedIn(): Boolean { + return try { + val hasToken = !getAccessToken().isNullOrEmpty() + val isLoggedIn = sharedPrefs.getBoolean(IS_LOGGED_IN, false) + hasToken && isLoggedIn + } catch (e: Exception) { + Log.e(TAG, "Error checking login status: ${e.message}") + false + } + } + + fun getAllSessionData(): Map { + return mapOf( + "userName" to getUserName(), + "userEmail" to getUserEmail(), + "accessToken" to getAccessToken()?.take(10) + "...", // Don't log full token + "refreshToken" to getRefreshToken()?.take(10) + "...", // Don't log full token + "isLoggedIn" to isLoggedIn().toString() + ) + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/example/data/SessionModule.kt b/core/data/src/main/java/com/example/data/SessionModule.kt new file mode 100644 index 0000000..7d95ed1 --- /dev/null +++ b/core/data/src/main/java/com/example/data/SessionModule.kt @@ -0,0 +1,22 @@ +package com.example.data + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object SessionModule { + + @Provides + @Singleton + fun provideSessionManager( + @ApplicationContext context: Context + ): SessionManager { + return SessionManager(context) + } +} \ No newline at end of file diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts index 28db768..977bb8e 100644 --- a/core/domain/build.gradle.kts +++ b/core/domain/build.gradle.kts @@ -3,5 +3,5 @@ plugins { } dependencies { - + implementation(libs.kotlinx.coroutines.core) } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/ConnectivityObserver.kt b/core/domain/src/main/java/com/example/domain/ConnectivityObserver.kt similarity index 93% rename from app/src/main/java/com/example/notemark/ConnectivityObserver.kt rename to core/domain/src/main/java/com/example/domain/ConnectivityObserver.kt index 8758a75..d9c201a 100644 --- a/app/src/main/java/com/example/notemark/ConnectivityObserver.kt +++ b/core/domain/src/main/java/com/example/domain/ConnectivityObserver.kt @@ -1,4 +1,4 @@ -package com.example.notemark +package com.example.domain import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first diff --git a/app/src/main/java/com/example/notemark/auth/domain/DataError.kt b/core/domain/src/main/java/com/example/domain/DataError.kt similarity index 91% rename from app/src/main/java/com/example/notemark/auth/domain/DataError.kt rename to core/domain/src/main/java/com/example/domain/DataError.kt index 27fb6ca..28536e1 100644 --- a/app/src/main/java/com/example/notemark/auth/domain/DataError.kt +++ b/core/domain/src/main/java/com/example/domain/DataError.kt @@ -1,4 +1,4 @@ -package com.example.notemark.auth.domain +package com.example.domain sealed interface DataError: Error { diff --git a/core/domain/src/main/java/com/example/domain/Error.kt b/core/domain/src/main/java/com/example/domain/Error.kt new file mode 100644 index 0000000..d5b7a4d --- /dev/null +++ b/core/domain/src/main/java/com/example/domain/Error.kt @@ -0,0 +1,3 @@ +package com.example.domain + +sealed interface Error \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/HttpRoutes.kt b/core/domain/src/main/java/com/example/domain/HttpRoutes.kt similarity index 92% rename from app/src/main/java/com/example/notemark/core/HttpRoutes.kt rename to core/domain/src/main/java/com/example/domain/HttpRoutes.kt index 2ef7cb7..c734505 100644 --- a/app/src/main/java/com/example/notemark/core/HttpRoutes.kt +++ b/core/domain/src/main/java/com/example/domain/HttpRoutes.kt @@ -1,4 +1,4 @@ -package com.example.notemark.core +package com.example.domain object HttpRoutes { diff --git a/core/domain/src/main/java/com/example/domain/Note.kt b/core/domain/src/main/java/com/example/domain/Note.kt new file mode 100644 index 0000000..762c55e --- /dev/null +++ b/core/domain/src/main/java/com/example/domain/Note.kt @@ -0,0 +1,9 @@ +package com.example.domain + +data class Note( + val id: String, + val title: String, + val content: String, + val createdAt: String, + val updatedAt: String? = "" +) \ No newline at end of file diff --git a/core/domain/src/main/java/com/example/domain/NoteRequest.kt b/core/domain/src/main/java/com/example/domain/NoteRequest.kt new file mode 100644 index 0000000..ce131a2 --- /dev/null +++ b/core/domain/src/main/java/com/example/domain/NoteRequest.kt @@ -0,0 +1,9 @@ +package com.example.domain + +data class NoteRequest( + val id: String, + val title: String, + val content: String, + val createdAt: String, + val updatedAt: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/domain/Resource.kt b/core/domain/src/main/java/com/example/domain/Resource.kt similarity index 83% rename from app/src/main/java/com/example/notemark/auth/domain/Resource.kt rename to core/domain/src/main/java/com/example/domain/Resource.kt index c9bbf28..3d8c5b4 100644 --- a/app/src/main/java/com/example/notemark/auth/domain/Resource.kt +++ b/core/domain/src/main/java/com/example/domain/Resource.kt @@ -1,4 +1,4 @@ -package com.example.notemark.auth.domain +package com.example.domain sealed class Resource(val data: T? = null, val message: String? = null) { class Success(data: T?): Resource(data) diff --git a/app/src/main/java/com/example/notemark/auth/domain/RootError.kt b/core/domain/src/main/java/com/example/domain/RootError.kt similarity index 84% rename from app/src/main/java/com/example/notemark/auth/domain/RootError.kt rename to core/domain/src/main/java/com/example/domain/RootError.kt index 2b55048..b6506d5 100644 --- a/app/src/main/java/com/example/notemark/auth/domain/RootError.kt +++ b/core/domain/src/main/java/com/example/domain/RootError.kt @@ -1,4 +1,4 @@ -package com.example.notemark.auth.domain +package com.example.domain typealias RootError = Error diff --git a/core/domain/src/main/java/com/example/domain/SessionRepository.kt b/core/domain/src/main/java/com/example/domain/SessionRepository.kt new file mode 100644 index 0000000..026c98e --- /dev/null +++ b/core/domain/src/main/java/com/example/domain/SessionRepository.kt @@ -0,0 +1,22 @@ +package com.example.domain + +interface SessionRepository { + + fun saveTokens(accessToken: String, refreshToken: String) + + fun saveUserName(userName: String) + + fun getUserName(): String? + + fun getAccessToken(): String? + + fun getRefreshToken(): String? + + fun clearUserName() + + fun clearTokens() + + fun isLoggedIn(): Boolean + + fun clearAllData() +} \ No newline at end of file diff --git a/core/presentation/build.gradle.kts b/core/presentation/build.gradle.kts index eba01ce..7599737 100644 --- a/core/presentation/build.gradle.kts +++ b/core/presentation/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.example.presentation" + namespace = "com.example.notemark.core.presentation" } dependencies { diff --git a/app/src/main/java/com/example/notemark/ConnectivityViewModel.kt b/core/presentation/src/main/java/com/example/presentation/ConnectivityViewModel.kt similarity index 85% rename from app/src/main/java/com/example/notemark/ConnectivityViewModel.kt rename to core/presentation/src/main/java/com/example/presentation/ConnectivityViewModel.kt index 2ce9f17..2dab206 100644 --- a/app/src/main/java/com/example/notemark/ConnectivityViewModel.kt +++ b/core/presentation/src/main/java/com/example/presentation/ConnectivityViewModel.kt @@ -1,7 +1,8 @@ -package com.example.notemark +package com.example.presentation import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.example.domain.ConnectivityObserver import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn diff --git a/app/src/main/java/com/example/notemark/main/DateFormatter.kt b/core/presentation/src/main/java/com/example/presentation/DateFormatter.kt similarity index 98% rename from app/src/main/java/com/example/notemark/main/DateFormatter.kt rename to core/presentation/src/main/java/com/example/presentation/DateFormatter.kt index 052049d..0603634 100644 --- a/app/src/main/java/com/example/notemark/main/DateFormatter.kt +++ b/core/presentation/src/main/java/com/example/presentation/DateFormatter.kt @@ -1,4 +1,4 @@ -package com.example.notemark.main +package com.example.presentation import java.time.Instant import java.time.LocalDateTime diff --git a/core/presentation/src/main/java/com/example/presentation/DeviceConfiguration.kt b/core/presentation/src/main/java/com/example/presentation/DeviceConfiguration.kt new file mode 100644 index 0000000..8354634 --- /dev/null +++ b/core/presentation/src/main/java/com/example/presentation/DeviceConfiguration.kt @@ -0,0 +1,29 @@ +package com.example.presentation + +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowSizeClass +import androidx.window.core.layout.WindowWidthSizeClass + +enum class DeviceConfiguration { + MOBILE_PORTRAIT, + MOBILE_LANDSCAPE, + TABLET_PORTRAIT, + TABLET_LANDSCAPE, + DESKTOP; + + companion object { + fun fromWindowSizeClass(windowSizeClass: WindowSizeClass): DeviceConfiguration { + val widthClass = windowSizeClass.windowWidthSizeClass + val heightClass = windowSizeClass.windowHeightSizeClass + + return when (widthClass) { + WindowWidthSizeClass.COMPACT if heightClass == WindowHeightSizeClass.MEDIUM -> MOBILE_PORTRAIT + WindowWidthSizeClass.COMPACT if heightClass == WindowHeightSizeClass.EXPANDED -> MOBILE_PORTRAIT + WindowWidthSizeClass.EXPANDED if heightClass == WindowHeightSizeClass.COMPACT -> MOBILE_LANDSCAPE + WindowWidthSizeClass.MEDIUM if heightClass == WindowHeightSizeClass.EXPANDED -> TABLET_PORTRAIT + WindowWidthSizeClass.EXPANDED if heightClass == WindowHeightSizeClass.MEDIUM -> TABLET_LANDSCAPE + else -> DESKTOP + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/util/FormValidation.kt b/core/presentation/src/main/java/com/example/presentation/FormValidation.kt similarity index 97% rename from app/src/main/java/com/example/notemark/auth/presentation/util/FormValidation.kt rename to core/presentation/src/main/java/com/example/presentation/FormValidation.kt index 8eedaea..977d786 100644 --- a/app/src/main/java/com/example/notemark/auth/presentation/util/FormValidation.kt +++ b/core/presentation/src/main/java/com/example/presentation/FormValidation.kt @@ -1,4 +1,4 @@ -package com.example.notemark.auth.presentation.util +package com.example.presentation import android.util.Patterns diff --git a/core/presentation/src/main/java/com/example/presentation/NoteMarkDefaultScreen.kt b/core/presentation/src/main/java/com/example/presentation/NoteMarkDefaultScreen.kt new file mode 100644 index 0000000..dd607e5 --- /dev/null +++ b/core/presentation/src/main/java/com/example/presentation/NoteMarkDefaultScreen.kt @@ -0,0 +1,48 @@ +package com.example.presentation + +import android.app.Activity +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowInsetsControllerCompat + +@Composable +fun NoteMarkDefaultScreen( + modifier: Modifier = Modifier, + containerColor: Color = MaterialTheme.colorScheme.background, + bottomBar: @Composable () -> Unit = {}, + content: @Composable () -> Unit = {}, +) { + Scaffold( + bottomBar = { bottomBar() }, + modifier = modifier, + containerColor = containerColor, + ) { innerPadding -> + val view = LocalView.current + Box( + modifier = + Modifier + .padding(innerPadding), + ) { + content() + } + + SideEffect { + val window = (view.context as? Activity)?.window + if (!view.isInEditMode && window != null) { + val isLightBackground = containerColor.luminance() > 0.5f + + WindowInsetsControllerCompat(window, window.decorView).apply { + isAppearanceLightStatusBars = isLightBackground + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/auth/presentation/UiText.kt b/core/presentation/src/main/java/com/example/presentation/UiText.kt similarity index 94% rename from app/src/main/java/com/example/notemark/auth/presentation/UiText.kt rename to core/presentation/src/main/java/com/example/presentation/UiText.kt index cf1b64a..f7447d3 100644 --- a/app/src/main/java/com/example/notemark/auth/presentation/UiText.kt +++ b/core/presentation/src/main/java/com/example/presentation/UiText.kt @@ -1,4 +1,4 @@ -package com.example.notemark.auth.presentation +package com.example.presentation import android.content.Context import androidx.annotation.StringRes diff --git a/core/presentation/src/main/java/com/example/presentation/asUiText.kt b/core/presentation/src/main/java/com/example/presentation/asUiText.kt new file mode 100644 index 0000000..9682f6e --- /dev/null +++ b/core/presentation/src/main/java/com/example/presentation/asUiText.kt @@ -0,0 +1,44 @@ +package com.example.presentation + +import com.example.notemark.core.presentation.R +import com.example.domain.DataError + +fun DataError.asUiText(): UiText { + return when (this) { + DataError.Network.REQUEST_TIMEOUT -> UiText.StringResource( + R.string.the_request_timed_out + ) + + DataError.Network.TOO_MANY_REQUESTS -> UiText.StringResource( + R.string.youve_hit_your_rate_limit + ) + + DataError.Network.NO_INTERNET -> UiText.StringResource( + R.string.no_internet + ) + + DataError.Network.SERVER_ERROR -> UiText.StringResource( + R.string.server_error + ) + + DataError.Network.UNKNOWN -> UiText.StringResource( + R.string.unknown_error + ) + + DataError.Local.DISK_FULL -> UiText.StringResource( + R.string.disk_full + ) + + DataError.ValidationErrors.INVALID_FORMAT -> UiText.StringResource( + R.string.invalid_format + ) + + DataError.Note.NOTE_IS_NOT_DELETED -> UiText.StringResource( + R.string.note_is_not_deleted + ) + } +} + +//fun Result.Error<*, DataError>.asErrorUiText(): UiText { +// return error.asUiText() +//} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/components/NoteMarkButton.kt b/core/presentation/src/main/java/com/example/presentation/components/NoteMarkButton.kt similarity index 96% rename from app/src/main/java/com/example/notemark/core/components/NoteMarkButton.kt rename to core/presentation/src/main/java/com/example/presentation/components/NoteMarkButton.kt index 4dbfbe4..559c577 100644 --- a/app/src/main/java/com/example/notemark/core/components/NoteMarkButton.kt +++ b/core/presentation/src/main/java/com/example/presentation/components/NoteMarkButton.kt @@ -1,4 +1,4 @@ -package com.example.notemark.core.components +package com.example.presentation.components import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.size diff --git a/app/src/main/java/com/example/notemark/core/components/NoteMarkLink.kt b/core/presentation/src/main/java/com/example/presentation/components/NoteMarkLink.kt similarity index 93% rename from app/src/main/java/com/example/notemark/core/components/NoteMarkLink.kt rename to core/presentation/src/main/java/com/example/presentation/components/NoteMarkLink.kt index 0fe8919..ee272b8 100644 --- a/app/src/main/java/com/example/notemark/core/components/NoteMarkLink.kt +++ b/core/presentation/src/main/java/com/example/presentation/components/NoteMarkLink.kt @@ -1,4 +1,4 @@ -package com.example.notemark.core.components +package com.example.presentation.components import androidx.compose.foundation.clickable import androidx.compose.material3.MaterialTheme diff --git a/app/src/main/java/com/example/notemark/core/components/NoteMarkTextField.kt b/core/presentation/src/main/java/com/example/presentation/components/NoteMarkTextField.kt similarity index 98% rename from app/src/main/java/com/example/notemark/core/components/NoteMarkTextField.kt rename to core/presentation/src/main/java/com/example/presentation/components/NoteMarkTextField.kt index 0e7152a..2ac2f45 100644 --- a/app/src/main/java/com/example/notemark/core/components/NoteMarkTextField.kt +++ b/core/presentation/src/main/java/com/example/presentation/components/NoteMarkTextField.kt @@ -1,4 +1,4 @@ -package com.example.notemark.core.components +package com.example.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -30,7 +30,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import com.example.notemark.R +import com.example.notemark.core.presentation.R @Composable fun NoteMarkTextField( diff --git a/core/presentation/src/main/java/com/example/presentation/getFormattedCreatedAt.kt b/core/presentation/src/main/java/com/example/presentation/getFormattedCreatedAt.kt new file mode 100644 index 0000000..79c7b98 --- /dev/null +++ b/core/presentation/src/main/java/com/example/presentation/getFormattedCreatedAt.kt @@ -0,0 +1,6 @@ +package com.example.presentation + +import com.example.domain.Note + +fun Note.getFormattedCreatedAt(): String = createdAt.formatAsNoteDate() +fun Note.getFormattedUpdatedAt(): String = updatedAt?.formatAsDetailsDate() ?: "" \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/getInitials.kt b/core/presentation/src/main/java/com/example/presentation/getInitials.kt similarity index 85% rename from app/src/main/java/com/example/notemark/core/getInitials.kt rename to core/presentation/src/main/java/com/example/presentation/getInitials.kt index 88fdb7b..86caa97 100644 --- a/app/src/main/java/com/example/notemark/core/getInitials.kt +++ b/core/presentation/src/main/java/com/example/presentation/getInitials.kt @@ -1,4 +1,4 @@ -package com.example.notemark.core +package com.example.presentation fun getInitials(username: String): String { val trimmedName = username.trim() @@ -11,7 +11,7 @@ fun getInitials(username: String): String { words.size == 1 -> { val word = words[0].uppercase() - if (word.length >= 2) word.substring(0, 2) else word + if (word.length >= 2) word.take(2) else word } else -> { diff --git a/core/presentation/src/main/java/com/example/presentation/isValidEmail.kt b/core/presentation/src/main/java/com/example/presentation/isValidEmail.kt new file mode 100644 index 0000000..5ed6c85 --- /dev/null +++ b/core/presentation/src/main/java/com/example/presentation/isValidEmail.kt @@ -0,0 +1,5 @@ +package com.example.presentation + +import android.util.Patterns + +fun isValidEmail(email: String): Boolean = Patterns.EMAIL_ADDRESS.matcher(email).matches() \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/truncateAtWord.kt b/core/presentation/src/main/java/com/example/presentation/truncateAtWord.kt similarity index 90% rename from app/src/main/java/com/example/notemark/core/truncateAtWord.kt rename to core/presentation/src/main/java/com/example/presentation/truncateAtWord.kt index 3e29441..3009f89 100644 --- a/app/src/main/java/com/example/notemark/core/truncateAtWord.kt +++ b/core/presentation/src/main/java/com/example/presentation/truncateAtWord.kt @@ -1,4 +1,4 @@ -package com.example.notemark.core +package com.example.presentation fun String.truncateAtWord(maxLength: Int): String { if (this.length <= maxLength) return this diff --git a/app/src/main/res/drawable/book_open.xml b/core/presentation/src/main/res/drawable/book_open.xml similarity index 100% rename from app/src/main/res/drawable/book_open.xml rename to core/presentation/src/main/res/drawable/book_open.xml diff --git a/app/src/main/res/drawable/check.xml b/core/presentation/src/main/res/drawable/check.xml similarity index 100% rename from app/src/main/res/drawable/check.xml rename to core/presentation/src/main/res/drawable/check.xml diff --git a/app/src/main/res/drawable/chevron_right.xml b/core/presentation/src/main/res/drawable/chevron_right.xml similarity index 100% rename from app/src/main/res/drawable/chevron_right.xml rename to core/presentation/src/main/res/drawable/chevron_right.xml diff --git a/app/src/main/res/drawable/clock.xml b/core/presentation/src/main/res/drawable/clock.xml similarity index 100% rename from app/src/main/res/drawable/clock.xml rename to core/presentation/src/main/res/drawable/clock.xml diff --git a/app/src/main/res/drawable/cloud_off.xml b/core/presentation/src/main/res/drawable/cloud_off.xml similarity index 100% rename from app/src/main/res/drawable/cloud_off.xml rename to core/presentation/src/main/res/drawable/cloud_off.xml diff --git a/app/src/main/res/drawable/copy.xml b/core/presentation/src/main/res/drawable/copy.xml similarity index 100% rename from app/src/main/res/drawable/copy.xml rename to core/presentation/src/main/res/drawable/copy.xml diff --git a/app/src/main/res/drawable/edit.xml b/core/presentation/src/main/res/drawable/edit.xml similarity index 100% rename from app/src/main/res/drawable/edit.xml rename to core/presentation/src/main/res/drawable/edit.xml diff --git a/app/src/main/res/drawable/eye.xml b/core/presentation/src/main/res/drawable/eye.xml similarity index 100% rename from app/src/main/res/drawable/eye.xml rename to core/presentation/src/main/res/drawable/eye.xml diff --git a/app/src/main/res/drawable/eye_off.xml b/core/presentation/src/main/res/drawable/eye_off.xml similarity index 100% rename from app/src/main/res/drawable/eye_off.xml rename to core/presentation/src/main/res/drawable/eye_off.xml diff --git a/core/presentation/src/main/res/drawable/image.png b/core/presentation/src/main/res/drawable/image.png new file mode 100644 index 0000000000000000000000000000000000000000..40331ecfee933406f55c59ce1ca5f17a89b8a6ac GIT binary patch literal 122562 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

yfZ)lyR&MYSk1pSugc0HUT7c?rIxgU0)i0CPX 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ios_arrow_left.xml b/core/presentation/src/main/res/drawable/ios_arrow_left.xml similarity index 100% rename from app/src/main/res/drawable/ios_arrow_left.xml rename to core/presentation/src/main/res/drawable/ios_arrow_left.xml diff --git a/app/src/main/res/drawable/logo.xml b/core/presentation/src/main/res/drawable/logo.xml similarity index 100% rename from app/src/main/res/drawable/logo.xml rename to core/presentation/src/main/res/drawable/logo.xml diff --git a/app/src/main/res/drawable/logout.xml b/core/presentation/src/main/res/drawable/logout.xml similarity index 100% rename from app/src/main/res/drawable/logout.xml rename to core/presentation/src/main/res/drawable/logout.xml diff --git a/core/presentation/src/main/res/drawable/outline_delete_outline_24.xml b/core/presentation/src/main/res/drawable/outline_delete_outline_24.xml new file mode 100644 index 0000000..2e1bb74 --- /dev/null +++ b/core/presentation/src/main/res/drawable/outline_delete_outline_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/plus.xml b/core/presentation/src/main/res/drawable/plus.xml similarity index 100% rename from app/src/main/res/drawable/plus.xml rename to core/presentation/src/main/res/drawable/plus.xml diff --git a/app/src/main/res/drawable/settings.xml b/core/presentation/src/main/res/drawable/settings.xml similarity index 100% rename from app/src/main/res/drawable/settings.xml rename to core/presentation/src/main/res/drawable/settings.xml diff --git a/app/src/main/res/drawable/sync_icon.xml b/core/presentation/src/main/res/drawable/sync_icon.xml similarity index 100% rename from app/src/main/res/drawable/sync_icon.xml rename to core/presentation/src/main/res/drawable/sync_icon.xml diff --git a/app/src/main/res/drawable/x.xml b/core/presentation/src/main/res/drawable/x.xml similarity index 100% rename from app/src/main/res/drawable/x.xml rename to core/presentation/src/main/res/drawable/x.xml diff --git a/app/src/main/res/font/inter_18pt_medium.ttf b/core/presentation/src/main/res/font/inter_18pt_medium.ttf similarity index 100% rename from app/src/main/res/font/inter_18pt_medium.ttf rename to core/presentation/src/main/res/font/inter_18pt_medium.ttf diff --git a/app/src/main/res/font/inter_18pt_regular.ttf b/core/presentation/src/main/res/font/inter_18pt_regular.ttf similarity index 100% rename from app/src/main/res/font/inter_18pt_regular.ttf rename to core/presentation/src/main/res/font/inter_18pt_regular.ttf diff --git a/app/src/main/res/font/space_grotesk_regular.ttf b/core/presentation/src/main/res/font/space_grotesk_regular.ttf similarity index 100% rename from app/src/main/res/font/space_grotesk_regular.ttf rename to core/presentation/src/main/res/font/space_grotesk_regular.ttf diff --git a/app/src/main/res/raw/success.json b/core/presentation/src/main/res/raw/success.json similarity index 100% rename from app/src/main/res/raw/success.json rename to core/presentation/src/main/res/raw/success.json diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml new file mode 100644 index 0000000..649be7e --- /dev/null +++ b/core/presentation/src/main/res/values/strings.xml @@ -0,0 +1,30 @@ + + + NoteMark + Request time out + Too many request + no internet + Server error occur. + Unknown error, please reach the support center. + Disk full, please clean the cache. + Invalid email + For some reason note is not deleted, please try again or contact support! + Log out + Last sync: + Sync Data + Sync interval + settings + Manual Only + 15 minutes + 30 minutes + 1 hour + You’ve got an empty board,\n let’s place your first note on it! + all notes + Date created + Last edited + update note + Discard Changes? + You have unsaved changes. If you discard now, all changes will be lost. + Discard + Keep Editing + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a5390a..34fdf4a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,55 +1,56 @@ [versions] agp = "8.13.2" -kotlin = "2.3.0" -coreKtx = "1.16.0" +kotlin = "2.2.10" +coreKtx = "1.17.0" junit = "4.13.2" -junitVersion = "1.2.1" -espressoCore = "3.6.1" +junitVersion = "1.3.0" +espressoCore = "3.7.0" # ktor -ktor = "3.2.0" -ktorClientAndroid = "3.2.0" -ktorClientCio = "3.2.0" -ktorClientCore = "3.2.0" -ktorClientLogging = "3.2.0" -ktorClientSerialization = "3.2.0" -ktorClientContentNegotiation = "3.2.0" - -lifecycleRuntimeKtx = "2.9.1" -activityCompose = "1.10.1" -composeBom = "2025.07.00" -composeNavigation = "2.9.0" -lottieCompose = "6.6.7" +ktor = "3.3.3" +ktorClientAndroid = "3.3.3" +ktorClientCio = "3.3.3" +ktorClientCore = "3.3.3" +ktorClientLogging = "3.3.3" +ktorClientSerialization = "3.3.3" +ktorClientContentNegotiation = "3.3.3" + +lifecycleRuntimeKtx = "2.10.0" +activityCompose = "1.12.2" +composeBom = "2025.12.01" +composeNavigation = "2.9.6" +lottieCompose = "6.7.1" pagingRuntimeKtx = "3.3.6" -roomKtx = "2.7.2" +roomKtx = "2.6.1" serialization = "1.9.0" -coreSplashscreen = "1.0.1" +coreSplashscreen = "1.2.0" iconExtend = "1.7.8" -adaptive = "1.1.2" +adaptive = "1.2.0" +coroutines = "1.10.2" androidTools = "31.12.0" # hilt -hiltAndroidCompiler = "2.56.2" -hiltCompiler = "1.2.0" +hiltAndroidCompiler = "2.54" +hiltCompiler = "1.3.0" -lifecycle = "2.9.2" +lifecycle = "2.10.0" appcompat = "1.7.1" material = "1.13.0" -animation = "1.8.1" -material3Adaptive = "1.1.0" +animation = "1.10.0" +material3Adaptive = "1.2.0" # room -room = "2.7.2" +room = "2.8.4" ksp = "2.2.10-2.0.2" # Project Info projectAppId = "com.example.notemark" projectVersionName = "1.0.0" projectVersionCode = "1" projectMinSdkVersion = "30" -projectTargetSdkVersion = "35" -projectCompileSdkVersion = "35" +projectTargetSdkVersion = "36" +projectCompileSdkVersion = "36" navigationRuntimeKtx = "2.9.3" [libraries] @@ -71,6 +72,7 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } # ktor ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktorClientAndroid" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientCio" } @@ -114,7 +116,6 @@ material = { group = "com.google.android.material", name = "material", version.r # Room room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } -room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } # Gradle android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } @@ -128,13 +129,15 @@ room-gradlePlugin = { group = "androidx.room", name = "room-gradle-plugin", vers android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } room = { id = "androidx.room", version.ref = "room" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } + notemark-android-application = { id = "notemark.android.application", version = "unspesified" } +ktor-client-convention = { id = "notemark.convention.ktor.client", version = "unspecified" } notemark-jvm-library = { id = "notemark.jvm.library", version = "unspesified" } notemark-android-library = { id = "notemark.android.library", version = "unspesified" } notemark-android-library-compose = { id = "notemark.android.library.compose", version = "unspesified" } diff --git a/note/data/build.gradle.kts b/note/data/build.gradle.kts index d4f105d..196bf28 100644 --- a/note/data/build.gradle.kts +++ b/note/data/build.gradle.kts @@ -1,11 +1,18 @@ plugins { alias(libs.plugins.notemark.android.library) + alias(libs.plugins.ktor.client.convention) + alias(libs.plugins.ksp) + alias(libs.plugins.compose.compiler) + } android { - namespace = "com.example.data" + namespace = "com.example.notemark.note.data" } dependencies { implementation(projects.note.domain) + implementation(libs.bundles.room) + implementation(libs.androidx.paging.runtime.ktx) + implementation(projects.core.domain) } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/core/di/AppModule.kt b/note/data/src/main/java/com/example/data/di/NoteModule.kt similarity index 65% rename from app/src/main/java/com/example/notemark/core/di/AppModule.kt rename to note/data/src/main/java/com/example/data/di/NoteModule.kt index b15f8d2..55d1b76 100644 --- a/app/src/main/java/com/example/notemark/core/di/AppModule.kt +++ b/note/data/src/main/java/com/example/data/di/NoteModule.kt @@ -1,20 +1,19 @@ -package com.example.notemark.core.di +package com.example.data.di +import NoteRemoteMediator import android.content.Context import androidx.paging.ExperimentalPagingApi import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.room.Room -import com.example.notemark.AndroidConnectivityObserver -import com.example.notemark.ConnectivityObserver -import com.example.notemark.ConnectivityViewModel -import com.example.notemark.core.manager.SessionManager -import com.example.notemark.main.data.local.NoteDatabase -import com.example.notemark.main.data.local.NoteEntity -import com.example.notemark.main.data.remote.NoteRemoteMediator -import com.example.notemark.main.data.remote.api.NoteService -import com.example.notemark.main.data.remote.repositoryImpl.NoteServiceImpl -import com.example.notemark.main.domain.repository.NotesRepository +import com.example.data.local.LocalNoteDataSourceImpl +import com.example.data.local.NoteDatabase +import com.example.data.local.NoteEntity +import com.example.data.remote.NoteServiceImpl +import com.example.domain.ConnectivityObserver +import com.example.domain.LocalNoteDataSource +import com.example.domain.NoteService +import com.example.domain.SessionRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -23,10 +22,9 @@ import dagger.hilt.components.SingletonComponent import io.ktor.client.HttpClient import javax.inject.Singleton -@OptIn(ExperimentalPagingApi::class) @Module @InstallIn(SingletonComponent::class) -object AppModule { +object NoteModule { @Provides @Singleton @@ -43,7 +41,16 @@ object AppModule { @Provides @Singleton - fun provideNotesPager(notesDb: NoteDatabase, noteService: NoteService): Pager { + fun provideLocalNoteDataSource( + noteDatabase: NoteDatabase + ): LocalNoteDataSource { + return LocalNoteDataSourceImpl(noteDatabase) + } + + @OptIn(ExperimentalPagingApi::class) + @Provides + @Singleton + fun provideNotesPager(notesDb: NoteDatabase, noteService: NoteService): Pager { return Pager( config = PagingConfig(pageSize = 20), remoteMediator = NoteRemoteMediator( @@ -56,17 +63,11 @@ object AppModule { ) } - @Provides - @Singleton - fun provideConnectivityObserver( - @ApplicationContext context: Context - ): ConnectivityObserver = AndroidConnectivityObserver(context) - @Provides @Singleton fun provideNoteService( client: HttpClient, - sessionManager: SessionManager, + sessionManager: SessionRepository, @ApplicationContext applicationContext: Context, connectivityObserver: ConnectivityObserver ): NoteService { @@ -78,17 +79,11 @@ object AppModule { ) } - @Provides - @Singleton - fun provideNoteRepository(api: NoteServiceImpl): NotesRepository { - return NotesRepository(api) - } - @Provides @Singleton fun provideNoteServiceImpl( api: HttpClient, - sessionManager: SessionManager, + sessionManager: SessionRepository, @ApplicationContext applicationContext: Context, connectivityObserver: ConnectivityObserver ): NoteServiceImpl { diff --git a/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt b/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt new file mode 100644 index 0000000..c37d412 --- /dev/null +++ b/note/data/src/main/java/com/example/data/local/LocalNoteDataSourceImpl.kt @@ -0,0 +1,17 @@ +package com.example.data.local + +import com.example.domain.LocalNoteDataSource +import javax.inject.Inject + +class LocalNoteDataSourceImpl @Inject constructor( + private val noteDatabase: NoteDatabase +) : LocalNoteDataSource { + + override suspend fun clearAllNotes() { + noteDatabase.dao.clearAll() + } + + override suspend fun getNotesCount(): Int { + return noteDatabase.dao.getNotesCount() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/local/NoteDao.kt b/note/data/src/main/java/com/example/data/local/NoteDao.kt similarity index 76% rename from app/src/main/java/com/example/notemark/main/data/local/NoteDao.kt rename to note/data/src/main/java/com/example/data/local/NoteDao.kt index 4fe7e4c..ec6b8e8 100644 --- a/app/src/main/java/com/example/notemark/main/data/local/NoteDao.kt +++ b/note/data/src/main/java/com/example/data/local/NoteDao.kt @@ -1,10 +1,9 @@ -package com.example.notemark.main.data.local +package com.example.data.local import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert -import com.example.notemark.main.presentation.screens.note.SyncRecord @Dao interface NoteDao { @@ -22,7 +21,7 @@ interface NoteDao { suspend fun deleteNoteById(noteId: String) @Query("SELECT * FROM noteentity") - fun pagingSource(): PagingSource + fun pagingSource(): PagingSource @Query("DELETE FROM noteentity") suspend fun clearAll() diff --git a/note/data/src/main/java/com/example/data/local/NoteDatabase.kt b/note/data/src/main/java/com/example/data/local/NoteDatabase.kt new file mode 100644 index 0000000..76a611b --- /dev/null +++ b/note/data/src/main/java/com/example/data/local/NoteDatabase.kt @@ -0,0 +1,12 @@ +package com.example.data.local + +import androidx.room.Database +import androidx.room.RoomDatabase + +@Database( + entities = [NoteEntity::class], + version = 3 +) +abstract class NoteDatabase: RoomDatabase() { + abstract val dao: NoteDao +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/local/NoteEntity.kt b/note/data/src/main/java/com/example/data/local/NoteEntity.kt similarity index 89% rename from app/src/main/java/com/example/notemark/main/data/local/NoteEntity.kt rename to note/data/src/main/java/com/example/data/local/NoteEntity.kt index c6c82b8..29189c5 100644 --- a/app/src/main/java/com/example/notemark/main/data/local/NoteEntity.kt +++ b/note/data/src/main/java/com/example/data/local/NoteEntity.kt @@ -1,4 +1,4 @@ -package com.example.notemark.main.data.local +package com.example.data.local import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/app/src/main/java/com/example/notemark/main/data/remote/NoteDTO.kt b/note/data/src/main/java/com/example/data/remote/NoteDTO.kt similarity index 85% rename from app/src/main/java/com/example/notemark/main/data/remote/NoteDTO.kt rename to note/data/src/main/java/com/example/data/remote/NoteDTO.kt index 51590c8..91099a6 100644 --- a/app/src/main/java/com/example/notemark/main/data/remote/NoteDTO.kt +++ b/note/data/src/main/java/com/example/data/remote/NoteDTO.kt @@ -1,4 +1,4 @@ -package com.example.notemark.main.data.remote +package com.example.data.remote import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/note/data/src/main/java/com/example/data/remote/NoteMappers.kt b/note/data/src/main/java/com/example/data/remote/NoteMappers.kt new file mode 100644 index 0000000..d0cbf3e --- /dev/null +++ b/note/data/src/main/java/com/example/data/remote/NoteMappers.kt @@ -0,0 +1,101 @@ +package com.example.data.remote + +import com.example.data.local.NoteEntity +import com.example.domain.Note +import com.example.domain.NoteRequest +import com.example.domain.NotesResponse as DomainNotesResponse + +// DTO <-> Domain Note +fun NoteDTO.toDomain(): Note { + return Note( + id = id, + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt + ) +} + +fun Note.toDTO(): NoteDTO { + return NoteDTO( + id = id, + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt + ) +} + +// DTO <-> NoteEntity +fun NoteDTO.toEntity(): NoteEntity { + return NoteEntity( + id = id, + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt + ) +} + +fun NoteEntity.toDTO(): NoteDTO { + return NoteDTO( + id = id, + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt + ) +} + +// NoteEntity <-> Domain Note +fun NoteEntity.toDomain(): Note { + return Note( + id = id, + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt ?: "" + ) +} + +fun Note.toEntity(): NoteEntity { + return NoteEntity( + id = id, + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt + ) +} + +// NoteRequest -> DTO +fun NoteRequest.toDTO(): NoteRequestDTO { + return NoteRequestDTO( + id = id, + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt + ) +} + +// NoteRequest -> DTO +fun NoteRequestDTO.toNote(): Note { + return Note( + id = id ?: "", + title = title, + content = content, + createdAt = createdAt, + updatedAt = updatedAt + ) +} + +// NotesResponse (data) <-> NotesResponse (domain) +fun NotesResponse.toDomain(): DomainNotesResponse { + return DomainNotesResponse( + notes = this.notes.map { it.toDomain() }, + totalPages = this.totalPages, + currentPage = this.currentPage, + totalNotes = this.totalCount + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/remote/NoteRemoteMediator.kt b/note/data/src/main/java/com/example/data/remote/NoteRemoteMediator.kt similarity index 81% rename from app/src/main/java/com/example/notemark/main/data/remote/NoteRemoteMediator.kt rename to note/data/src/main/java/com/example/data/remote/NoteRemoteMediator.kt index 8475cc2..7b69cf8 100644 --- a/app/src/main/java/com/example/notemark/main/data/remote/NoteRemoteMediator.kt +++ b/note/data/src/main/java/com/example/data/remote/NoteRemoteMediator.kt @@ -1,5 +1,3 @@ -package com.example.notemark.main.data.remote - import android.net.http.HttpException import android.os.Build import android.util.Log @@ -10,9 +8,10 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import androidx.room.withTransaction -import com.example.notemark.main.data.local.NoteDatabase -import com.example.notemark.main.data.local.NoteEntity -import com.example.notemark.main.data.remote.api.NoteService +import com.example.data.local.NoteDatabase +import com.example.data.remote.toEntity +import com.example.domain.NoteEntity +import com.example.domain.NoteService import kotlinx.io.IOException @OptIn(ExperimentalPagingApi::class) @@ -31,11 +30,11 @@ class NoteRemoteMediator( LoadType.REFRESH -> 1 LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) LoadType.APPEND -> { - val lastItem = state.lastItemOrNull() - if(lastItem == null) { - return MediatorResult.Success(endOfPaginationReached = true) - } - noteDatabase.dao.getNotesCount() + state.lastItemOrNull() ?: return MediatorResult.Success( + endOfPaginationReached = true + ) + // Calculate the next page based on current items count + (noteDatabase.dao.getNotesCount() / state.config.pageSize) + 1 } } val notesResponses = noteApi.getNotes( @@ -47,7 +46,8 @@ class NoteRemoteMediator( if (loadType == LoadType.REFRESH) { noteDatabase.dao.clearAll() } - val noteEntities = notesResponses.toNoteEntityList() + // Convert NotesResponse to List + val noteEntities = notesResponses.notes.map { it.toEntity() } noteDatabase.dao.upsertAll(noteEntities) } diff --git a/note/data/src/main/java/com/example/data/remote/NoteRequestDTO.kt b/note/data/src/main/java/com/example/data/remote/NoteRequestDTO.kt new file mode 100644 index 0000000..c5fc977 --- /dev/null +++ b/note/data/src/main/java/com/example/data/remote/NoteRequestDTO.kt @@ -0,0 +1,12 @@ +package com.example.data.remote + +import kotlinx.serialization.Serializable + +@Serializable +data class NoteRequestDTO( + val id: String? = null, + val title: String, + val content: String, + val createdAt: String, + val updatedAt: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/data/remote/repositoryImpl/NoteServiceImpl.kt b/note/data/src/main/java/com/example/data/remote/NoteServiceImpl.kt similarity index 55% rename from app/src/main/java/com/example/notemark/main/data/remote/repositoryImpl/NoteServiceImpl.kt rename to note/data/src/main/java/com/example/data/remote/NoteServiceImpl.kt index 1031d48..2d948a6 100644 --- a/app/src/main/java/com/example/notemark/main/data/remote/repositoryImpl/NoteServiceImpl.kt +++ b/note/data/src/main/java/com/example/data/remote/NoteServiceImpl.kt @@ -1,20 +1,14 @@ -package com.example.notemark.main.data.remote.repositoryImpl +package com.example.data.remote -import com.example.notemark.ConnectivityObserver -import com.example.notemark.ConnectivityViewModel -import com.example.notemark.core.HttpRoutes -import com.example.notemark.core.manager.SessionManager -import com.example.notemark.main.data.local.NoteDatabase -import com.example.notemark.main.data.local.SyncDao -import com.example.notemark.main.data.remote.NoteDTO -import com.example.notemark.main.data.remote.api.NoteService -import com.example.notemark.main.data.remote.toNotEntity -import com.example.notemark.main.data.remote.toNoteDTO -import com.example.notemark.main.data.remote.toNoteEntity -import com.example.notemark.main.domain.model.NoteRequest -import com.example.notemark.main.domain.model.NotesResponse -import com.example.notemark.main.presentation.screens.note.SyncOperation -import com.example.notemark.main.presentation.screens.note.SyncRecord +import com.example.data.local.NoteDatabase +import com.example.domain.ConnectivityObserver +import com.example.domain.HttpRoutes +import com.example.domain.Note +import com.example.domain.NoteRequest +import com.example.domain.NoteService +import com.example.domain.Result +import com.example.domain.RootError +import com.example.domain.SessionRepository import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.delete @@ -26,17 +20,16 @@ import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.http.contentType -import kotlinx.serialization.json.Json import java.util.UUID class NoteServiceImpl( private val client: HttpClient, - private val sessionManager: SessionManager, + private val sessionManager: SessionRepository, private val noteDatabase: NoteDatabase, private val connectivityObserver: ConnectivityObserver, ): NoteService { - override suspend fun getNotes(page: Int, size: Int): NotesResponse { + override suspend fun getNotes(page: Int, size: Int): com.example.domain.NotesResponse { return try { val accessToken = sessionManager.getAccessToken() @@ -45,33 +38,22 @@ class NoteServiceImpl( header("Authorization", "Bearer $accessToken") header("X-User-Email", HttpRoutes.EMAIL) } - response.body() + response.body() } catch (e: Exception) { throw e } } - override suspend fun createNote(body: NoteRequest): Result { + override suspend fun createNote(body: NoteRequest): Result { return try { // 1. Always save locally first (offline-first principle) - val noteEntity = body.toNotEntity() - val localResult = noteDatabase.dao.upsertNote(note = noteEntity) + val noteEntity = body.toDTO() val username = sessionManager.getUserName() val userId = UUID.nameUUIDFromBytes(username?.toByteArray()) val isOnline = connectivityObserver.getCurrentNetworkState() - // 2. Add to sync queue for later synchronization - val syncRecord = SyncRecord( - id = UUID.randomUUID(), - userId = userId.toString(), // Internal user ID - noteId = noteEntity.id, // Assuming noteEntity has an ID - operation = SyncOperation.CREATE, - payload = Json.encodeToString(body), // JSON representation - timeStamp = System.currentTimeMillis().toString() - ) - noteDatabase.syncDao.insertSyncRecord(syncRecord) // 3. Try to sync immediately if online - return if (isOnline) { + if (isOnline) { try { val accessToken = sessionManager.getAccessToken() val response = client.post( @@ -83,30 +65,28 @@ class NoteServiceImpl( header("X-User-Email", HttpRoutes.EMAIL) } - // 4. If sync successful, remove from sync queue - noteDatabase.syncDao.deleteSyncRecord(syncRecord.noteId) - // 5. Update local record with server response if needed val serverNote = response.body() - noteDatabase.dao.upsertNote(serverNote.toNoteEntity()) + noteDatabase.dao.upsertNote(serverNote.toEntity()) - Result.success(serverNote) + Result.Success(serverNote.toDomain()) } catch (e: Exception) { // Network failed, but local save succeeded // SyncRecord remains in queue for background sync - Result.success(noteEntity.toNoteDTO()) // Return local version + Result.Success(noteEntity.toNote()) // Return local version } } else { // Offline - return local result - Result.success(noteEntity.toNoteDTO()) + Result.Success(noteEntity.toNote()) } } catch (e: Exception) { - Result.failure(e) + Result.Error(e as RootError) } } - override suspend fun updateNote(body: NoteRequest): Result { + + override suspend fun updateNote(body: NoteRequest): Result { return try { val accessToken = sessionManager.getAccessToken() val response = client.put( @@ -117,24 +97,24 @@ class NoteServiceImpl( header("Authorization", "Bearer $accessToken") header("X-User-Email", HttpRoutes.EMAIL) } - Result.success(response.body()) + Result.Success(response.body().toDomain()) } catch (e: Exception) { - Result.failure(e) + Result.Error(e as RootError) } } - override suspend fun deleteNote(id: String): Result { - noteDatabase.dao.deleteNoteById(id) + override suspend fun deleteNote(id: String): Result { return try { + noteDatabase.dao.deleteNoteById(id) val response = client.delete( urlString = "${HttpRoutes.NOTES}/$id" ) { header(HttpHeaders.Accept, "application/json") header("X-User-Email", HttpRoutes.EMAIL) } - Result.success(response.body()) + Result.Success(Unit) } catch (e: Exception) { - Result.failure(e) + Result.Error(e as RootError) } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/domain/model/NotesResponse.kt b/note/data/src/main/java/com/example/data/remote/NotesResponse.kt similarity index 68% rename from app/src/main/java/com/example/notemark/main/domain/model/NotesResponse.kt rename to note/data/src/main/java/com/example/data/remote/NotesResponse.kt index 57db901..3f13895 100644 --- a/app/src/main/java/com/example/notemark/main/domain/model/NotesResponse.kt +++ b/note/data/src/main/java/com/example/data/remote/NotesResponse.kt @@ -1,6 +1,5 @@ -package com.example.notemark.main.domain.model +package com.example.data.remote -import com.example.notemark.main.data.remote.NoteDTO import kotlinx.serialization.Serializable @Serializable diff --git a/note/domain/build.gradle.kts b/note/domain/build.gradle.kts index 28db768..89bdc9c 100644 --- a/note/domain/build.gradle.kts +++ b/note/domain/build.gradle.kts @@ -3,5 +3,6 @@ plugins { } dependencies { - + implementation(libs.kotlinx.coroutines.core) + implementation(projects.core.domain) } \ No newline at end of file diff --git a/note/domain/src/main/java/com/example/domain/LocalNoteDataSource.kt b/note/domain/src/main/java/com/example/domain/LocalNoteDataSource.kt new file mode 100644 index 0000000..77a8578 --- /dev/null +++ b/note/domain/src/main/java/com/example/domain/LocalNoteDataSource.kt @@ -0,0 +1,6 @@ +package com.example.domain + +interface LocalNoteDataSource { + suspend fun clearAllNotes() + suspend fun getNotesCount(): Int +} \ No newline at end of file diff --git a/note/domain/src/main/java/com/example/domain/NoteEntity.kt b/note/domain/src/main/java/com/example/domain/NoteEntity.kt new file mode 100644 index 0000000..a83a46f --- /dev/null +++ b/note/domain/src/main/java/com/example/domain/NoteEntity.kt @@ -0,0 +1,9 @@ +package com.example.domain + +data class NoteEntity( + val id: String, + val title: String, + val content: String, + val createdAt: String, + val updatedAt: String? = "" +) \ No newline at end of file diff --git a/note/domain/src/main/java/com/example/domain/NoteService.kt b/note/domain/src/main/java/com/example/domain/NoteService.kt new file mode 100644 index 0000000..ea94af1 --- /dev/null +++ b/note/domain/src/main/java/com/example/domain/NoteService.kt @@ -0,0 +1,21 @@ +package com.example.domain + +interface NoteService { + + suspend fun getNotes( + page: Int, + size: Int, + ): NotesResponse + + suspend fun createNote( + body: NoteRequest + ): Result + + suspend fun updateNote( + body: NoteRequest + ): Result + + suspend fun deleteNote( + id: String + ): Result +} diff --git a/note/domain/src/main/java/com/example/domain/NotesResponse.kt b/note/domain/src/main/java/com/example/domain/NotesResponse.kt new file mode 100644 index 0000000..8be9d5a --- /dev/null +++ b/note/domain/src/main/java/com/example/domain/NotesResponse.kt @@ -0,0 +1,8 @@ +package com.example.domain + +data class NotesResponse( + val notes: List, + val totalPages: Int? = null, + val currentPage: Int? = null, + val totalNotes: Int? = null +) \ No newline at end of file diff --git a/note/presentation/build.gradle.kts b/note/presentation/build.gradle.kts index e40d2c6..393e151 100644 --- a/note/presentation/build.gradle.kts +++ b/note/presentation/build.gradle.kts @@ -3,13 +3,17 @@ plugins { } android { - namespace = "com.example.presentation" + namespace = "com.example.notemark.note.presentation" } dependencies { + implementation(libs.androidx.paging.compose) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.lottie.compose) with(projects) { - implementation(core.domain) implementation(note.domain) + implementation(core.domain) implementation(core.presentation) + implementation(auth.presentation) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/presentation/vm/NoteViewModel.kt b/note/presentation/src/main/java/com/example/presentation/NoteViewModel.kt similarity index 57% rename from app/src/main/java/com/example/notemark/main/presentation/vm/NoteViewModel.kt rename to note/presentation/src/main/java/com/example/presentation/NoteViewModel.kt index 4f4480e..e2f668a 100644 --- a/app/src/main/java/com/example/notemark/main/presentation/vm/NoteViewModel.kt +++ b/note/presentation/src/main/java/com/example/presentation/NoteViewModel.kt @@ -1,17 +1,16 @@ -package com.example.notemark.main.presentation.vm +package com.example.presentation import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.cachedIn -import androidx.paging.map -import com.example.notemark.main.data.local.NoteDatabase -import com.example.notemark.main.data.local.NoteEntity -import com.example.notemark.main.data.remote.NoteDTO -import com.example.notemark.main.data.remote.toNote -import com.example.notemark.main.domain.model.Note -import com.example.notemark.main.domain.model.NoteRequest -import com.example.notemark.main.domain.repository.NotesRepository +import com.example.domain.LocalNoteDataSource +import com.example.domain.Note +import com.example.domain.NoteRequest +import com.example.domain.NoteService +import com.example.domain.Result +import com.example.domain.RootError +import com.example.domain.SessionRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -19,24 +18,19 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class NotesViewModel @Inject constructor( - private val repository: NotesRepository, - private val noteDatabase: NoteDatabase, - pager: Pager + private val service: NoteService, + private val sessionManager: SessionRepository, + private val localDataSource: LocalNoteDataSource, + pager: Pager ) : ViewModel() { val notePagingFlow = pager .flow - .map { pagingData -> - pagingData.map { - it.toNote() - } - } .cachedIn(viewModelScope) private val _createNoteState = MutableStateFlow(NoteUiState()) @@ -48,20 +42,17 @@ class NotesViewModel @Inject constructor( private val _deleteNoteState = MutableStateFlow(DeleteNoteUiState.Idle) val deleteNoteState: StateFlow = _deleteNoteState.asStateFlow() - // Current note being edited (only for edit mode) - private val _currentNote = MutableStateFlow(null) - val currentNote: StateFlow = _currentNote.asStateFlow() + private val _currentNote = MutableStateFlow(null) + val currentNote: StateFlow = _currentNote.asStateFlow() - // Auto-save status (only for edit mode) private val _autoSaveStatus = MutableStateFlow(AutoSaveStatus.Saved) val autoSaveStatus: StateFlow = _autoSaveStatus.asStateFlow() - // Track current mode private val _isEditMode = MutableStateFlow(false) val isEditMode: StateFlow = _isEditMode.asStateFlow() private var autoSaveJob: Job? = null - private val autoSaveDelay = 500L // 500ms debounce + private val autoSaveDelay = 500L sealed class AutoSaveStatus { object Saved : AutoSaveStatus() @@ -69,42 +60,51 @@ class NotesViewModel @Inject constructor( object Error : AutoSaveStatus() } + private val _username = MutableStateFlow(null) + val username: StateFlow = _username.asStateFlow() + + fun getUsername(): String? = sessionManager.getUserName() + + private val _accessToken = MutableStateFlow(null) + val accessToken: StateFlow = _accessToken.asStateFlow() + + fun getAccessToken(): String? = sessionManager.getAccessToken() + + private val _refreshToken = MutableStateFlow(null) + val refreshToken: StateFlow = _refreshToken.asStateFlow() + + fun getRefreshToken(): String? = sessionManager.getRefreshToken() + // CREATE MODE FUNCTIONS fun createNote(noteRequest: NoteRequest) { viewModelScope.launch { _createNoteState.value = _createNoteState.value.copy(isLoading = true, error = null) - val result = repository.createNote(noteRequest) + when (val result = service.createNote(noteRequest)) { + is Result.Success -> { + _createNoteState.value = _createNoteState.value.copy( + isLoading = false, + isSuccess = true, + createdNote = result.data + ) + _currentNote.value = result.data + } + is Result.Error -> { + _createNoteState.value = _createNoteState.value.copy( + isLoading = false, + error = result.error.toString() + ) + } - result.collect { result -> - result.fold( - onSuccess = { data -> - _createNoteState.value = _createNoteState.value.copy( - isLoading = false, - isSuccess = true, - createdNote = data - ) - // Set the created note as current note for tracking - _currentNote.value = data - }, - onFailure = { error -> - _createNoteState.value = _createNoteState.value.copy( - isLoading = false, - error = error.message ?: "Unknown error occurred while creating note." - ) - } - ) } } } - // Function to check if current note is empty (for deletion when user cancels) fun isCurrentNoteEmpty(): Boolean { val note = _currentNote.value ?: return true return note.title.isBlank() || note.title == "Note Title" && note.content.isBlank() } - // Function to delete current note if empty fun deleteCurrentNoteIfEmpty() { val note = _currentNote.value ?: return if (isCurrentNoteEmpty()) { @@ -112,14 +112,13 @@ class NotesViewModel @Inject constructor( } } - // Clear current note when leaving screen fun clearCurrentNote() { _currentNote.value = null autoSaveJob?.cancel() } // EDIT MODE FUNCTIONS - fun enterEditMode(note: NoteDTO) { + fun enterEditMode(note: Note) { _isEditMode.value = true _currentNote.value = note _autoSaveStatus.value = AutoSaveStatus.Saved @@ -132,9 +131,8 @@ class NotesViewModel @Inject constructor( _autoSaveStatus.value = AutoSaveStatus.Saved } - // Auto-save functions (only work in edit mode) fun updateNoteTitle(newTitle: String) { - if (!_isEditMode.value) return // Only work in edit mode + if (!_isEditMode.value) return val current = _currentNote.value ?: return val updatedNote = current.copy(title = newTitle) @@ -143,7 +141,7 @@ class NotesViewModel @Inject constructor( } fun updateNoteContent(newContent: String) { - if (!_isEditMode.value) return // Only work in edit mode + if (!_isEditMode.value) return val current = _currentNote.value ?: return val updatedNote = current.copy(content = newContent) @@ -152,15 +150,11 @@ class NotesViewModel @Inject constructor( } private fun scheduleAutoSave() { - if (!_isEditMode.value) return // Only work in edit mode + if (!_isEditMode.value) return - // Cancel previous auto-save job autoSaveJob?.cancel() - - // Set status to saving _autoSaveStatus.value = AutoSaveStatus.Saving - // Schedule new auto-save job with debounce autoSaveJob = viewModelScope.launch { delay(autoSaveDelay) performAutoSave() @@ -168,7 +162,7 @@ class NotesViewModel @Inject constructor( } private suspend fun performAutoSave() { - if (!_isEditMode.value) return // Only work in edit mode + if (!_isEditMode.value) return val noteToSave = _currentNote.value ?: return @@ -178,35 +172,29 @@ class NotesViewModel @Inject constructor( title = noteToSave.title, content = noteToSave.content, createdAt = noteToSave.createdAt, - updatedAt = System.currentTimeMillis().toString() // Update lastEditedAt + updatedAt = System.currentTimeMillis().toString() ) - val result = repository.updateNote(noteRequest) - - result.collect { result -> - result.fold( - onSuccess = { data -> - _currentNote.value = data - _autoSaveStatus.value = AutoSaveStatus.Saved - }, - onFailure = { error -> - _autoSaveStatus.value = AutoSaveStatus.Error - } - ) + when (val result = service.updateNote(noteRequest)) { + is Result.Success -> { + _currentNote.value = result.data + _autoSaveStatus.value = AutoSaveStatus.Saved + } + is Result.Error -> { + _autoSaveStatus.value = AutoSaveStatus.Error + } } } catch (e: Exception) { _autoSaveStatus.value = AutoSaveStatus.Error } } - // Force save for edit mode (when user finishes editing) fun finishEditing() { if (!_isEditMode.value) return autoSaveJob?.cancel() viewModelScope.launch { performAutoSave() - // Update lastEditedAt to current time when finishing editing _currentNote.value?.let { note -> val updatedNote = note.copy( updatedAt = System.currentTimeMillis().toString() @@ -217,29 +205,26 @@ class NotesViewModel @Inject constructor( } } - // Regular update function (for manual saves, not auto-save) fun updateNote(noteRequest: NoteRequest) { viewModelScope.launch { _updateNoteState.value = _updateNoteState.value.copy(isLoading = true, error = null) - val result = repository.updateNote(noteRequest) - - result.collect { result -> - result.fold( - onSuccess = { data -> - _updateNoteState.value = _updateNoteState.value.copy( - isLoading = false, - isSuccess = true, - createdNote = data - ) - }, - onFailure = { error -> - _updateNoteState.value = _updateNoteState.value.copy( - isLoading = false, - error = error.message ?: "Unknown error occurred while updating note." - ) - } - ) + val result = service.updateNote(noteRequest) + + when (result) { + is com.example.domain.Result.Success -> { + _updateNoteState.value = _updateNoteState.value.copy( + isLoading = false, + isSuccess = true, + createdNote = result.data + ) + } + is com.example.domain.Result.Error -> { + _updateNoteState.value = _updateNoteState.value.copy( + isLoading = false, + error = result.error.toString() + ) + } } } } @@ -249,22 +234,24 @@ class NotesViewModel @Inject constructor( _deleteNoteState.value = DeleteNoteUiState.Loading try { - val result = repository.deleteNote(noteId) - result.fold( - onSuccess = { + when (val result = service.deleteNote(noteId)) { + is Result.Success -> { _deleteNoteState.value = DeleteNoteUiState.Success - }, - onFailure = { error -> - _deleteNoteState.value = DeleteNoteUiState.Error(error.message ?: "Unknown error occurred") } - ) + is Result.Error -> { + _deleteNoteState.value = DeleteNoteUiState.Error( + result.error.toString() + ) + } + } } catch (e: Exception) { - _deleteNoteState.value = DeleteNoteUiState.Error(e.message ?: "Unknown error occurred") + _deleteNoteState.value = DeleteNoteUiState.Error( + e.message ?: "Unknown error occurred" + ) } } } - // Utility functions fun resetDeleteState() { _deleteNoteState.value = DeleteNoteUiState.Idle } @@ -277,10 +264,9 @@ class NotesViewModel @Inject constructor( _updateNoteState.value = NoteUiState() } - // Clear all saved notes locally fun clear() { viewModelScope.launch(Dispatchers.IO) { - noteDatabase.dao.clearAll() + localDataSource.clearAllNotes() } } @@ -300,6 +286,6 @@ sealed class DeleteNoteUiState { data class NoteUiState( val isLoading: Boolean = false, val isSuccess: Boolean = false, - val createdNote: NoteDTO? = null, + val createdNote: Note? = null, val error: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/domain/model/NotesUiState.kt b/note/presentation/src/main/java/com/example/presentation/NotesUiState.kt similarity index 75% rename from app/src/main/java/com/example/notemark/main/domain/model/NotesUiState.kt rename to note/presentation/src/main/java/com/example/presentation/NotesUiState.kt index 0d8ecd8..a71ba43 100644 --- a/app/src/main/java/com/example/notemark/main/domain/model/NotesUiState.kt +++ b/note/presentation/src/main/java/com/example/presentation/NotesUiState.kt @@ -1,4 +1,6 @@ -package com.example.notemark.main.domain.model +package com.example.presentation + +import com.example.domain.Note data class NotesUiState( val notes: List = emptyList(), diff --git a/note/presentation/src/main/java/com/example/presentation/SyncInterval.kt b/note/presentation/src/main/java/com/example/presentation/SyncInterval.kt new file mode 100644 index 0000000..0aa27ad --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/SyncInterval.kt @@ -0,0 +1,8 @@ +package com.example.presentation + +enum class SyncInterval(val displayName: String) { + MANUAL_ONLY("Manual Only"), + EVERY_15_MINUTES("15 minutes"), + EVERY_30_MINUTES("30 minutes"), + EVERY_HOUR("1 hour") +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notemark/main/presentation/screens/note/CreateNote.kt b/note/presentation/src/main/java/com/example/presentation/edit_note/CreateNote.kt similarity index 86% rename from app/src/main/java/com/example/notemark/main/presentation/screens/note/CreateNote.kt rename to note/presentation/src/main/java/com/example/presentation/edit_note/CreateNote.kt index 7151e03..9223c0f 100644 --- a/app/src/main/java/com/example/notemark/main/presentation/screens/note/CreateNote.kt +++ b/note/presentation/src/main/java/com/example/presentation/edit_note/CreateNote.kt @@ -1,12 +1,12 @@ -package com.example.notemark.main.presentation.screens.note +package com.example.presentation.edit_note +import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars @@ -48,13 +48,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController -import com.example.notemark.R -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.main.domain.model.NoteRequest -import com.example.notemark.main.presentation.vm.NotesViewModel +import com.example.presentation.DateFormatter +import com.example.presentation.DeviceConfiguration +import com.example.presentation.NotesViewModel +import com.example.notemark.core.presentation.R +import com.example.domain.NoteRequest +import java.util.UUID @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -63,7 +64,7 @@ fun CreateNoteScreen( ) { val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) + val deviceConfiguration = DeviceConfiguration.Companion.fromWindowSizeClass(windowSizeClass) when(deviceConfiguration) { DeviceConfiguration.MOBILE_PORTRAIT -> { @@ -112,7 +113,7 @@ fun CreateNoteBody( // Log current note changes LaunchedEffect(currentNote) { - android.util.Log.d("CreateNote", "Current note changed: ${currentNote?.id}") + Log.d("CreateNote", "Current note changed: ${currentNote?.id}") currentNote?.let { note -> if (title.isEmpty() || title == "Note Title") title = note.title if (content.isEmpty()) content = note.content @@ -122,11 +123,11 @@ fun CreateNoteBody( // In your CreateNoteBody, modify the LaunchedEffect for creating the note: // Create initial note when screen opens LaunchedEffect(Unit) { - android.util.Log.d("CreateNote", "Screen opened, creating initial note") + Log.d("CreateNote", "Screen opened, creating initial note") focusRequester.requestFocus() // Generate a unique ID for the note - val noteId = java.util.UUID.randomUUID().toString() + val noteId = UUID.randomUUID().toString() val initialNoteRequest = NoteRequest( id = noteId, // Use generated UUID instead of empty string @@ -140,7 +141,7 @@ fun CreateNoteBody( // Navigate after successful CREATE LaunchedEffect(createNoteState.isSuccess) { if (createNoteState.isSuccess) { - android.util.Log.d("CreateNote", "Note created successfully: ${createNoteState.createdNote?.id}") + Log.d("CreateNote", "Note created successfully: ${createNoteState.createdNote?.id}") viewModel.resetCreateNoteState() } } @@ -148,7 +149,7 @@ fun CreateNoteBody( // Navigate after successful UPDATE LaunchedEffect(noteState.isSuccess) { if (noteState.isSuccess) { - android.util.Log.d("CreateNote", "Note updated successfully, navigating back") + Log.d("CreateNote", "Note updated successfully, navigating back") viewModel.resetUpdateNoteState() viewModel.clearCurrentNote() navController.navigateUp() @@ -157,7 +158,7 @@ fun CreateNoteBody( // Handle back navigation BackHandler { - android.util.Log.d("CreateNote", "Back pressed") + Log.d("CreateNote", "Back pressed") handleCreateModeBackNavigation(viewModel, navController) } @@ -168,7 +169,7 @@ fun CreateNoteBody( navigationIcon = { IconButton( onClick = { - android.util.Log.d("CreateNote", "X button clicked") + Log.d("CreateNote", "X button clicked") handleCreateModeBackNavigation(viewModel, navController) } ) { @@ -181,26 +182,26 @@ fun CreateNoteBody( actions = { TextButton( onClick = { - android.util.Log.d("CreateNote", "Save button clicked") - android.util.Log.d("CreateNote", "Title: '$title'") - android.util.Log.d("CreateNote", "Content: '$content'") - android.util.Log.d("CreateNote", "CurrentNote: ${currentNote?.id}") - android.util.Log.d("CreateNote", "isLoading: ${noteState.isLoading}") + Log.d("CreateNote", "Save button clicked") + Log.d("CreateNote", "Title: '$title'") + Log.d("CreateNote", "Content: '$content'") + Log.d("CreateNote", "CurrentNote: ${currentNote?.id}") + Log.d("CreateNote", "isLoading: ${noteState.isLoading}") if (title.isNotBlank() || content.isNotBlank()) { currentNote?.let { note -> - android.util.Log.d("CreateNote", "Updating note with id: ${note.id}") + Log.d("CreateNote", "Updating note with id: ${note.id}") val noteRequest = NoteRequest( id = note.id, title = title.trim(), content = content.trim(), createdAt = note.createdAt, - updatedAt = System.currentTimeMillis().toString() + updatedAt = DateFormatter.getCurrentIsoString() ) viewModel.updateNote(noteRequest) - } ?: android.util.Log.e("CreateNote", "CurrentNote is NULL!") + } ?: Log.e("CreateNote", "CurrentNote is NULL!") } else { - android.util.Log.d("CreateNote", "Note is empty, navigating back") + Log.d("CreateNote", "Note is empty, navigating back") handleCreateModeBackNavigation(viewModel, navController) } }, diff --git a/app/src/main/java/com/example/notemark/main/presentation/screens/note/EditNote.kt b/note/presentation/src/main/java/com/example/presentation/edit_note/EditNote.kt similarity index 90% rename from app/src/main/java/com/example/notemark/main/presentation/screens/note/EditNote.kt rename to note/presentation/src/main/java/com/example/presentation/edit_note/EditNote.kt index fac6cf8..2c6d864 100644 --- a/app/src/main/java/com/example/notemark/main/presentation/screens/note/EditNote.kt +++ b/note/presentation/src/main/java/com/example/presentation/edit_note/EditNote.kt @@ -1,4 +1,4 @@ -package com.example.notemark.main.presentation.screens.note +package com.example.presentation.edit_note import android.widget.Toast import androidx.activity.compose.BackHandler @@ -46,21 +46,19 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController -import androidx.navigation.compose.rememberNavController import androidx.paging.compose.collectAsLazyPagingItems -import com.example.notemark.R -import com.example.notemark.auth.presentation.util.DeviceConfiguration -import com.example.notemark.main.DateFormatter -import com.example.notemark.main.domain.model.NoteRequest -import com.example.notemark.main.domain.model.getFormattedCreatedAt -import com.example.notemark.main.presentation.vm.NotesViewModel -import com.example.notemark.navigation.screens.HomeScreens +import com.example.presentation.DateFormatter +import com.example.presentation.DeviceConfiguration +import com.example.presentation.NotesViewModel +import com.example.notemark.core.presentation.R +import com.example.domain.NoteRequest +import com.example.presentation.getFormattedCreatedAt @OptIn(ExperimentalMaterial3Api::class) @Composable fun EditNoteScreen( - navController: NavController = rememberNavController(), + onNavigateHome: () -> Unit = {}, + onNavigateUp: () -> Unit = {}, noteId: String, ) { @@ -71,7 +69,8 @@ fun EditNoteScreen( DeviceConfiguration.MOBILE_PORTRAIT -> { EditNoteScreenBody( modifier = Modifier, - navController = navController, + onNavigateToHome = onNavigateHome, + onNavigateUp = onNavigateUp, noteId = noteId ) } @@ -84,7 +83,8 @@ fun EditNoteScreen( .windowInsetsPadding(WindowInsets.displayCutout) .fillMaxSize() .consumeWindowInsets(WindowInsets.navigationBars), - navController = navController, + onNavigateToHome = onNavigateHome, + onNavigateUp = onNavigateUp, noteId = noteId ) } @@ -93,7 +93,8 @@ fun EditNoteScreen( DeviceConfiguration.DESKTOP -> { EditNoteScreenBody( modifier = Modifier, - navController = navController, + onNavigateToHome = onNavigateHome, + onNavigateUp = onNavigateUp, noteId = noteId ) } @@ -104,7 +105,8 @@ fun EditNoteScreen( @OptIn(ExperimentalMaterial3Api::class) private fun EditNoteScreenBody( modifier: Modifier, - navController: NavController, + onNavigateToHome: () -> Unit = {}, + onNavigateUp: () -> Unit = {}, noteId: String, ) { @@ -116,15 +118,7 @@ private fun EditNoteScreenBody( val notes = viewModel.notePagingFlow.collectAsLazyPagingItems() val note = notes.itemSnapshotList.items.find { it.id == noteId } - LaunchedEffect(noteState) { - if (noteState.isSuccess) { - navController.navigate(HomeScreens.Home.route) { - popUpTo(HomeScreens.Home.route) { - inclusive = true - } - } - } - } + LaunchedEffect(noteState) { if (noteState.isSuccess) { onNavigateToHome() } } LaunchedEffect(noteState.error) { noteState.error?.let { error -> @@ -146,7 +140,7 @@ private fun EditNoteScreenBody( if (hasChanges) { showExitDialog = true } else { - navController.popBackStack() + onNavigateUp() } } @@ -164,7 +158,7 @@ private fun EditNoteScreenBody( if (hasChanges) { showExitDialog = true } else { - navController.popBackStack() + onNavigateUp() } } ) { @@ -295,7 +289,7 @@ private fun EditNoteScreenBody( TextButton( onClick = { showExitDialog = false - navController.navigateUp() + onNavigateUp() } ) { Text( diff --git a/note/presentation/src/main/java/com/example/presentation/home/Home.kt b/note/presentation/src/main/java/com/example/presentation/home/Home.kt new file mode 100644 index 0000000..f327d3e --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/home/Home.kt @@ -0,0 +1,340 @@ +package com.example.presentation.home + +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.itemKey +import com.example.presentation.DateFormatter +import com.example.presentation.DeviceConfiguration +import com.example.presentation.NoteUiState +import com.example.presentation.NotesViewModel +import com.example.notemark.core.presentation.R +import com.example.domain.Note +import com.example.domain.NoteRequest +import com.example.presentation.getInitials +import com.example.presentation.home.components.EmptyState +import com.example.presentation.home.components.NoteItem +import java.util.UUID + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HomeScreen( + connectivityState: State, + notesList: LazyPagingItems, + noteUiState: State, + onNavigateToDetailsWithId: (String) -> Unit, + onNavigateToCreateNote: () -> Unit, + onNavigateToLogin: () -> Unit, + onNavigateToSettings: () -> Unit, + onNavigateToHome: () -> Unit, + username: String?, + accessToken: String?, + refreshToken: String? + ) { + + LaunchedEffect(Unit) { if (accessToken.isNullOrEmpty() && refreshToken.isNullOrEmpty()) { onNavigateToLogin() } } + + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) + + when(deviceConfiguration) { + DeviceConfiguration.MOBILE_PORTRAIT -> { + MainContent( + modifier = Modifier, + onNavigateToDetailsWithId = onNavigateToDetailsWithId, + onNavigateToCreateNote = onNavigateToCreateNote, + onNavigateToSettings = onNavigateToSettings, + onNavigateToHome = onNavigateToHome, + username = username, + notes = notesList, + connectivityState = connectivityState, + columnCount = 2 + ) + } + DeviceConfiguration.MOBILE_LANDSCAPE -> { + MainContent( + modifier = Modifier + .background( + MaterialTheme.colorScheme.surface + ) + .windowInsetsPadding(WindowInsets.displayCutout) + .fillMaxSize() + .consumeWindowInsets(WindowInsets.navigationBars), + onNavigateToDetailsWithId = onNavigateToDetailsWithId, + onNavigateToCreateNote = onNavigateToCreateNote, + onNavigateToSettings = onNavigateToSettings, + onNavigateToHome = onNavigateToHome, + username = username, + notes = notesList, + connectivityState = connectivityState, + columnCount = 3 + ) + } + DeviceConfiguration.TABLET_PORTRAIT, + DeviceConfiguration.TABLET_LANDSCAPE, + DeviceConfiguration.DESKTOP -> { + MainContent( + modifier = Modifier, + onNavigateToDetailsWithId = onNavigateToDetailsWithId, + onNavigateToCreateNote = onNavigateToCreateNote, + onNavigateToSettings = onNavigateToSettings, + onNavigateToHome = onNavigateToHome, + username = username, + notes = notesList, + connectivityState = connectivityState, + columnCount = 3 + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun MainContent( + modifier: Modifier, + onNavigateToDetailsWithId: (String) -> Unit = {}, + onNavigateToCreateNote: () -> Unit = {}, + onNavigateToSettings: () -> Unit = {}, + onNavigateToHome: () -> Unit = {}, + username: String?, + notes: LazyPagingItems, + connectivityState: State, + columnCount: Int, +) { + val context = LocalContext.current + val uuid = remember { UUID.randomUUID() } + val noteViewModel = hiltViewModel() + val noteState by noteViewModel.createNoteState.collectAsStateWithLifecycle() + val creationTime by remember { mutableStateOf(DateFormatter.getCurrentIsoString()) } + + LaunchedEffect(notes.loadState) { + when { + notes.loadState.refresh is LoadState.Error -> { + val error = (notes.loadState.refresh as LoadState.Error).error + Log.e("Home", "Refresh error: ${error.message}") + if (notes.itemCount == 0) { + Toast.makeText( + context, + "Unable to load notes. Showing cached data.", + Toast.LENGTH_SHORT + ).show() + } + } + notes.loadState.append is LoadState.Error -> { + val error = (notes.loadState.append as LoadState.Error).error + Log.e("Home", "Append error: ${error.message}") + } + } + } + + val initials = getInitials(username ?: "U") + + LaunchedEffect(noteState) { if (noteState.isSuccess) onNavigateToHome() } + + LaunchedEffect(noteState.error) { + noteState.error?.let { error -> + Toast.makeText(context, error, Toast.LENGTH_LONG).show() + Log.d( + "CreateNoteScreen", + "CreateNoteScreen: ${noteState.error}" + ) + } + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(end = 4.dp) + ) + if (!connectivityState.value) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.cloud_off), + contentDescription = "Offline", + tint = Color(0x66535364), + ) + } + } + }, + actions = { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.settings), + contentDescription = "Settings", + modifier = Modifier + .clickable { onNavigateToSettings() } + .padding(end = 16.dp) + ) + Text( + modifier = Modifier + .padding(end = 16.dp) + .size(40.dp) + .clip(RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.primary) + .padding(10.dp), + text = initials, + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onPrimary, + textAlign = TextAlign.Center + ) + ) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.onPrimary + ) + ) + }, + + floatingActionButton = { + FloatingActionButton( + onClick = { + noteViewModel.createNote( + NoteRequest( + id = uuid.toString(), + title = "Note Title", + content = "", + createdAt = creationTime, + updatedAt = creationTime, + ) + ) + onNavigateToCreateNote() + }, + modifier = Modifier + .size(64.dp) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color(0xFF58A1F8), + Color(0xFF5A4CF7) + ) + ), + shape = RoundedCornerShape(20.dp) + ), + containerColor = Color.Transparent, + elevation = FloatingActionButtonDefaults.elevation(0.dp) + ) { + Icon( + tint = MaterialTheme.colorScheme.onPrimary, + painter = painterResource(R.drawable.plus), + contentDescription = null + ) + } + }, + contentWindowInsets = WindowInsets.statusBars + ) { innerPadding -> + Column( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surface) + .padding(innerPadding) + ) { + if (notes.loadState.refresh is LoadState.Loading) { + CircularProgressIndicator( + modifier = Modifier + .size(24.dp) + .align(Alignment.CenterHorizontally) + ) + } else { + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Fixed(columnCount), + state = rememberLazyStaggeredGridState(), + modifier = Modifier.weight(1f), + contentPadding = PaddingValues(16.dp), + verticalItemSpacing = 8.dp, + horizontalArrangement = Arrangement.spacedBy(8.dp), + content = { + items( + notes.itemCount, + key = notes.itemKey { it.id } + ) { index -> + notes[index]?.let { + NoteItem( + modifier = Modifier, + onNavigateToDetailsWithId = { noteId -> + onNavigateToDetailsWithId(noteId) + }, + note = it, + notes = notes + ) + } + } + if (notes.loadState.append is LoadState.Loading) { + item(span = StaggeredGridItemSpan.FullLine) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp) + ) + } + } + } + } + ) + } + EmptyState(notes) + } + } +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/home/components/ActionContent.kt b/note/presentation/src/main/java/com/example/presentation/home/components/ActionContent.kt new file mode 100644 index 0000000..52b3ef3 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/home/components/ActionContent.kt @@ -0,0 +1,106 @@ +package com.example.presentation.home.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +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.size +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +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.res.painterResource +import androidx.compose.ui.unit.dp +import com.example.presentation.DeleteNoteUiState +import com.example.notemark.core.presentation.R + +@Composable +internal fun ActionContent( + deleteNoteState: DeleteNoteUiState, + onDeleteClick: () -> Unit, +) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (deleteNoteState is DeleteNoteUiState.Success) { + SuccessContent() + } else { + Card( + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.medium) + .clickable( + enabled = deleteNoteState !is DeleteNoteUiState.Loading + ) { + onDeleteClick() + }, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface + ), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + ListItem( + colors = ListItemDefaults.colors( + containerColor = Color.Transparent + ), + headlineContent = { + Text( + text = when (deleteNoteState) { + is DeleteNoteUiState.Loading -> "Deleting..." + else -> "Delete Note?" + }, + style = MaterialTheme.typography.bodyLarge.copy( + color = if (deleteNoteState is DeleteNoteUiState.Loading) { + MaterialTheme.colorScheme.onSurfaceVariant + } else { + MaterialTheme.colorScheme.error + } + ) + ) + }, + supportingContent = { + Text( + text = "Are you sure you want to delete this note? This action cannot be undone.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + leadingContent = { + Box( + modifier = Modifier.size(40.dp), + contentAlignment = Alignment.Center + ) { + if (deleteNoteState is DeleteNoteUiState.Loading) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + strokeWidth = 2.dp, + color = MaterialTheme.colorScheme.primary + ) + } else { + Icon( + painter = painterResource(R.drawable.outline_delete_outline_24), + contentDescription = "Delete", + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(24.dp) + ) + } + } + } + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + } +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/home/components/EmptyState.kt b/note/presentation/src/main/java/com/example/presentation/home/components/EmptyState.kt new file mode 100644 index 0000000..559fb29 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/home/components/EmptyState.kt @@ -0,0 +1,38 @@ +package com.example.presentation.home.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import com.example.notemark.core.presentation.R +import com.example.domain.Note + +@Composable +internal fun EmptyState(notes: LazyPagingItems) { + + if (notes.loadState.refresh !is LoadState.Loading && notes.itemSnapshotList.items.isEmpty() && !notes.loadState.hasError) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(top = 48.dp), + contentAlignment = Alignment.TopCenter + ) { + Text( + text = stringResource(R.string.empty_container_text), + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center + ) + ) + } + } +} diff --git a/note/presentation/src/main/java/com/example/presentation/home/components/NoteActionSheet.kt b/note/presentation/src/main/java/com/example/presentation/home/components/NoteActionSheet.kt new file mode 100644 index 0000000..03e1b40 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/home/components/NoteActionSheet.kt @@ -0,0 +1,78 @@ +package com.example.presentation.home.components + +import android.widget.Toast +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.compose.LazyPagingItems +import com.example.domain.Note +import com.example.presentation.DeleteNoteUiState +import com.example.presentation.NotesViewModel +import kotlinx.coroutines.delay + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NoteActionSheet( + isVisible: Boolean, + onDismissSheet: () -> Unit, + noteId: String, + notes: LazyPagingItems +) { + val context = LocalContext.current + val viewModel: NotesViewModel = hiltViewModel() + val deleteNoteState by viewModel.deleteNoteState.collectAsStateWithLifecycle() + val onDeleteClick = { viewModel.deleteNote(noteId = noteId) } + + LaunchedEffect(deleteNoteState) { + when (deleteNoteState) { + is DeleteNoteUiState.Success -> { + delay(2_000L) + notes.refresh() + viewModel.resetDeleteState() + onDismissSheet() + } + is DeleteNoteUiState.Error -> { + Toast.makeText( + context, + (deleteNoteState as DeleteNoteUiState.Error).message, + Toast.LENGTH_SHORT + ).show() + viewModel.resetDeleteState() + } + else -> {} + } + } + + if (isVisible) { + ModalBottomSheet( + onDismissRequest = { + onDismissSheet() + }, + containerColor = MaterialTheme.colorScheme.surfaceContainerLowest, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + ActionContent( + deleteNoteState = deleteNoteState, + onDeleteClick = onDeleteClick, + ) + } + } + } +} diff --git a/note/presentation/src/main/java/com/example/presentation/home/components/NoteItem.kt b/note/presentation/src/main/java/com/example/presentation/home/components/NoteItem.kt new file mode 100644 index 0000000..a04c50e --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/home/components/NoteItem.kt @@ -0,0 +1,96 @@ +package com.example.presentation.home.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.paging.compose.LazyPagingItems +import com.example.domain.Note +import com.example.presentation.DeviceConfiguration +import com.example.presentation.getFormattedCreatedAt +import com.example.presentation.truncateAtWord + +@Composable +internal fun NoteItem( + modifier: Modifier, + onNavigateToDetailsWithId: (String) -> Unit, + note: Note, + notes: LazyPagingItems +) { + + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val deviceConfiguration = DeviceConfiguration.Companion.fromWindowSizeClass(windowSizeClass) + val isPhone = deviceConfiguration == DeviceConfiguration.MOBILE_PORTRAIT + + var contextMenuNoteId by rememberSaveable { mutableStateOf(null) } + val haptics = LocalHapticFeedback.current + + Column( + modifier = modifier + .combinedClickable( + onClick = { onNavigateToDetailsWithId(note.id) }, + onLongClick = { + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + contextMenuNoteId = note.id + }, + ) + .background( + MaterialTheme.colorScheme.surfaceContainerLowest, + RoundedCornerShape(16.dp) + ) + .padding(16.dp), + horizontalAlignment = Alignment.Start, + ) { + + Text( + text = note.getFormattedCreatedAt(), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = note.title, + style = MaterialTheme.typography.titleMedium.copy( + color = MaterialTheme.colorScheme.onSurface + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + Text( + text = note.content.truncateAtWord(if (isPhone) 150 else 250), + style = MaterialTheme.typography.bodySmall, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + if (contextMenuNoteId != null) { + NoteActionSheet( + isVisible = note.id == contextMenuNoteId, + onDismissSheet = { contextMenuNoteId = null }, + + noteId = contextMenuNoteId ?: "", + notes = notes + ) + } +} diff --git a/note/presentation/src/main/java/com/example/presentation/home/components/SuccessContent.kt b/note/presentation/src/main/java/com/example/presentation/home/components/SuccessContent.kt new file mode 100644 index 0000000..b35fde9 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/home/components/SuccessContent.kt @@ -0,0 +1,74 @@ +package com.example.presentation.home.components + +import androidx.compose.foundation.layout.Box +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.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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 com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.rememberLottieAnimatable +import com.airbnb.lottie.compose.rememberLottieComposition +import com.example.notemark.core.presentation.R + +@Composable +internal fun SuccessContent() { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val animationState = rememberLottieAnimatable() + val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.success)) + + + LaunchedEffect(composition) { + composition?.let { + animationState.animate( + composition = it, + iterations = 1 + ) + } + } + + Box( + modifier = Modifier.size(120.dp), + contentAlignment = Alignment.Center + ) { + if (composition != null) { + LottieAnimation( + composition = composition, + progress = { animationState.progress }, + modifier = Modifier.size(120.dp) + ) + } else { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + strokeWidth = 2.dp + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Note deleted successfully!", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center + ) + } +} diff --git a/app/src/main/java/com/example/notemark/main/presentation/vm/DetailsViewModel.kt b/note/presentation/src/main/java/com/example/presentation/note_details/DetailsViewModel.kt similarity index 98% rename from app/src/main/java/com/example/notemark/main/presentation/vm/DetailsViewModel.kt rename to note/presentation/src/main/java/com/example/presentation/note_details/DetailsViewModel.kt index 8748606..73e7a67 100644 --- a/app/src/main/java/com/example/notemark/main/presentation/vm/DetailsViewModel.kt +++ b/note/presentation/src/main/java/com/example/presentation/note_details/DetailsViewModel.kt @@ -1,4 +1,4 @@ -package com.example.notemark.main.presentation.vm +package com.example.presentation.note_details import android.content.pm.ActivityInfo import androidx.lifecycle.ViewModel diff --git a/note/presentation/src/main/java/com/example/presentation/note_details/NoteDetails.kt b/note/presentation/src/main/java/com/example/presentation/note_details/NoteDetails.kt new file mode 100644 index 0000000..2d671ba --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/note_details/NoteDetails.kt @@ -0,0 +1,71 @@ +package com.example.presentation.note_details + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import androidx.paging.compose.LazyPagingItems +import com.example.domain.Note +import com.example.presentation.DeviceConfiguration +import com.example.presentation.note_details.components.DetailsContent +import com.example.presentation.note_details.components.LandscapeDetailsScreenContent +import kotlin.collections.find + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DetailsScreen( + navController: NavHostController = rememberNavController(), + noteId: String? = null, + notes: LazyPagingItems +) { + val note = notes.itemSnapshotList.items.find { it.id == noteId } + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) + + when(deviceConfiguration) { + DeviceConfiguration.MOBILE_PORTRAIT -> { + DetailsContent( + modifier = Modifier, + navController = navController, + note = note, + noteId = noteId, + ) + } + DeviceConfiguration.MOBILE_LANDSCAPE -> { + LandscapeDetailsScreenContent( + modifier = Modifier + .windowInsetsPadding(WindowInsets.displayCutout) + .consumeWindowInsets(WindowInsets.navigationBars), + navController = navController, + note = note, + noteId = noteId, + ) + } + DeviceConfiguration.TABLET_PORTRAIT, + DeviceConfiguration.TABLET_LANDSCAPE -> { + LandscapeDetailsScreenContent( + modifier = Modifier + .windowInsetsPadding(WindowInsets.displayCutout) + .consumeWindowInsets(WindowInsets.navigationBars), + navController = navController, + note = note, + noteId = noteId, + ) + } + DeviceConfiguration.DESKTOP -> { + DetailsContent( + modifier = Modifier, + navController = navController, + note = note, + noteId = noteId, + ) + } + } +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/note_details/components/DetailsContent.kt b/note/presentation/src/main/java/com/example/presentation/note_details/components/DetailsContent.kt new file mode 100644 index 0000000..eebfda2 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/note_details/components/DetailsContent.kt @@ -0,0 +1,266 @@ +package com.example.presentation.note_details.components + +import android.app.Activity +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.example.notemark.core.presentation.R +import com.example.domain.Note +import com.example.presentation.getFormattedUpdatedAt +import com.example.presentation.note_details.DetailsViewModel + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +internal fun DetailsContent( + modifier: Modifier, + navController: NavHostController, + note: Note?, + noteId: String?, +) { + val context = LocalContext.current + val activity = context as Activity + + val scrollState = rememberScrollState() + val viewModel: DetailsViewModel = viewModel() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(uiState.requestedOrientation) { + uiState.requestedOrientation?.let { orientation -> + activity.requestedOrientation = orientation + viewModel.onOrientationHandled() + } + } + + LaunchedEffect(Unit) { + viewModel.setOriginalOrientation(activity.requestedOrientation) + } + + BackHandler(enabled = uiState.isReaderMode) { + viewModel.toggleReaderMode() + } + + LaunchedEffect(scrollState.value) { + if (scrollState.isScrollInProgress) { + viewModel.onScrollDetected() + } + } + + Scaffold( + topBar = { + AnimatedVisibility( + visible = !uiState.isReaderMode || uiState.isUiVisible, + enter = fadeIn(animationSpec = tween(300)) + slideInVertically( + animationSpec = tween(300) + ) { -it }, + exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( + animationSpec = tween(300) + ) { -it } + ) { + TopAppBar( + title = { + Text( + text = stringResource(R.string.all_notes).uppercase(), + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 1.0.sp, + fontFamily = FontFamily(Font(R.font.space_grotesk_regular)), + ) + }, + navigationIcon = { + IconButton( + onClick = { + if (uiState.isReaderMode) viewModel.toggleReaderMode() else navController.popBackStack() + } + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ios_arrow_left), + contentDescription = "Navigate up", + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.onPrimary + ) + ) + } + } + ) { innerPadding -> + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.onPrimary) + .padding( + if (!uiState.isReaderMode || uiState.isUiVisible) innerPadding else PaddingValues( + 0.dp + ) + ) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + viewModel.onScreenTap() + }, + ) { + if (note != null && note.id == noteId) { + Column( + modifier = Modifier.verticalScroll(scrollState) + ) { + Text( + text = note.title, + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onSurface, + ), + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + + HorizontalDivider() + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = stringResource(R.string.date_created), + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + ) + Text( + text = note.getFormattedUpdatedAt(), + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold + ) + ) + } + if (!note.getFormattedUpdatedAt().isEmpty()){ + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = stringResource(R.string.last_edited), + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + ) + Text( + text = note.getFormattedUpdatedAt(), + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold + ) + ) + } + } + } + + HorizontalDivider() + + Text( + text = note.content, + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + ), + modifier = Modifier + .fillMaxWidth() + .padding( + if (uiState.isReaderMode && !uiState.isUiVisible) { + PaddingValues(horizontal = 24.dp, vertical = 32.dp) + } else { + PaddingValues(16.dp) + } + ) + ) + } + } else { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + AnimatedVisibility( + visible = !uiState.isReaderMode || uiState.isUiVisible, + enter = fadeIn(animationSpec = tween(300)) + slideInVertically( + animationSpec = tween(300) + ) { it }, + exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( + animationSpec = tween(300) + ) { it }, + modifier = Modifier.align(Alignment.BottomCenter) + ) { + ExtendedFabFSheet( + onEditClick = { + // if we are in reader mode, first exit reader mode then handle the navigation +// navController.navigate( +// HomeScreens.EditNote( +// noteId = noteId ?: "" +// ) +// ) + }, + onReaderModeClick = { + viewModel.toggleReaderMode() + }, + isReaderMode = uiState.isReaderMode + ) + } + } + } +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/note_details/components/ExtendedFabFSheet.kt b/note/presentation/src/main/java/com/example/presentation/note_details/components/ExtendedFabFSheet.kt new file mode 100644 index 0000000..b120b70 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/note_details/components/ExtendedFabFSheet.kt @@ -0,0 +1,69 @@ +package com.example.presentation.note_details.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +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.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.example.notemark.core.presentation.R + +@Composable +internal fun ExtendedFabFSheet( + modifier: Modifier = Modifier, + onEditClick: () -> Unit, + onReaderModeClick: () -> Unit, + isReaderMode: Boolean = false +) { + Row( + modifier = modifier + .padding(16.dp) + .background( + MaterialTheme.colorScheme.surface, + shape = MaterialTheme.shapes.medium + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + IconButton( + onClick = onEditClick + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.edit), + contentDescription = null + ) + } + + IconButton( + onClick = onReaderModeClick, + modifier = Modifier + .then( + if (isReaderMode) { + Modifier + .padding(4.dp) + .background( + color = Color(0x1A5977F7), + shape = MaterialTheme.shapes.medium + ) + } else { + Modifier + } + ) + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.book_open), + contentDescription = null, + tint = if (isReaderMode) MaterialTheme.colorScheme.primary else LocalContentColor.current + ) + } + } +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/note_details/components/LandscapeDetailsScreenContent.kt b/note/presentation/src/main/java/com/example/presentation/note_details/components/LandscapeDetailsScreenContent.kt new file mode 100644 index 0000000..2b453c4 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/note_details/components/LandscapeDetailsScreenContent.kt @@ -0,0 +1,267 @@ +package com.example.presentation.note_details.components + +import android.app.Activity +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.example.notemark.core.presentation.R +import com.example.domain.Note +import com.example.presentation.getFormattedUpdatedAt +import com.example.presentation.note_details.DetailsViewModel + +@Composable +internal fun LandscapeDetailsScreenContent( + modifier: Modifier, + navController: NavHostController, + note: Note?, + noteId: String? +) { + val context = LocalContext.current + val activity = context as Activity + val scrollState = rememberScrollState() + val viewModel: DetailsViewModel = viewModel() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(uiState.requestedOrientation) { + uiState.requestedOrientation?.let { orientation -> + activity.requestedOrientation = orientation + viewModel.onOrientationHandled() + } + } + + LaunchedEffect(Unit) { + viewModel.setOriginalOrientation(activity.requestedOrientation) + } + + BackHandler(enabled = uiState.isReaderMode) { + viewModel.toggleReaderMode() + } + + LaunchedEffect(scrollState.value) { + if (scrollState.isScrollInProgress) { + viewModel.onScrollDetected() + } + } + + Box( + modifier = modifier.fillMaxSize() + ) { + // Main content - this stays in place + Row( + modifier = Modifier + .fillMaxSize() + .padding(start = 80.dp) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + viewModel.onScreenTap() + } + ) { + // Left spacer to account for back button (keep content centered) + Spacer(modifier = Modifier.width(64.dp)) // Adjust based on your back button width + + // Content area + Box( + modifier = Modifier + .fillMaxSize() + .padding(top = 24.dp, bottom = 80.dp) // Add bottom padding for FAB + ) { + if (note != null && note.id == noteId) { + Column( + modifier = Modifier.verticalScroll(scrollState) + ) { + Text( + text = note.title, + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onSurface, + ), + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + + HorizontalDivider() + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = stringResource(R.string.date_created), + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + ) + Text( + text = note.getFormattedUpdatedAt(), + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold + ) + ) + } + if (!note.getFormattedUpdatedAt().isEmpty()) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = stringResource(R.string.last_edited), + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + ) + Text( + text = note.getFormattedUpdatedAt(), + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold + ) + ) + } + } + } + + HorizontalDivider() + + Text( + text = note.content, + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + ), + modifier = Modifier + .fillMaxWidth() + .padding( + if (uiState.isReaderMode && !uiState.isUiVisible) { + PaddingValues(horizontal = 16.dp, vertical = 32.dp) + } else { + PaddingValues(horizontal = 16.dp, vertical = 32.dp) + } + ) + ) + } + } else { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + } + } + + // Back button overlay - positioned absolutely at top-left + AnimatedVisibility( + visible = !uiState.isReaderMode || uiState.isUiVisible, + enter = fadeIn(animationSpec = tween(300)) + slideInVertically( + animationSpec = tween(300) + ) { -it }, + exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( + animationSpec = tween(300) + ) { -it }, + modifier = Modifier + .align(Alignment.TopStart) + .padding(top = 24.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = { + if (uiState.isReaderMode) viewModel.toggleReaderMode() else navController.popBackStack() + } + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ios_arrow_left), + contentDescription = "Navigate up", + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = stringResource(R.string.all_notes).uppercase(), + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 1.0.sp, + fontFamily = FontFamily(Font(R.font.space_grotesk_regular)), + ) + } + } + + // FAB overlay - positioned absolutely at bottom-center + AnimatedVisibility( + visible = !uiState.isReaderMode || uiState.isUiVisible, + enter = fadeIn(animationSpec = tween(300)) + slideInVertically( + animationSpec = tween(300) + ) { it }, + exit = fadeOut(animationSpec = tween(300)) + slideOutVertically( + animationSpec = tween(300) + ) { it }, + modifier = Modifier.align(Alignment.BottomCenter) + ) { + ExtendedFabFSheet( + onEditClick = { +// navController.navigate( +// HomeScreens.EditNote( +// noteId = noteId ?: "" +// ) +// ) + }, + onReaderModeClick = { viewModel.toggleReaderMode() }, + isReaderMode = uiState.isReaderMode + ) + } + } +} diff --git a/note/presentation/src/main/java/com/example/presentation/settings/SettingsScreen.kt b/note/presentation/src/main/java/com/example/presentation/settings/SettingsScreen.kt new file mode 100644 index 0000000..76e7002 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/settings/SettingsScreen.kt @@ -0,0 +1,40 @@ +package com.example.presentation.settings + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import com.example.presentation.DeviceConfiguration +import com.example.presentation.settings.components.SettingsItem + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + onNavigateUp: () -> Unit = {}, + onNavigateToLanding: () -> Unit = {} +) { + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val deviceConfiguration = DeviceConfiguration.fromWindowSizeClass(windowSizeClass) + + when(deviceConfiguration) { + DeviceConfiguration.MOBILE_PORTRAIT -> { + SettingsItem( + onNavigateUp = onNavigateUp, + onNavigateToLanding = onNavigateToLanding + ) + } + DeviceConfiguration.MOBILE_LANDSCAPE -> { + SettingsItem( + onNavigateUp = onNavigateUp, + onNavigateToLanding = onNavigateToLanding + ) + } + DeviceConfiguration.TABLET_PORTRAIT, + DeviceConfiguration.TABLET_LANDSCAPE, + DeviceConfiguration.DESKTOP -> { + SettingsItem( + onNavigateUp = onNavigateUp, + onNavigateToLanding = onNavigateToLanding + ) + } + } +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/settings/components/SettingsContent.kt b/note/presentation/src/main/java/com/example/presentation/settings/components/SettingsContent.kt new file mode 100644 index 0000000..303a80e --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/settings/components/SettingsContent.kt @@ -0,0 +1,111 @@ +package com.example.presentation.settings.components + +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.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +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.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.example.notemark.core.presentation.R +import com.example.presentation.SyncInterval + +@Composable +internal fun SettingsContent( + onLogoutClick: () -> Unit, + onSyncClick: () -> Unit, + onSyncIntervalClick: () -> Unit, + showSyncIntervalDropdown: Boolean, + onDismissSyncIntervalDropdown: () -> Unit, + selectedSyncInterval: SyncInterval, + onSyncIntervalSelected: (SyncInterval) -> Unit +) { + SingleItem( + onEndIconAndTextClick = onSyncIntervalClick, + supportingText = null, + headline = stringResource(R.string.sync_interval), + headlineTextColor = MaterialTheme.colorScheme.onSurface, + leadingIcon = ImageVector.vectorResource(R.drawable.clock), + iconTint = MaterialTheme.colorScheme.onSurfaceVariant, + endIcon = ImageVector.vectorResource(R.drawable.chevron_right), + endIconTint = MaterialTheme.colorScheme.onSurfaceVariant, + endText = selectedSyncInterval.displayName, + endTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + showDropdown = showSyncIntervalDropdown, + onDismissDropdown = onDismissSyncIntervalDropdown, + dropdownContent = { + SyncInterval.entries.forEach { interval -> + DropdownMenuItem( + modifier = Modifier + .width(190.dp) + .background( + color = MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(16.dp) + ), + text = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = interval.displayName + ) + + if (interval == selectedSyncInterval) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.check), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp) + ) + } + } + }, + onClick = { + onSyncIntervalSelected(interval) + } + ) + } + } + ) + + SingleItem( + onClick = onSyncClick, + supportingText = stringResource(R.string.last_sync), + headline = stringResource(R.string.sync_data), + headlineTextColor = MaterialTheme.colorScheme.onSurface, + supportingTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + leadingIcon = ImageVector.vectorResource(R.drawable.sync_icon), + iconTint = MaterialTheme.colorScheme.onSurfaceVariant, + endIcon = null, + endIconTint = null, + endText = null, + endTextColor = null + ) + + SingleItem( + onClick = onLogoutClick, + headline = stringResource(R.string.logout), + supportingText = null, + headlineTextColor = MaterialTheme.colorScheme.error, + supportingTextColor = null, + leadingIcon = ImageVector.vectorResource(R.drawable.logout), + iconTint = MaterialTheme.colorScheme.error, + endIcon = null, + endIconTint = null, + endText = null, + endTextColor = null + ) +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/settings/components/SettingsItem.kt b/note/presentation/src/main/java/com/example/presentation/settings/components/SettingsItem.kt new file mode 100644 index 0000000..3d9fac8 --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/settings/components/SettingsItem.kt @@ -0,0 +1,148 @@ +package com.example.presentation.settings.components + +import android.widget.Toast +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.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +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.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.example.presentation.NotesViewModel +import com.example.notemark.core.presentation.R +import com.example.presentation.SyncInterval +import com.example.presentation.login.LoginViewModel +import com.example.presentation.login.LogoutState + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +internal fun SettingsItem( + onNavigateUp: () -> Unit = {}, + onNavigateToLanding: () -> Unit = {}, +) { + val context = LocalContext.current + val loginViewModel: LoginViewModel = hiltViewModel() + val notesViewModel: NotesViewModel = hiltViewModel() + var showSyncIntervalDropdown by remember { mutableStateOf(false) } + val logoutState by loginViewModel.logoutState.collectAsStateWithLifecycle() + var selectedSyncInterval by remember { mutableStateOf(SyncInterval.MANUAL_ONLY) } + val refreshToken by notesViewModel.refreshToken.collectAsStateWithLifecycle() + + LaunchedEffect(logoutState) { + when (logoutState) { + is LogoutState.Error -> { + Toast.makeText( + context, + (logoutState as LogoutState.Error).message, + Toast.LENGTH_SHORT + ).show() + loginViewModel.clearState() + } + + is LogoutState.Success -> { + notesViewModel.clear() + onNavigateToLanding() + loginViewModel.clearState() + } + LogoutState.Loading, + LogoutState.Idle -> {} + } + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(R.string.settings).uppercase(), + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 1.0.sp, + fontFamily = FontFamily(Font(R.font.space_grotesk_regular)), + ) + }, + navigationIcon = { + IconButton( + onClick = { onNavigateUp() } + ) { + Icon( + tint = MaterialTheme.colorScheme.onSurfaceVariant, + imageVector = ImageVector.vectorResource(R.drawable.ios_arrow_left), + contentDescription = "Navigate up" + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface + ) + ) + } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surface) + .padding(innerPadding) + ) { + + if (logoutState is LogoutState.Loading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } else { + SettingsContent( + onLogoutClick = { + loginViewModel.logoutUser(refreshToken = refreshToken ?: "") + }, + onSyncClick = { + Toast.makeText( + context, + "This page is not available yet!", + Toast.LENGTH_SHORT + ).show() + }, + onSyncIntervalClick = { + showSyncIntervalDropdown = true + }, + showSyncIntervalDropdown = showSyncIntervalDropdown, + onDismissSyncIntervalDropdown = { + showSyncIntervalDropdown = false + }, + selectedSyncInterval = selectedSyncInterval, + onSyncIntervalSelected = { interval -> + selectedSyncInterval = interval + showSyncIntervalDropdown = false + } + ) + } + } + } +} \ No newline at end of file diff --git a/note/presentation/src/main/java/com/example/presentation/settings/components/SingleItem.kt b/note/presentation/src/main/java/com/example/presentation/settings/components/SingleItem.kt new file mode 100644 index 0000000..6f9e3ab --- /dev/null +++ b/note/presentation/src/main/java/com/example/presentation/settings/components/SingleItem.kt @@ -0,0 +1,131 @@ +package com.example.presentation.settings.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +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.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +@Composable +internal fun SingleItem( + modifier: Modifier = Modifier, + onEndIconAndTextClick: (() -> Unit)? = null, + onClick: (() -> Unit)? = null, + supportingText: String? = null, + supportingTextColor: Color? = null, + headline: String, + headlineTextColor: Color, + leadingIcon: ImageVector? = null, + iconTint: Color? = null, + endIcon: ImageVector? = null, + endIconTint: Color? = null, + endText: String? = null, + endTextColor: Color? = null, + showDropdown: Boolean = false, + onDismissDropdown: (() -> Unit)? = null, + dropdownContent: (@Composable ColumnScope.() -> Unit)? = null, +) { + Row( + modifier = modifier + .fillMaxWidth() + .heightIn(min = 56.dp) + .padding(horizontal = 16.dp, vertical = 8.dp) + .then( + if (onClick != null) { + Modifier.clickable { onClick() } + } else { + Modifier + } + ), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + + leadingIcon?.let { icon -> + Icon( + imageVector = icon, + contentDescription = null, + tint = iconTint ?: MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start + ) { + Text( + text = headline, + style = MaterialTheme.typography.titleSmall.copy( + color = headlineTextColor + ) + ) + + if (!supportingText.isNullOrBlank()) { + Text( + text = supportingText, + style = MaterialTheme.typography.bodySmall.copy( + color = supportingTextColor ?: MaterialTheme.colorScheme.onSurfaceVariant + ) + ) + } + } + + val hasEndContent = !endText.isNullOrBlank() || endIcon != null + if (hasEndContent) { + Box { + Row( + modifier = Modifier.then( + if (onEndIconAndTextClick != null) { + Modifier.clickable { onEndIconAndTextClick() } + } else { + Modifier + } + ), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (!endText.isNullOrBlank()) { + Text( + text = endText, + style = MaterialTheme.typography.bodySmall.copy( + color = endTextColor ?: MaterialTheme.colorScheme.onSurfaceVariant + ) + ) + } + + endIcon?.let { icon -> + Icon( + imageVector = icon, + contentDescription = null, + tint = endIconTint ?: MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + if (dropdownContent != null) { + DropdownMenu( + expanded = showDropdown, + onDismissRequest = onDismissDropdown ?: {} + ) { + dropdownContent() + } + } + } + } + } +} \ No newline at end of file diff --git a/releases/data/build.gradle.kts b/releases/data/build.gradle.kts index 2582fe0..a32890c 100644 --- a/releases/data/build.gradle.kts +++ b/releases/data/build.gradle.kts @@ -1,11 +1,18 @@ plugins { alias(libs.plugins.notemark.android.library) + alias(libs.plugins.ktor.client.convention) + alias(libs.plugins.compose.compiler) + } android { - namespace = "com.example.data" + namespace = "com.example.notemark.releases.data" } dependencies { - implementation(projects.releases.domain) + + with(projects){ + implementation(core.domain) + implementation(releases.domain) + } } \ No newline at end of file diff --git a/releases/data/src/main/java/com/example/data/ReleasesService.kt b/releases/data/src/main/java/com/example/data/ReleasesService.kt new file mode 100644 index 0000000..7b64845 --- /dev/null +++ b/releases/data/src/main/java/com/example/data/ReleasesService.kt @@ -0,0 +1,6 @@ +package com.example.data + +//interface ReleasesService { +// +// suspend fun getLatestUpdate() +//} \ No newline at end of file diff --git a/releases/data/src/main/java/com/example/data/ReleasesServiceImpl.kt b/releases/data/src/main/java/com/example/data/ReleasesServiceImpl.kt new file mode 100644 index 0000000..0db6b4e --- /dev/null +++ b/releases/data/src/main/java/com/example/data/ReleasesServiceImpl.kt @@ -0,0 +1,22 @@ +package com.example.data + +import com.example.domain.HttpRoutes +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.http.cio.Response + +//class ReleasesServiceImpl( +// private val client: HttpClient, +//): ReleasesService { +// +// // override suspend fun getLatestUpdate(): Response { +//// return try { +//// client.get(HttpRoutes.UPDATES) +//// } catch (e: Exception) { +//// Response.Failure(e) +//// } +//// } +// override suspend fun getLatestUpdate() { +// TODO("Not yet implemented") +// } +//} \ No newline at end of file diff --git a/releases/presentation/build.gradle.kts b/releases/presentation/build.gradle.kts index 4bd5b53..af3f4b0 100644 --- a/releases/presentation/build.gradle.kts +++ b/releases/presentation/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.example.presentation" + namespace = "com.example.notemark.releases.presentation" } dependencies { diff --git a/releases/presentation/src/main/java/com/example/presentation/UpdateScreen.kt b/releases/presentation/src/main/java/com/example/presentation/UpdateScreen.kt new file mode 100644 index 0000000..2cfda3f --- /dev/null +++ b/releases/presentation/src/main/java/com/example/presentation/UpdateScreen.kt @@ -0,0 +1,17 @@ +package com.example.presentation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun UpdateScreenRoot(modifier: Modifier = Modifier) { + + UpdateScreen() +} + +@Composable +private fun UpdateScreen( + modifier: Modifier = Modifier +) { + +} \ No newline at end of file