├── app
├── .gitignore
├── release
│ ├── app-release.apk
│ └── output-metadata.json
├── src
│ └── main
│ │ ├── ic_launcher-playstore.png
│ │ ├── java
│ │ └── ru
│ │ │ └── tech
│ │ │ └── cookhelper
│ │ │ ├── domain
│ │ │ ├── utils
│ │ │ │ ├── Domain.kt
│ │ │ │ └── text
│ │ │ │ │ ├── TextValidator.kt
│ │ │ │ │ ├── ChainTextValidator.kt
│ │ │ │ │ ├── validators
│ │ │ │ │ ├── NonEmptyTextValidator.kt
│ │ │ │ │ ├── EmailTextValidator.kt
│ │ │ │ │ ├── HasNumberTextValidator.kt
│ │ │ │ │ └── LengthTextValidator.kt
│ │ │ │ │ └── ValidatorResult.kt
│ │ │ ├── model
│ │ │ │ ├── FormMessage.kt
│ │ │ │ ├── Setting.kt
│ │ │ │ ├── MatchedRecipe.kt
│ │ │ │ ├── Product.kt
│ │ │ │ ├── Message.kt
│ │ │ │ ├── FileData.kt
│ │ │ │ ├── Chat.kt
│ │ │ │ ├── Post.kt
│ │ │ │ ├── Topic.kt
│ │ │ │ ├── Reply.kt
│ │ │ │ ├── User.kt
│ │ │ │ ├── ForumFilters.kt
│ │ │ │ └── Recipe.kt
│ │ │ ├── use_case
│ │ │ │ ├── get_user
│ │ │ │ │ └── GetUserUseCase.kt
│ │ │ │ ├── log_out
│ │ │ │ │ └── LogOutUseCase.kt
│ │ │ │ ├── get_feed
│ │ │ │ │ └── GetFeedUseCase.kt
│ │ │ │ ├── get_chat_list
│ │ │ │ │ └── GetChatListUseCase.kt
│ │ │ │ ├── load_user
│ │ │ │ │ └── LoadUserByIdUseCase.kt
│ │ │ │ ├── GetMatchedRecipesUseCase.kt
│ │ │ │ ├── stop_awaiting_feed
│ │ │ │ │ └── StopAwaitingFeedUseCase.kt
│ │ │ │ ├── stop_awaiting_messages
│ │ │ │ │ └── StopAwaitingMessagesUseCase.kt
│ │ │ │ ├── log_in
│ │ │ │ │ └── LoginUseCase.kt
│ │ │ │ ├── check_code
│ │ │ │ │ └── CheckCodeUseCase.kt
│ │ │ │ ├── get_available_products
│ │ │ │ │ └── GetAvailableProductsUseCase.kt
│ │ │ │ ├── insert_setting
│ │ │ │ │ └── InsertSettingUseCase.kt
│ │ │ │ ├── cache_user
│ │ │ │ │ └── CacheUserUseCase.kt
│ │ │ │ ├── restore_password
│ │ │ │ │ ├── SendRestoreCodeUseCase.kt
│ │ │ │ │ └── ApplyPasswordByCodeUseCase.kt
│ │ │ │ ├── send_message
│ │ │ │ │ └── SendMessagesUseCase.kt
│ │ │ │ ├── observe_user
│ │ │ │ │ └── ObserveUserUseCase.kt
│ │ │ │ ├── request_code
│ │ │ │ │ └── RequestCodeUseCase.kt
│ │ │ │ ├── check_email
│ │ │ │ │ └── CheckEmailForAvailabilityUseCase.kt
│ │ │ │ ├── check_login
│ │ │ │ │ └── CheckLoginForAvailabilityUseCase.kt
│ │ │ │ ├── add_products_to_fridge
│ │ │ │ │ └── AddProductsToFridgeUseCase.kt
│ │ │ │ ├── remove_products_from_fridge
│ │ │ │ │ └── RemoveProductsFromFridgeUseCase.kt
│ │ │ │ ├── get_all_messages
│ │ │ │ │ └── GetAllMessagesUseCase.kt
│ │ │ │ ├── create_topic
│ │ │ │ │ └── CreateTopicUseCase.kt
│ │ │ │ ├── await_new_messages
│ │ │ │ │ └── AwaitNewMessagesUseCase.kt
│ │ │ │ ├── create_post
│ │ │ │ │ └── CreatePostUseCase.kt
│ │ │ │ ├── get_settings_list
│ │ │ │ │ └── GetSettingsListUseCaseFlow.kt
│ │ │ │ ├── registration
│ │ │ │ │ └── RegistrationUseCase.kt
│ │ │ │ └── close_connection
│ │ │ │ │ └── CloseConnectionsUseCase.kt
│ │ │ └── repository
│ │ │ │ ├── SettingsRepository.kt
│ │ │ │ ├── FridgeRepository.kt
│ │ │ │ ├── MessageRepository.kt
│ │ │ │ └── UserRepository.kt
│ │ │ ├── data
│ │ │ ├── remote
│ │ │ │ ├── utils
│ │ │ │ │ ├── Response.kt
│ │ │ │ │ └── Dto.kt
│ │ │ │ ├── web_socket
│ │ │ │ │ ├── Service.kt
│ │ │ │ │ ├── feed
│ │ │ │ │ │ ├── FeedService.kt
│ │ │ │ │ │ └── FeedServiceImpl.kt
│ │ │ │ │ ├── message
│ │ │ │ │ │ ├── MessageService.kt
│ │ │ │ │ │ └── MessageServiceImpl.kt
│ │ │ │ │ ├── user
│ │ │ │ │ │ ├── UserService.kt
│ │ │ │ │ │ └── UserServiceImpl.kt
│ │ │ │ │ └── WebSocketState.kt
│ │ │ │ ├── dto
│ │ │ │ │ ├── ProductDto.kt
│ │ │ │ │ ├── MatchedRecipeDto.kt
│ │ │ │ │ ├── MessageDto.kt
│ │ │ │ │ ├── ChatDto.kt
│ │ │ │ │ ├── PostDto.kt
│ │ │ │ │ ├── TopicDto.kt
│ │ │ │ │ ├── RecipeDto.kt
│ │ │ │ │ └── UserDto.kt
│ │ │ │ └── api
│ │ │ │ │ ├── chat
│ │ │ │ │ └── ChatApi.kt
│ │ │ │ │ ├── ingredients
│ │ │ │ │ └── FridgeApi.kt
│ │ │ │ │ ├── user
│ │ │ │ │ └── UserApi.kt
│ │ │ │ │ └── auth
│ │ │ │ │ └── AuthService.kt
│ │ │ ├── local
│ │ │ │ ├── utils
│ │ │ │ │ └── DatabaseEntity.kt
│ │ │ │ ├── entity
│ │ │ │ │ ├── SettingsEntity.kt
│ │ │ │ │ └── UserEntity.kt
│ │ │ │ ├── dao
│ │ │ │ │ ├── UserDao.kt
│ │ │ │ │ └── SettingsDao.kt
│ │ │ │ └── database
│ │ │ │ │ ├── Database.kt
│ │ │ │ │ └── TypeConverters.kt
│ │ │ ├── utils
│ │ │ │ ├── MoshiParser.kt
│ │ │ │ └── JsonParser.kt
│ │ │ └── repository
│ │ │ │ ├── SettingsRepositoryImpl.kt
│ │ │ │ └── MessageRepositoryImpl.kt
│ │ │ ├── presentation
│ │ │ ├── restore_password
│ │ │ │ └── components
│ │ │ │ │ ├── RestoreState.kt
│ │ │ │ │ └── RestorePasswordState.kt
│ │ │ ├── profile
│ │ │ │ ├── components
│ │ │ │ │ ├── RecipesTabContent.kt
│ │ │ │ │ ├── SelectedTab.kt
│ │ │ │ │ ├── LogoutDialog.kt
│ │ │ │ │ ├── PostActionButton.kt
│ │ │ │ │ ├── AuthorBubble.kt
│ │ │ │ │ └── EditStatusDialog.kt
│ │ │ │ └── viewModel
│ │ │ │ │ └── ProfileViewModel.kt
│ │ │ ├── confirm_email
│ │ │ │ └── components
│ │ │ │ │ └── CodeState.kt
│ │ │ ├── app
│ │ │ │ ├── components
│ │ │ │ │ ├── UserState.kt
│ │ │ │ │ ├── ExitDialog.kt
│ │ │ │ │ ├── MainModalDrawerHeader.kt
│ │ │ │ │ ├── BottomSheetHost.kt
│ │ │ │ │ └── SimpleScaffold.kt
│ │ │ │ └── MainActivity.kt
│ │ │ ├── chat
│ │ │ │ └── components
│ │ │ │ │ ├── ChatState.kt
│ │ │ │ │ └── MessageHeader.kt
│ │ │ ├── login_screen
│ │ │ │ └── components
│ │ │ │ │ └── LoginState.kt
│ │ │ ├── chat_list
│ │ │ │ └── components
│ │ │ │ │ ├── ChatListState.kt
│ │ │ │ │ └── ChatPicture.kt
│ │ │ ├── feed_screen
│ │ │ │ └── components
│ │ │ │ │ └── FeedState.kt
│ │ │ ├── post_creation
│ │ │ │ └── components
│ │ │ │ │ └── PostCreationState.kt
│ │ │ ├── topic_creation
│ │ │ │ └── components
│ │ │ │ │ └── TopicCreationState.kt
│ │ │ ├── registration_screen
│ │ │ │ └── components
│ │ │ │ │ ├── RegistrationState.kt
│ │ │ │ │ ├── CheckEmailState.kt
│ │ │ │ │ └── CheckLoginState.kt
│ │ │ ├── matched_recipes
│ │ │ │ └── components
│ │ │ │ │ └── MatchedRecipeState.kt
│ │ │ ├── ui
│ │ │ │ ├── utils
│ │ │ │ │ ├── provider
│ │ │ │ │ │ ├── LocalSettingsState.kt
│ │ │ │ │ │ ├── LocalSnackbarHostState.kt
│ │ │ │ │ │ ├── LocalToastHostState.kt
│ │ │ │ │ │ ├── LocalBottomSheetController.kt
│ │ │ │ │ │ ├── LocalWindowSizeClass.kt
│ │ │ │ │ │ └── LocalScreenController.kt
│ │ │ │ │ ├── event
│ │ │ │ │ │ ├── ViewModelEvents.kt
│ │ │ │ │ │ ├── ViewModelEventsImpl.kt
│ │ │ │ │ │ ├── Event.kt
│ │ │ │ │ │ └── EventUtils.kt
│ │ │ │ │ ├── android
│ │ │ │ │ │ ├── ContentUtils.kt
│ │ │ │ │ │ ├── ShareUtils.kt
│ │ │ │ │ │ ├── ConfigurationUtils.kt
│ │ │ │ │ │ ├── Logger.kt
│ │ │ │ │ │ ├── SystemBarUtils.kt
│ │ │ │ │ │ └── exception
│ │ │ │ │ │ │ └── GlobalExceptionHandler.kt
│ │ │ │ │ ├── compose
│ │ │ │ │ │ ├── ToastUtils.kt
│ │ │ │ │ │ ├── SnackbarUtils.kt
│ │ │ │ │ │ ├── UIText.kt
│ │ │ │ │ │ ├── ResUtils.kt
│ │ │ │ │ │ ├── ColorUtils.kt
│ │ │ │ │ │ ├── ScrollUtils.kt
│ │ │ │ │ │ ├── TopAppBarUtils.kt
│ │ │ │ │ │ ├── PaddingUtils.kt
│ │ │ │ │ │ ├── StateUtils.kt
│ │ │ │ │ │ └── Modifiers.kt
│ │ │ │ │ └── navigation
│ │ │ │ │ │ └── BottomSheet.kt
│ │ │ │ ├── widgets
│ │ │ │ │ ├── KeepScreenOn.kt
│ │ │ │ │ ├── zooomable
│ │ │ │ │ │ └── ZoomParams.kt
│ │ │ │ │ ├── Spacer.kt
│ │ │ │ │ └── Placeholder.kt
│ │ │ │ └── theme
│ │ │ │ │ ├── Transitions.kt
│ │ │ │ │ └── Typography.kt
│ │ │ ├── fullscreen_image_pager
│ │ │ │ └── FileDataSaver.kt
│ │ │ ├── settings
│ │ │ │ ├── viewModel
│ │ │ │ │ └── SettingsViewModel.kt
│ │ │ │ ├── components
│ │ │ │ │ ├── RotationButton.kt
│ │ │ │ │ └── PickLanguageDialog.kt
│ │ │ │ └── SettingsScreen.kt
│ │ │ ├── m3
│ │ │ │ └── M3Activity.kt
│ │ │ ├── forum_discussion
│ │ │ │ ├── components
│ │ │ │ │ ├── TagGroup.kt
│ │ │ │ │ ├── TagItem.kt
│ │ │ │ │ └── RatingButton.kt
│ │ │ │ └── viewModel
│ │ │ │ │ └── ForumDiscussionViewModel.kt
│ │ │ ├── recipe_post_creation
│ │ │ │ ├── components
│ │ │ │ │ ├── Separator.kt
│ │ │ │ │ ├── FabSize.kt
│ │ │ │ │ └── LeaveUnsavedDataDialog.kt
│ │ │ │ └── viewModel
│ │ │ │ │ └── RecipePostCreationViewModel.kt
│ │ │ ├── authentication
│ │ │ │ └── components
│ │ │ │ │ └── LockScreenOrientaion.kt
│ │ │ ├── fridge_screen
│ │ │ │ └── components
│ │ │ │ │ ├── ProductUtils.kt
│ │ │ │ │ └── ProductItem.kt
│ │ │ ├── recipe_details
│ │ │ │ └── viewModel
│ │ │ │ │ └── RecipeDetailsViewModel.kt
│ │ │ ├── home_screen
│ │ │ │ └── components
│ │ │ │ │ └── BottomNavigationBar.kt
│ │ │ ├── crash_screen
│ │ │ │ └── viewModel
│ │ │ │ │ └── CrashViewModel.kt
│ │ │ ├── forum_screen
│ │ │ │ └── components
│ │ │ │ │ └── SearchBox.kt
│ │ │ ├── all_images
│ │ │ │ └── components
│ │ │ │ │ └── AdaptiveVerticalGrid.kt
│ │ │ └── edit_profile
│ │ │ │ └── components
│ │ │ │ └── EditProfileItem.kt
│ │ │ └── core
│ │ │ ├── application
│ │ │ └── CookHelperApplication.kt
│ │ │ ├── utils
│ │ │ ├── ReflectionUtils.kt
│ │ │ ├── kotlin
│ │ │ │ └── KotlinUtils.kt
│ │ │ ├── ConnectionUtils.kt
│ │ │ └── RetrofitUtils.kt
│ │ │ ├── constants
│ │ │ ├── Constants.kt
│ │ │ └── Status.kt
│ │ │ ├── di
│ │ │ ├── RoomModule.kt
│ │ │ ├── RetrofitModule.kt
│ │ │ ├── NetworkModule.kt
│ │ │ └── RepositoryModule.kt
│ │ │ └── Action.kt
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ └── themes.xml
│ │ ├── values-night
│ │ │ └── colors.xml
│ │ ├── xml
│ │ │ ├── locales_config.xml
│ │ │ ├── backup_rules.xml
│ │ │ └── data_extraction_rules.xml
│ │ └── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ └── AndroidManifest.xml
└── proguard-rules.pro
├── dynamic_theme
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ └── AndroidManifest.xml
├── libs
│ └── material-color-util.jar
├── proguard-rules.pro
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle.kts
└── gradle.properties
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/dynamic_theme/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/dynamic_theme/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/release/app-release.apk
--------------------------------------------------------------------------------
/dynamic_theme/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/Domain.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils
2 |
3 | interface Domain
--------------------------------------------------------------------------------
/dynamic_theme/libs/material-color-util.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/dynamic_theme/libs/material-color-util.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/T8RIN/CookHelper/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #002B33
4 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/utils/Response.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.utils
2 |
3 | data class Response(
4 | val status: Int,
5 | val data: T?
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/restore_password/components/RestoreState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.restore_password.components
2 |
3 | enum class RestoreState { Login, Password }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/utils/Dto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.utils
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | interface Dto {
6 | fun asDomain(): Domain
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/Service.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket
2 |
3 | interface Service {
4 | fun sendMessage(data: String)
5 | fun closeService()
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/FormMessage.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | data class FormMessage(
4 | val text: String,
5 | val replyToId: Int,
6 | val attachments: List
7 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/local/utils/DatabaseEntity.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.local.utils
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | interface DatabaseEntity {
6 | fun asDomain(): Domain
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Setting.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class Setting(
6 | val id: Int,
7 | val option: String
8 | ) : Domain
9 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/locales_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/profile/components/RecipesTabContent.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.profile.components
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | fun RecipesTabContent() {
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/confirm_email/components/CodeState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.confirm_email.components
2 |
3 | data class CodeState(
4 | val isLoading: Boolean = false,
5 | val error: Boolean = false
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/MatchedRecipe.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class MatchedRecipe(
6 | val recipe: Recipe,
7 | val percentString: String
8 | ) : Domain
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/text/TextValidator.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils.text
2 |
3 | interface TextValidator {
4 | fun validate(stringToValidate: String): ValidatorResult
5 | var validatorResult: ValidatorResult
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/application/CookHelperApplication.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.application
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class CookHelperApplication : Application()
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/profile/components/SelectedTab.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.profile.components
2 |
3 | enum class SelectedTab { Posts, Recipes }
4 |
5 | fun Int.toTab(): SelectedTab = SelectedTab.values().first { it.ordinal == this }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 15 18:54:45 MSK 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/app/components/UserState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.app.components
2 |
3 | import ru.tech.cookhelper.domain.model.User
4 |
5 | data class UserState(
6 | val user: User? = null,
7 | val token: String = user?.token ?: ""
8 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Product.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class Product(
6 | val id: Int,
7 | val title: String,
8 | val category: Int,
9 | val mimetype: String
10 | ) : Domain
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/chat/components/ChatState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.chat.components
2 |
3 | data class ChatState(
4 | val isLoading: Boolean = false,
5 | val image: String? = null,
6 | val title: String = "",
7 | val newMessages: Int = 0
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/login_screen/components/LoginState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.login_screen.components
2 |
3 | import ru.tech.cookhelper.domain.model.User
4 |
5 | data class LoginState(
6 | val isLoading: Boolean = false,
7 | val user: User? = null
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/utils/ReflectionUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.utils
2 |
3 | import kotlin.reflect.KClass
4 |
5 | object ReflectionUtils {
6 | inline val KClass.name: String
7 | get() {
8 | return simpleName.toString()
9 | }
10 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/chat_list/components/ChatListState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.chat_list.components
2 |
3 | import ru.tech.cookhelper.domain.model.Chat
4 |
5 | data class ChatListState(
6 | val isLoading: Boolean = false,
7 | val chatList: List = emptyList()
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/feed_screen/components/FeedState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.feed_screen.components
2 |
3 | import ru.tech.cookhelper.domain.model.Recipe
4 |
5 | data class FeedState(
6 | val data: List = emptyList(),
7 | val isLoading: Boolean = false,
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/post_creation/components/PostCreationState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.post_creation.components
2 |
3 | import ru.tech.cookhelper.domain.model.Post
4 |
5 | data class PostCreationState(
6 | val isLoading: Boolean = false,
7 | val post: Post? = null
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/topic_creation/components/TopicCreationState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.topic_creation.components
2 |
3 | import ru.tech.cookhelper.domain.model.Topic
4 |
5 | data class TopicCreationState(
6 | val isLoading: Boolean = false,
7 | val topic: Topic? = null
8 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/registration_screen/components/RegistrationState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.registration_screen.components
2 |
3 | import ru.tech.cookhelper.domain.model.User
4 |
5 | data class RegistrationState(
6 | val isLoading: Boolean = false,
7 | val user: User? = null
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/matched_recipes/components/MatchedRecipeState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.matched_recipes.components
2 |
3 | import ru.tech.cookhelper.domain.model.MatchedRecipe
4 |
5 | data class MatchedRecipeState(
6 | val isLoading: Boolean = false,
7 | val recipes: List = emptyList()
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Message.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class Message(
6 | val id: Long,
7 | val text: String,
8 | val attachments: List,
9 | val replyToId: Long,
10 | val timestamp: Long,
11 | val author: User
12 | ) : Domain
13 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/provider/LocalSettingsState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.provider
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import ru.tech.cookhelper.presentation.settings.components.SettingsState
5 |
6 | val LocalSettingsProvider = compositionLocalOf { error("SettingsState not present") }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/registration_screen/components/CheckEmailState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.registration_screen.components
2 |
3 | import ru.tech.cookhelper.presentation.ui.utils.compose.UIText
4 |
5 | data class CheckEmailState(
6 | val isValid: Boolean = false,
7 | val error: UIText = UIText.Empty(),
8 | val isLoading: Boolean = false
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/registration_screen/components/CheckLoginState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.registration_screen.components
2 |
3 | import ru.tech.cookhelper.presentation.ui.utils.compose.UIText
4 |
5 | data class CheckLoginState(
6 | val isValid: Boolean = false,
7 | val error: UIText = UIText.Empty(),
8 | val isLoading: Boolean = false
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/get_user/GetUserUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.get_user
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class GetUserUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | operator fun invoke() = userRepository.getUser()
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/restore_password/components/RestorePasswordState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.restore_password.components
2 |
3 | import ru.tech.cookhelper.domain.model.User
4 |
5 | data class RestorePasswordState(
6 | val isLoading: Boolean = false,
7 | val state: RestoreState = RestoreState.Login,
8 | val user: User? = null
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/FileData.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class FileData(
6 | val link: String,
7 | val id: String
8 | ) : Domain
9 |
10 | //data class FileData(
11 | // val id: Long,
12 | // val name: String,
13 | // val link: String,
14 | // val type: String
15 | //)
16 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/widgets/KeepScreenOn.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.widgets
2 |
3 | import android.view.View
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.viewinterop.AndroidView
6 |
7 | @Composable
8 | fun KeepScreenOn(flag: Boolean = true) {
9 | if (flag) AndroidView({ View(it).apply { keepScreenOn = true } })
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/log_out/LogOutUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.log_out
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class LogoutUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | suspend operator fun invoke() = userRepository.logOut()
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/get_feed/GetFeedUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.get_feed
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class GetFeedUseCase @Inject constructor(
7 | private val repository: UserRepository
8 | ) {
9 | operator fun invoke(token: String) = repository.getFeed(token)
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/widgets/zooomable/ZoomParams.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.widgets.zooomable
2 |
3 | import androidx.compose.ui.geometry.Offset
4 |
5 | data class ZoomParams(
6 | val zoomEnabled: Boolean = false,
7 | val hideBarsOnTap: Boolean = false,
8 | val minZoomScale: Float = 1f,
9 | val maxZoomScale: Float = 4f,
10 | val onTap: (Offset) -> Unit = {}
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/get_chat_list/GetChatListUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.get_chat_list
2 |
3 | import ru.tech.cookhelper.domain.repository.MessageRepository
4 | import javax.inject.Inject
5 |
6 | class GetChatListUseCase @Inject constructor(
7 | private val repository: MessageRepository
8 | ) {
9 | operator fun invoke(token: String) = repository.getChatList(token)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/load_user/LoadUserByIdUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.load_user
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class LoadUserByIdUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | suspend operator fun invoke(id: String) = userRepository.loadUserById(id)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/repository/SettingsRepository.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.repository
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.domain.model.Setting
5 |
6 | interface SettingsRepository {
7 |
8 | fun getSettingsFlow(): Flow>
9 |
10 | suspend fun getSettings(): List
11 |
12 | suspend fun insertSetting(id: Int, option: String)
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/GetMatchedRecipesUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case
2 |
3 | import ru.tech.cookhelper.domain.repository.FridgeRepository
4 | import javax.inject.Inject
5 |
6 | class GetMatchedRecipesUseCase @Inject constructor(
7 | private val fridgeRepository: FridgeRepository
8 | ) {
9 | suspend operator fun invoke(token: String) = fridgeRepository.getMatchedRecipes(token)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/stop_awaiting_feed/StopAwaitingFeedUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.stop_awaiting_feed
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class StopAwaitingFeedUseCase @Inject constructor(
7 | private val repository: UserRepository
8 | ) {
9 | operator fun invoke() = repository.stopAwaitingFeed()
10 | }
11 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "CookHelper"
16 | include(":app")
17 | include(":dynamic_theme")
18 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Chat.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class Chat(
6 | val id: Long,
7 | val images: List?,
8 | val title: String,
9 | val lastMessage: Message?,
10 | val newMessagesCount: Int,
11 | val members: List,
12 | val messages: List,
13 | val creationTimestamp: Long
14 | ) : Domain
15 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/stop_awaiting_messages/StopAwaitingMessagesUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.stop_awaiting_messages
2 |
3 | import ru.tech.cookhelper.domain.repository.MessageRepository
4 | import javax.inject.Inject
5 |
6 | class StopAwaitingMessagesUseCase @Inject constructor(
7 | private val repository: MessageRepository
8 | ) {
9 | operator fun invoke() = repository.stopAwaitingMessages()
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/log_in/LoginUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.log_in
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class LoginUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | operator fun invoke(
10 | login: String,
11 | password: String
12 | ) = userRepository.loginWith(login, password)
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/check_code/CheckCodeUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.check_code
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class CheckCodeUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | operator fun invoke(
10 | code: String,
11 | token: String
12 | ) = userRepository.checkCode(code, token)
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/get_available_products/GetAvailableProductsUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.get_available_products
2 |
3 | import ru.tech.cookhelper.domain.repository.FridgeRepository
4 | import javax.inject.Inject
5 |
6 | class GetAvailableProductsUseCase @Inject constructor(
7 | private val fridgeRepository: FridgeRepository
8 | ) {
9 | suspend operator fun invoke() = fridgeRepository.getAvailableProducts()
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/insert_setting/InsertSettingUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.insert_setting
2 |
3 | import ru.tech.cookhelper.domain.repository.SettingsRepository
4 | import javax.inject.Inject
5 |
6 | class InsertSettingUseCase @Inject constructor(
7 | private val repository: SettingsRepository
8 | ) {
9 |
10 | suspend operator fun invoke(id: Int, option: String) = repository.insertSetting(id, option)
11 |
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/cache_user/CacheUserUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.cache_user
2 |
3 | import ru.tech.cookhelper.domain.model.User
4 | import ru.tech.cookhelper.domain.repository.UserRepository
5 | import javax.inject.Inject
6 |
7 | class CacheUserUseCase @Inject constructor(
8 | private val userRepository: UserRepository
9 | ) {
10 | suspend operator fun invoke(user: User) = userRepository.cacheUser(user)
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/feed/FeedService.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket.feed
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.data.remote.dto.RecipeDto
5 | import ru.tech.cookhelper.data.remote.web_socket.Service
6 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketState
7 |
8 | interface FeedService : Service {
9 | operator fun invoke(token: String): Flow>>
10 | }
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "ru.tech.cookhelper",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 6,
15 | "versionName": "0.1.23-alpha",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File"
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/restore_password/SendRestoreCodeUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.restore_password
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class SendRestoreCodeUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | suspend operator fun invoke(
10 | login: String
11 | ) = userRepository.requestPasswordRestoreCode(login)
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Post.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class Post(
6 | val id: Long,
7 | val author: User,
8 | val timestamp: Long,
9 | val label: String,
10 | val text: String,
11 | val likes: List,
12 | val comments: List,
13 | val reposts: List,
14 | val attachments: List,
15 | val images: List
16 | ) : Domain
17 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/send_message/SendMessagesUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.send_message
2 |
3 | import ru.tech.cookhelper.domain.model.FormMessage
4 | import ru.tech.cookhelper.domain.repository.MessageRepository
5 | import javax.inject.Inject
6 |
7 | class SendMessagesUseCase @Inject constructor(
8 | private val repository: MessageRepository
9 | ) {
10 | operator fun invoke(formMessage: FormMessage) = repository.sendMessage(formMessage)
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/ProductDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.Product
5 |
6 | data class ProductDto(
7 | val id: Int,
8 | val title: String,
9 | val category: Int,
10 | val mimetype: String
11 | ) : Dto {
12 | override fun asDomain(): Product = Product(
13 | id = id, title = title, category = category, mimetype = mimetype
14 | )
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/message/MessageService.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket.message
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.data.remote.dto.MessageDto
5 | import ru.tech.cookhelper.data.remote.web_socket.Service
6 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketState
7 |
8 | interface MessageService : Service {
9 | operator fun invoke(chatId: Long, token: String): Flow>
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/local/entity/SettingsEntity.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.local.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import ru.tech.cookhelper.data.local.utils.DatabaseEntity
6 | import ru.tech.cookhelper.domain.model.Setting
7 |
8 | @Entity
9 | data class SettingsEntity(
10 | @PrimaryKey val id: Int,
11 | val option: String
12 | ) : DatabaseEntity {
13 | override fun asDomain(): Setting = Setting(id = id, option = option)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/utils/MoshiParser.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.utils
2 |
3 | import com.squareup.moshi.Moshi
4 | import java.lang.reflect.Type
5 |
6 | class MoshiParser(private val moshi: Moshi) : JsonParser {
7 |
8 | override fun toJson(obj: T, type: Type): String? {
9 | return moshi.adapter(type).toJson(obj)
10 | }
11 |
12 | override fun fromJson(json: String, type: Type): T? {
13 | return moshi.adapter(type).fromJson(json)
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/observe_user/ObserveUserUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.observe_user
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class ObserveUserUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | operator fun invoke(
10 | id: Long,
11 | token: String
12 | ) = userRepository.observeUser(
13 | id = id,
14 | token = token
15 | )
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/request_code/RequestCodeUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.request_code
2 |
3 | import ru.tech.cookhelper.domain.model.User
4 | import ru.tech.cookhelper.domain.repository.UserRepository
5 | import javax.inject.Inject
6 |
7 | class RequestCodeUseCase @Inject constructor(
8 | private val userRepository: UserRepository
9 | ) {
10 | suspend operator fun invoke(
11 | token: String
12 | ): Result = userRepository.requestCode(token)
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/MatchedRecipeDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.MatchedRecipe
5 | import ru.tech.cookhelper.domain.model.Recipe
6 |
7 | data class MatchedRecipeDto(
8 | val recipe: Recipe,
9 | val percentString: String
10 | ) : Dto {
11 | override fun asDomain(): MatchedRecipe = MatchedRecipe(
12 | recipe = recipe,
13 | percentString = percentString
14 | )
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Topic.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class Topic(
6 | val id: Long,
7 | val author: User,
8 | val title: String,
9 | val text: String,
10 | val replies: List,
11 | val attachments: List,
12 | val tags: List,
13 | val timestamp: Long,
14 | val closed: Boolean,
15 | val ratingPositive: List,
16 | val ratingNegative: List
17 | ) : Domain
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/event/ViewModelEvents.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.event
2 |
3 | import androidx.compose.runtime.Composable
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface ViewModelEvents {
7 | val eventFlow: Flow
8 | fun sendEvent(event: T): Boolean
9 | }
10 |
11 | @Composable
12 | inline fun ViewModelEvents.collectEvents(
13 | noinline eventCollector: suspend (event: T) -> Unit
14 | ) = eventFlow.collectWithLifecycle(action = eventCollector)
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/user/UserService.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket.user
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.data.remote.dto.UserDto
5 | import ru.tech.cookhelper.data.remote.utils.Response
6 | import ru.tech.cookhelper.data.remote.web_socket.Service
7 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketState
8 |
9 | interface UserService : Service {
10 | operator fun invoke(id: Long, token: String): Flow>>
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/android/ContentUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.android
2 |
3 | import androidx.activity.compose.ManagedActivityResultLauncher
4 | import androidx.activity.result.PickVisualMediaRequest
5 | import androidx.activity.result.contract.ActivityResultContracts
6 |
7 | object ContentUtils {
8 |
9 | fun ManagedActivityResultLauncher.pickImage() =
10 | launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
11 |
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/check_email/CheckEmailForAvailabilityUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.check_email
2 |
3 | import ru.tech.cookhelper.core.Action
4 | import ru.tech.cookhelper.domain.repository.UserRepository
5 | import javax.inject.Inject
6 |
7 | class CheckEmailForAvailabilityUseCase @Inject constructor(
8 | private val userRepository: UserRepository
9 | ) {
10 | suspend operator fun invoke(
11 | email: String
12 | ): Action = userRepository.checkEmailForAvailability(email)
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/check_login/CheckLoginForAvailabilityUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.check_login
2 |
3 | import ru.tech.cookhelper.core.Action
4 | import ru.tech.cookhelper.domain.repository.UserRepository
5 | import javax.inject.Inject
6 |
7 | class CheckLoginForAvailabilityUseCase @Inject constructor(
8 | private val userRepository: UserRepository
9 | ) {
10 | suspend operator fun invoke(
11 | login: String
12 | ): Action = userRepository.checkLoginForAvailability(login)
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/restore_password/ApplyPasswordByCodeUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.restore_password
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import javax.inject.Inject
5 |
6 | class ApplyPasswordByCodeUseCase @Inject constructor(
7 | private val userRepository: UserRepository
8 | ) {
9 | operator fun invoke(
10 | login: String,
11 | code: String,
12 | newPassword: String
13 | ) = userRepository.restorePasswordBy(login, code, newPassword)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/event/ViewModelEventsImpl.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.event
2 |
3 | import kotlinx.coroutines.channels.Channel
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.receiveAsFlow
6 |
7 | class ViewModelEventsImpl : ViewModelEvents {
8 | private val eventChannel: Channel = Channel(Channel.BUFFERED)
9 | override val eventFlow: Flow = eventChannel.receiveAsFlow()
10 | override fun sendEvent(event: T) = eventChannel.trySend(event).isSuccess
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/provider/LocalSnackbarHostState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.provider
2 |
3 | import androidx.compose.material3.SnackbarHostState
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.compositionLocalOf
6 | import androidx.compose.runtime.remember
7 |
8 | val LocalSnackbarHost =
9 | compositionLocalOf { error("SnackbarHostState not present") }
10 |
11 | @Composable
12 | fun rememberSnackbarHostState() = remember { SnackbarHostState() }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/provider/LocalToastHostState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.provider
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.compositionLocalOf
5 | import androidx.compose.runtime.remember
6 | import ru.tech.cookhelper.presentation.app.components.ToastHostState
7 |
8 | val LocalToastHostState =
9 | compositionLocalOf { error("ToastHostState not present") }
10 |
11 | @Composable
12 | fun rememberToastHostState() = remember { ToastHostState() }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/WebSocketState.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket
2 |
3 | import okhttp3.Response
4 |
5 | sealed class WebSocketState {
6 | class Message(val obj: T?) : WebSocketState()
7 | class Error(val t: Throwable) : WebSocketState()
8 | class Opening : WebSocketState()
9 | class Opened(val response: Response) : WebSocketState()
10 | class Restarting : WebSocketState()
11 | class Closing : WebSocketState()
12 | class Closed : WebSocketState()
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/utils/JsonParser.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.utils
2 |
3 | import java.lang.reflect.Type
4 |
5 | interface JsonParser {
6 |
7 | /**
8 | * [type] is type of [obj]: [T], which is converted to json
9 | *
10 | * @return Json from given object
11 | */
12 | fun toJson(obj: T, type: Type): String?
13 |
14 | /**
15 | * [type] is type of [T], which is will be parsed from json
16 | *
17 | * @return Object from given json
18 | */
19 | fun fromJson(json: String, type: Type): T?
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/theme/Transitions.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.theme
2 |
3 | import androidx.compose.animation.*
4 | import androidx.compose.animation.core.tween
5 | import dev.olshevski.navigation.reimagined.NavTransitionSpec
6 |
7 | @OptIn(ExperimentalAnimationApi::class)
8 | val ScaleCrossfadeTransitionSpec = NavTransitionSpec { _, _, _ ->
9 | (fadeIn(tween(200)) + scaleIn(initialScale = 0.9f, animationSpec = tween(200)))
10 | .with(fadeOut(tween(200)) + scaleOut(targetScale = 0.9f, animationSpec = tween(200)))
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/add_products_to_fridge/AddProductsToFridgeUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.add_products_to_fridge
2 |
3 | import ru.tech.cookhelper.domain.model.Product
4 | import ru.tech.cookhelper.domain.repository.FridgeRepository
5 | import javax.inject.Inject
6 |
7 | class AddProductsToFridgeUseCase @Inject constructor(
8 | private val fridgeRepository: FridgeRepository
9 | ) {
10 | suspend operator fun invoke(
11 | token: String,
12 | fridge: List
13 | ) = fridgeRepository.addProductsToFridge(token, fridge)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/remove_products_from_fridge/RemoveProductsFromFridgeUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.remove_products_from_fridge
2 |
3 | import ru.tech.cookhelper.domain.model.Product
4 | import ru.tech.cookhelper.domain.repository.FridgeRepository
5 | import javax.inject.Inject
6 |
7 | class RemoveProductsFromFridgeUseCase @Inject constructor(
8 | private val fridgeRepository: FridgeRepository
9 | ) {
10 | suspend operator fun invoke(
11 | token: String,
12 | fridge: List
13 | ) = fridgeRepository.removeProductsFromFridge(token, fridge)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/api/chat/ChatApi.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.api.chat
2 |
3 | import retrofit2.http.GET
4 | import retrofit2.http.Query
5 | import ru.tech.cookhelper.data.remote.dto.ChatDto
6 | import ru.tech.cookhelper.data.remote.utils.Response
7 |
8 | interface ChatApi {
9 |
10 | @GET("api/chat/get/by-id/")
11 | suspend fun getChat(
12 | @Query("id") chatId: Long,
13 | @Query("token") token: String
14 | ): Response
15 |
16 | @GET("api/chat/get")
17 | suspend fun getChatList(
18 | @Query("token") token: String
19 | ): String
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/get_all_messages/GetAllMessagesUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.get_all_messages
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.core.Action
5 | import ru.tech.cookhelper.domain.model.Chat
6 | import ru.tech.cookhelper.domain.repository.MessageRepository
7 | import javax.inject.Inject
8 |
9 | class GetChatUseCase @Inject constructor(
10 | private val repository: MessageRepository
11 | ) {
12 | operator fun invoke(
13 | chatId: Long,
14 | token: String
15 | ): Flow> = repository.getChat(chatId, token)
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/create_topic/CreateTopicUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.create_topic
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import java.io.File
5 | import javax.inject.Inject
6 |
7 | class CreateTopicUseCase @Inject constructor(
8 | private val userRepository: UserRepository
9 | ) {
10 | operator fun invoke(
11 | token: String,
12 | title: String,
13 | text: String,
14 | attachments: List>,
15 | tags: List
16 | ) = userRepository.createTopic(token, title, text, attachments, tags)
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/local/dao/UserDao.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.local.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import kotlinx.coroutines.flow.Flow
8 | import ru.tech.cookhelper.data.local.entity.UserEntity
9 |
10 | @Dao
11 | interface UserDao {
12 |
13 | @Query("SELECT * FROM userentity")
14 | fun getUser(): Flow
15 |
16 | @Insert(onConflict = OnConflictStrategy.REPLACE)
17 | suspend fun cacheUser(user: UserEntity)
18 |
19 | @Query("DELETE FROM userentity")
20 | suspend fun clearUser()
21 |
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/await_new_messages/AwaitNewMessagesUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.await_new_messages
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.core.Action
5 | import ru.tech.cookhelper.domain.model.Message
6 | import ru.tech.cookhelper.domain.repository.MessageRepository
7 | import javax.inject.Inject
8 |
9 | class AwaitNewMessagesUseCase @Inject constructor(
10 | private val repository: MessageRepository
11 | ) {
12 | operator fun invoke(
13 | chatId: Long,
14 | token: String
15 | ): Flow> = repository.awaitNewMessages(chatId, token)
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/text/ChainTextValidator.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils.text
2 |
3 | class ChainTextValidator(
4 | private vararg val validators: TextValidator
5 | ) : TextValidator {
6 | override var validatorResult: ValidatorResult = ValidatorResult.NoResult()
7 | override fun validate(stringToValidate: String): ValidatorResult {
8 | validators.forEach { validator ->
9 | validatorResult = validator.validate(stringToValidate)
10 | if (validatorResult is ValidatorResult.Failure) return validatorResult
11 | }
12 | return validatorResult
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/widgets/Spacer.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.widgets
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 |
7 | @Composable
8 | fun NavigationBarsSpacer() {
9 | Spacer(Modifier.padding(WindowInsets.navigationBars.asPaddingValues()))
10 | }
11 |
12 | @Composable
13 | fun StatusBarsSpacer() {
14 | Spacer(Modifier.padding(WindowInsets.statusBars.asPaddingValues()))
15 | }
16 |
17 | @Composable
18 | fun SystemBarsSpacer() {
19 | Spacer(Modifier.padding(WindowInsets.systemBars.asPaddingValues()))
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/repository/FridgeRepository.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.repository
2 |
3 | import ru.tech.cookhelper.core.Action
4 | import ru.tech.cookhelper.domain.model.MatchedRecipe
5 | import ru.tech.cookhelper.domain.model.Product
6 | import ru.tech.cookhelper.domain.model.User
7 |
8 | interface FridgeRepository {
9 | suspend fun getAvailableProducts(): Action>
10 | suspend fun addProductsToFridge(token: String, fridge: List): Action
11 | suspend fun getMatchedRecipes(token: String): Action>
12 | suspend fun removeProductsFromFridge(token: String, fridge: List): Action
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/android/ShareUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.android
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import ru.tech.cookhelper.R
6 |
7 | object ShareUtils {
8 |
9 | fun Context.shareWith(value: String?) {
10 | val intent = Intent().apply {
11 | action = Intent.ACTION_SEND
12 | putExtra(Intent.EXTRA_TEXT, value)
13 | type = "text/plain"
14 | }
15 | startActivity(
16 | Intent.createChooser(
17 | intent,
18 | getString(R.string.share)
19 | )
20 | )
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/utils/kotlin/KotlinUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.utils.kotlin
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.withContext
5 |
6 | inline fun T.applyCatching(
7 | block: T.() -> Unit
8 | ): T = apply {
9 | runCatching { block() }
10 | }
11 |
12 | inline fun Result.getOrExceptionAndNull(action: (Throwable) -> Unit): T? = try {
13 | getOrThrow()
14 | } catch (t: Throwable) {
15 | action(t)
16 | null
17 | }
18 |
19 | suspend fun runIo(
20 | function: suspend () -> T
21 | ): T = withContext(Dispatchers.IO) { function() }
22 |
23 | fun String.cptlize(): String = replaceFirstChar { it.titlecase() }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/repository/MessageRepository.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.repository
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.core.Action
5 | import ru.tech.cookhelper.domain.model.Chat
6 | import ru.tech.cookhelper.domain.model.FormMessage
7 | import ru.tech.cookhelper.domain.model.Message
8 |
9 | interface MessageRepository {
10 |
11 | fun getChat(chatId: Long, token: String): Flow>
12 |
13 | fun awaitNewMessages(chatId: Long, token: String): Flow>
14 |
15 | fun sendMessage(message: FormMessage)
16 |
17 | fun stopAwaitingMessages()
18 |
19 | fun getChatList(token: String): Flow>>
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/local/dao/SettingsDao.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.local.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import kotlinx.coroutines.flow.Flow
8 | import ru.tech.cookhelper.data.local.entity.SettingsEntity
9 |
10 | @Dao
11 | interface SettingsDao {
12 |
13 | @Query("SELECT * FROM settingsentity")
14 | fun getSettingsFlow(): Flow>
15 |
16 | @Query("SELECT * FROM settingsentity")
17 | suspend fun getSettings(): List
18 |
19 | @Insert(onConflict = OnConflictStrategy.REPLACE)
20 | suspend fun insertSetting(setting: SettingsEntity)
21 |
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/create_post/CreatePostUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.create_post
2 |
3 | import ru.tech.cookhelper.domain.repository.UserRepository
4 | import java.io.File
5 | import javax.inject.Inject
6 |
7 | class CreatePostUseCase @Inject constructor(
8 | private val userRepository: UserRepository
9 | ) {
10 | operator fun invoke(
11 | token: String,
12 | label: String,
13 | content: String,
14 | imageFile: File?,
15 | type: String
16 | ) = userRepository.createPost(
17 | token = token,
18 | label = label,
19 | content = content,
20 | imageFile = imageFile,
21 | type = type
22 | )
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/compose/ToastUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.compose
2 |
3 | import androidx.compose.ui.graphics.vector.ImageVector
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.launch
7 | import ru.tech.cookhelper.presentation.app.components.ToastDuration
8 | import ru.tech.cookhelper.presentation.app.components.ToastHostState
9 |
10 | fun ToastHostState.show(
11 | icon: ImageVector? = null,
12 | message: String,
13 | duration: ToastDuration = ToastDuration.Short,
14 | scope: CoroutineScope = CoroutineScope(Dispatchers.Main.immediate)
15 | ) = scope.launch { showToast(message, icon, duration) }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/text/validators/NonEmptyTextValidator.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils.text.validators
2 |
3 | import ru.tech.cookhelper.domain.utils.text.TextValidator
4 | import ru.tech.cookhelper.domain.utils.text.ValidatorResult
5 |
6 | class NonEmptyTextValidator(
7 | private val message: S
8 | ) : TextValidator {
9 | override var validatorResult: ValidatorResult = ValidatorResult.NoResult()
10 | override fun validate(stringToValidate: String): ValidatorResult {
11 | validatorResult = if (stringToValidate.trim().isEmpty())
12 | ValidatorResult.Failure(message)
13 | else ValidatorResult.Success()
14 |
15 | return validatorResult
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/get_settings_list/GetSettingsListUseCaseFlow.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.get_settings_list
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.domain.model.Setting
5 | import ru.tech.cookhelper.domain.repository.SettingsRepository
6 | import javax.inject.Inject
7 |
8 | class GetSettingsListUseCaseFlow @Inject constructor(
9 | private val repository: SettingsRepository
10 | ) {
11 | operator fun invoke(): Flow> = repository.getSettingsFlow()
12 | }
13 |
14 | class GetSettingsListUseCase @Inject constructor(
15 | private val repository: SettingsRepository
16 | ) {
17 | suspend operator fun invoke(): List = repository.getSettings()
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/registration/RegistrationUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.registration
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.core.Action
5 | import ru.tech.cookhelper.domain.model.User
6 | import ru.tech.cookhelper.domain.repository.UserRepository
7 | import javax.inject.Inject
8 |
9 | class RegistrationUseCase @Inject constructor(
10 | private val userRepository: UserRepository
11 | ) {
12 | operator fun invoke(
13 | name: String,
14 | surname: String,
15 | nickname: String,
16 | email: String,
17 | password: String
18 | ): Flow> = userRepository.registerWith(name, surname, nickname, email, password)
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/fullscreen_image_pager/FileDataSaver.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.fullscreen_image_pager
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.saveable.Saver
6 | import ru.tech.cookhelper.domain.model.FileData
7 |
8 | val FileDataSaver: Saver>, String> = Saver(
9 | save = {
10 | it.value.joinToString("*_*_*_*") { "${it.id}!_*_*_!${it.link}" }
11 | },
12 | restore = {
13 | mutableStateOf(
14 | it.split("*_*_*_*").map {
15 | val t = it.split("!_*_*_!")
16 | FileData(t[1], t[0])
17 | }
18 | )
19 | }
20 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/settings/viewModel/SettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.settings.viewModel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import dagger.hilt.android.lifecycle.HiltViewModel
6 | import kotlinx.coroutines.launch
7 | import ru.tech.cookhelper.domain.use_case.insert_setting.InsertSettingUseCase
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class SettingsViewModel @Inject constructor(
12 | private val insertSettingUseCase: InsertSettingUseCase
13 | ) : ViewModel() {
14 | fun insertSetting(id: Int, option: Any?) {
15 | viewModelScope.launch {
16 | insertSettingUseCase(id, option.toString())
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/m3/M3Activity.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.m3
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.core.view.WindowCompat
6 | import ru.tech.cookhelper.presentation.crash_screen.CrashActivity
7 | import ru.tech.cookhelper.presentation.ui.utils.android.exception.GlobalExceptionHandler
8 |
9 | abstract class M3Activity : AppCompatActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | actionBar?.hide()
14 | WindowCompat.setDecorFitsSystemWindows(window, false)
15 | GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/use_case/close_connection/CloseConnectionsUseCase.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.use_case.close_connection
2 |
3 | import ru.tech.cookhelper.data.remote.web_socket.feed.FeedService
4 | import ru.tech.cookhelper.data.remote.web_socket.message.MessageService
5 | import ru.tech.cookhelper.data.remote.web_socket.user.UserService
6 | import javax.inject.Inject
7 |
8 | class CloseConnectionsUseCase @Inject constructor(
9 | private val messageService: MessageService,
10 | private val userService: UserService,
11 | private val feedService: FeedService
12 | ) {
13 | operator fun invoke() {
14 | listOf(
15 | messageService, userService, feedService
16 | ).forEach { it.closeService() }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.app
2 |
3 | import android.os.Bundle
4 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
5 | import dagger.hilt.android.AndroidEntryPoint
6 | import ru.tech.cookhelper.presentation.app.components.CookHelperApp
7 | import ru.tech.cookhelper.presentation.m3.M3Activity
8 | import ru.tech.cookhelper.presentation.ui.utils.provider.setContentWithWindowSizeClass
9 |
10 | @AndroidEntryPoint
11 | class MainActivity : M3Activity() {
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | installSplashScreen()
15 | super.onCreate(savedInstanceState)
16 | setContentWithWindowSizeClass { CookHelperApp() }
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/text/ValidatorResult.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils.text
2 |
3 | sealed class ValidatorResult {
4 | class Failure(val message: S) : ValidatorResult()
5 | class NoResult : ValidatorResult()
6 | class Success : ValidatorResult()
7 | }
8 |
9 | inline fun ValidatorResult.onSuccess(
10 | action: () -> Unit
11 | ) = apply {
12 | if (this is ValidatorResult.Success) action()
13 | }
14 |
15 | inline fun ValidatorResult.onFailure(
16 | action: (S) -> Unit
17 | ) = apply {
18 | if (this is ValidatorResult.Failure) action(this.message)
19 | }
20 |
21 | inline fun ValidatorResult.isSuccess(
22 | action: (Boolean) -> Unit
23 | ) = apply {
24 | action(this is ValidatorResult.Success)
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/MessageDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.FileData
5 | import ru.tech.cookhelper.domain.model.Message
6 | import ru.tech.cookhelper.domain.model.User
7 |
8 | data class MessageDto(
9 | val id: Long,
10 | val text: String,
11 | val attachments: List,
12 | val replyToId: Long,
13 | val views: List,
14 | val timestamp: Long,
15 | val author: User
16 | ) : Dto {
17 | override fun asDomain(): Message = Message(
18 | id = id,
19 | text = text,
20 | attachments = attachments,
21 | replyToId = replyToId,
22 | timestamp = timestamp,
23 | author = author
24 | )
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/forum_discussion/components/TagGroup.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.forum_discussion.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.unit.dp
6 | import com.google.accompanist.flowlayout.FlowRow
7 |
8 | @Composable
9 | fun TagGroup(
10 | modifier: Modifier = Modifier,
11 | chips: List,
12 | onChipClick: (String) -> Unit
13 | ) {
14 | FlowRow(
15 | mainAxisSpacing = 8.dp,
16 | crossAxisSpacing = 8.dp,
17 | modifier = modifier
18 | ) {
19 | chips.forEach { chip ->
20 | TagItem(
21 | text = chip,
22 | onClick = { onChipClick(chip) }
23 | )
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/recipe_post_creation/components/Separator.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.recipe_post_creation.components
2 |
3 | import androidx.compose.material3.Divider
4 | import androidx.compose.material3.DividerDefaults
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.unit.Dp
10 |
11 | @Composable
12 | fun Separator(
13 | modifier: Modifier = Modifier,
14 | color: Color = MaterialTheme.colorScheme.outlineVariant,
15 | thickness: Dp = DividerDefaults.Thickness
16 | ) {
17 | Divider(
18 | modifier = modifier,
19 | color = color,
20 | thickness = thickness
21 | )
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Reply.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class Reply(
6 | val author: User,
7 | val timestamp: Long,
8 | val attachments: List,
9 | val text: String,
10 | val id: Long,
11 | val replyToId: Long,
12 | val ratingPositive: List,
13 | val ratingNegative: List,
14 | val replies: List
15 | ) : Domain
16 |
17 | fun Reply.userRate(author: User): Int = when {
18 | ratingNegative.contains(author.id) -> -1
19 | ratingPositive.contains(author.id) -> 1
20 | else -> 0
21 | }
22 |
23 | fun Reply.getRating(): String {
24 | val rating = ratingPositive.size - ratingNegative.size
25 | return if (rating > 0) "+$rating"
26 | else "$rating"
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/text/validators/EmailTextValidator.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils.text.validators
2 |
3 | import ru.tech.cookhelper.domain.utils.text.TextValidator
4 | import ru.tech.cookhelper.domain.utils.text.ValidatorResult
5 | import java.util.regex.Pattern
6 |
7 | class EmailTextValidator(
8 | private val message: S,
9 | private val pattern: Pattern
10 | ) : TextValidator {
11 | override var validatorResult: ValidatorResult = ValidatorResult.NoResult()
12 | override fun validate(stringToValidate: String): ValidatorResult {
13 | validatorResult = when {
14 | pattern.matcher(stringToValidate).matches() -> ValidatorResult.Success()
15 | else -> ValidatorResult.Failure(message)
16 | }
17 | return validatorResult
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/local/database/Database.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.local.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import ru.tech.cookhelper.data.local.dao.SettingsDao
6 | import ru.tech.cookhelper.data.local.dao.UserDao
7 | import ru.tech.cookhelper.data.local.entity.SettingsEntity
8 | import ru.tech.cookhelper.data.local.entity.UserEntity
9 | import androidx.room.TypeConverters as RoomTypeConverters
10 |
11 | @Database(
12 | entities = [SettingsEntity::class, UserEntity::class],
13 | exportSchema = false, version = 1,
14 | // autoMigrations = [AutoMigration(from = 1, to = 2)]
15 | )
16 | @RoomTypeConverters(TypeConverters::class)
17 | abstract class Database : RoomDatabase() {
18 | abstract val settingsDao: SettingsDao
19 | abstract val userDao: UserDao
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/text/validators/HasNumberTextValidator.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils.text.validators
2 |
3 | import ru.tech.cookhelper.domain.utils.text.TextValidator
4 | import ru.tech.cookhelper.domain.utils.text.ValidatorResult
5 |
6 | class HasNumberTextValidator(
7 | private val message: S,
8 | private val countOfNumbers: Int = 1
9 | ) : TextValidator {
10 | override var validatorResult: ValidatorResult = ValidatorResult.NoResult()
11 | override fun validate(stringToValidate: String): ValidatorResult {
12 | val count = stringToValidate.count { it.isDigit() }
13 | validatorResult = if (count >= countOfNumbers) {
14 | ValidatorResult.Success()
15 | } else ValidatorResult.Failure(message)
16 |
17 | return validatorResult
18 | }
19 | }
--------------------------------------------------------------------------------
/dynamic_theme/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/authentication/components/LockScreenOrientaion.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.authentication.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisposableEffect
5 | import androidx.compose.ui.platform.LocalContext
6 | import ru.tech.cookhelper.presentation.ui.utils.android.ContextUtils.findActivity
7 |
8 | @Composable
9 | fun LockScreenOrientation(orientation: Int) {
10 | val context = LocalContext.current
11 | DisposableEffect(Unit) {
12 | val activity = context.findActivity() ?: return@DisposableEffect onDispose {}
13 | val originalOrientation = activity.requestedOrientation
14 | activity.requestedOrientation = orientation
15 | onDispose {
16 | activity.requestedOrientation = originalOrientation
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/event/Event.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.event
2 |
3 | import androidx.compose.ui.graphics.vector.ImageVector
4 | import ru.tech.cookhelper.presentation.ui.utils.compose.UIText
5 | import ru.tech.cookhelper.presentation.ui.utils.navigation.Screen
6 |
7 | sealed class Event {
8 | class ShowSnackbar(val text: UIText, val action: () -> Unit) : Event()
9 | class ShowToast(val text: UIText, val icon: ImageVector? = null) : Event()
10 | class NavigateTo(val screen: Screen) : Event()
11 | class NavigateIf(val predicate: (Screen?) -> Boolean, val screen: Screen) : Event()
12 |
13 | class SendData(vararg val data: Pair) : Event() {
14 | val mappedData = data.toMap()
15 | inline operator fun get(key: String): T? = mappedData[key] as? T
16 | fun count() = mappedData.count()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/navigation/BottomSheet.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.navigation
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.IgnoredOnParcel
5 | import kotlinx.parcelize.Parcelize
6 | import ru.tech.cookhelper.domain.model.ForumFilters
7 |
8 | sealed class BottomSheet(
9 | val gesturesEnabled: Boolean = true,
10 | val dismissOnTapOutside: Boolean = true,
11 | val nestedScrollEnabled: Boolean = true,
12 | @IgnoredOnParcel val onDismiss: (() -> Unit)? = null
13 | ) : Parcelable {
14 | @Parcelize
15 | class ForumFilter(
16 | @IgnoredOnParcel val filters: ForumFilters = ForumFilters.empty(),
17 | @IgnoredOnParcel val onFiltersChange: (ForumFilters) -> Unit = {}
18 | ) : BottomSheet(
19 | gesturesEnabled = false,
20 | dismissOnTapOutside = false,
21 | nestedScrollEnabled = false
22 | )
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/ChatDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.Chat
5 | import ru.tech.cookhelper.domain.model.FileData
6 | import ru.tech.cookhelper.domain.model.Message
7 |
8 | data class ChatDto(
9 | val id: Long,
10 | val images: List?,
11 | val title: String,
12 | val lastMessage: Message?,
13 | val newMessagesCount: Int,
14 | val members: List,
15 | val messages: List,
16 | val creationTimestamp: Long
17 | ) : Dto {
18 | override fun asDomain(): Chat = Chat(
19 | id = id,
20 | images = images,
21 | title = title,
22 | lastMessage = lastMessage,
23 | newMessagesCount = newMessagesCount,
24 | members = members,
25 | messages = messages,
26 | creationTimestamp = creationTimestamp
27 | )
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/android/ConfigurationUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.android
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.ui.unit.Dp
5 | import androidx.compose.ui.unit.dp
6 | import kotlin.math.max
7 | import kotlin.math.min
8 |
9 | object ConfigurationUtils {
10 |
11 | val Configuration.isLandscape: Boolean
12 | get() {
13 | return orientation == Configuration.ORIENTATION_LANDSCAPE
14 | }
15 |
16 | val Configuration.isPortrait: Boolean
17 | get() {
18 | return orientation == Configuration.ORIENTATION_PORTRAIT
19 | }
20 |
21 | val Configuration.maxScreenDp: Dp
22 | get() {
23 | return max(screenHeightDp, screenWidthDp).dp
24 | }
25 |
26 | val Configuration.minScreenDp: Dp
27 | get() {
28 | return min(screenHeightDp, screenWidthDp).dp
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/User.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class User(
6 | val id: Long,
7 | val avatar: List = emptyList(),
8 | val bannedIngredients: List? = null,
9 | val bannedRecipes: List? = null,
10 | val email: String,
11 | val forums: List? = null,
12 | val fridge: List = emptyList(),
13 | val name: String,
14 | val nickname: String,
15 | val starredIngredients: List? = null,
16 | val userPosts: List? = null,
17 | val userRecipes: List? = null,
18 | val starredRecipes: List? = null,
19 | val status: String? = "",
20 | val verified: Boolean = false,
21 | val surname: String,
22 | val lastSeen: Long = 0,
23 | val token: String = ""
24 | ) : Domain
25 |
26 | fun User?.getLastAvatar(): String? = this?.avatar?.lastOrNull()?.link
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/provider/LocalBottomSheetController.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.provider
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import dev.olshevski.navigation.reimagined.NavController
5 | import dev.olshevski.navigation.reimagined.popAll
6 | import ru.tech.cookhelper.presentation.ui.utils.navigation.BottomSheet
7 | import ru.tech.cookhelper.presentation.ui.widgets.bottomsheet.BottomSheetState
8 |
9 | val LocalBottomSheetController =
10 | compositionLocalOf { error("BottomSheetController not present") }
11 |
12 | suspend fun BottomSheetController.show(sheet: BottomSheet) {
13 | controller.navigateAndPopAll(sheet)
14 | state.expand()
15 | }
16 |
17 | suspend fun BottomSheetController.close() {
18 | state.collapse()
19 | controller.popAll()
20 | }
21 |
22 | data class BottomSheetController(
23 | val controller: NavController,
24 | val state: BottomSheetState
25 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/PostDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.FileData
5 | import ru.tech.cookhelper.domain.model.Post
6 | import ru.tech.cookhelper.domain.model.User
7 |
8 | data class PostDto(
9 | val id: Long,
10 | val author: User,
11 | val timestamp: Long,
12 | val label: String,
13 | val text: String,
14 | val likes: List,
15 | val comments: List,
16 | val reposts: List,
17 | val attachments: List,
18 | val images: List
19 | ) : Dto {
20 | override fun asDomain(): Post = Post(
21 | id = id,
22 | author = author,
23 | timestamp = timestamp,
24 | label = label,
25 | text = text,
26 | likes = likes,
27 | comments = comments,
28 | reposts = reposts,
29 | attachments = attachments,
30 | images = attachments
31 | )
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/constants/Constants.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.constants
2 |
3 | object Constants {
4 |
5 | val DOTS = "." * 100
6 |
7 | const val DELIMITER = "*"
8 |
9 | const val HOST_URL = "192.168.43.51:8080"
10 |
11 | const val BASE_URL = "http://$HOST_URL/"
12 |
13 | const val WS_BASE_URL = "ws://$HOST_URL/"
14 |
15 | const val LOREM_IPSUM =
16 | "Lorem ipsum dolor sit amet, consectetur adipisci elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
17 |
18 | }
19 |
20 | private operator fun String.times(count: Int): String {
21 | var s = this
22 | repeat(count) { s += this }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/android/Logger.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.android
2 |
3 | import android.util.Log
4 |
5 | object Logger {
6 |
7 | fun makeLog(
8 | any: Any?,
9 | tag: String = this::class.java.simpleName,
10 | level: Level = Level.DEBUG
11 | ) {
12 | when (level) {
13 | Level.VERBOSE -> Log.v(tag, any.toString())
14 | Level.DEBUG -> Log.d(tag, any.toString())
15 | Level.INFO -> Log.i(tag, any.toString())
16 | Level.WARN -> Log.w(tag, any.toString())
17 | Level.ERROR -> Log.e(tag, any.toString())
18 | }
19 | }
20 |
21 | fun Any?.log(
22 | tag: String = (this@Logger)::class.java.simpleName,
23 | level: Level = Level.DEBUG
24 | ) {
25 | makeLog(
26 | any = this,
27 | tag = tag,
28 | level = level
29 | )
30 | }
31 |
32 | enum class Level {
33 | VERBOSE, DEBUG, INFO, WARN, ERROR
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/ForumFilters.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.domain.utils.Domain
4 |
5 | data class ForumFilters(
6 | val queryString: String,
7 | val noRepliesFilter: Boolean,
8 | val imageFilter: Boolean,
9 | val ratingNeutralFilter: Boolean,
10 | val ratingPositiveFilter: Boolean,
11 | val ratingNegativeFilter: Boolean,
12 | val tagFilter: String,
13 | val ratingSort: Boolean,
14 | val recencySort: Boolean,
15 | val reverseSort: Boolean,
16 | ) : Domain {
17 | companion object {
18 | fun empty() = ForumFilters(
19 | queryString = "",
20 | noRepliesFilter = false,
21 | imageFilter = false,
22 | ratingNeutralFilter = false,
23 | ratingPositiveFilter = false,
24 | ratingNegativeFilter = false,
25 | tagFilter = "",
26 | ratingSort = false,
27 | recencySort = true,
28 | reverseSort = false
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/theme/Typography.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | val Typography = Typography(
10 | bodyLarge = TextStyle(
11 | fontFamily = FontFamily.Default,
12 | fontWeight = FontWeight.Normal,
13 | fontSize = 16.sp,
14 | lineHeight = 24.sp,
15 | letterSpacing = 0.5.sp
16 | ),
17 | titleLarge = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.Normal,
20 | fontSize = 22.sp,
21 | lineHeight = 28.sp,
22 | letterSpacing = 0.sp
23 | ),
24 | labelSmall = TextStyle(
25 | fontFamily = FontFamily.Default,
26 | fontWeight = FontWeight.Medium,
27 | fontSize = 11.sp,
28 | lineHeight = 16.sp,
29 | letterSpacing = 0.5.sp
30 | )
31 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/utils/text/validators/LengthTextValidator.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.utils.text.validators
2 |
3 | import ru.tech.cookhelper.domain.utils.text.TextValidator
4 | import ru.tech.cookhelper.domain.utils.text.ValidatorResult
5 |
6 | class LengthTextValidator(
7 | private val minLength: Int? = null,
8 | private val maxLength: Int? = null,
9 | private val message: (minNotReached: Boolean, maxOverflowed: Boolean) -> S
10 | ) : TextValidator {
11 | override var validatorResult: ValidatorResult = ValidatorResult.NoResult()
12 | override fun validate(stringToValidate: String): ValidatorResult {
13 | validatorResult = when {
14 | minLength != null && stringToValidate.count() < minLength ->
15 | ValidatorResult.Failure(message(true, false))
16 | maxLength != null && stringToValidate.count() > maxLength ->
17 | ValidatorResult.Failure(message(false, true))
18 | else -> ValidatorResult.Success()
19 | }
20 | return validatorResult
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/message/MessageServiceImpl.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket.message
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.core.constants.Constants
5 | import ru.tech.cookhelper.data.remote.dto.MessageDto
6 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketClient
7 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketState
8 | import ru.tech.cookhelper.data.utils.JsonParser
9 | import javax.inject.Inject
10 |
11 | class MessageServiceImpl @Inject constructor(
12 | jsonParser: JsonParser,
13 | ) : WebSocketClient(jsonParser = jsonParser), MessageService {
14 |
15 | override operator fun invoke(
16 | chatId: Long, token: String
17 | ): Flow> = setBaseUrl(
18 | newBaseUrl = "${Constants.WS_BASE_URL}websocket/chat/?token=$token&id=$chatId"
19 | ).setType(MessageDto::class.java).openWebSocket().receiveAsFlow()
20 |
21 | override fun sendMessage(data: String) = send(data)
22 |
23 | override fun closeService() = close()
24 |
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/repository/SettingsRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.repository
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.map
5 | import ru.tech.cookhelper.data.local.dao.SettingsDao
6 | import ru.tech.cookhelper.data.local.entity.SettingsEntity
7 | import ru.tech.cookhelper.domain.model.Setting
8 | import ru.tech.cookhelper.domain.repository.SettingsRepository
9 | import javax.inject.Inject
10 |
11 | class SettingsRepositoryImpl @Inject constructor(
12 | private val settingsDao: SettingsDao
13 | ) : SettingsRepository {
14 |
15 | override fun getSettingsFlow(): Flow> =
16 | settingsDao.getSettingsFlow()
17 | .map { settingsList ->
18 | settingsList.map { entity -> entity.asDomain() }
19 | }
20 |
21 | override suspend fun getSettings(): List =
22 | settingsDao.getSettings().map { it.asDomain() }
23 |
24 | override suspend fun insertSetting(id: Int, option: String) {
25 | settingsDao.insertSetting(SettingsEntity(id, option))
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/TopicDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.FileData
5 | import ru.tech.cookhelper.domain.model.Reply
6 | import ru.tech.cookhelper.domain.model.Topic
7 | import ru.tech.cookhelper.domain.model.User
8 |
9 | data class TopicDto(
10 | val id: Long,
11 | val author: User,
12 | val title: String,
13 | val text: String,
14 | val replies: List,
15 | val attachments: List,
16 | val tags: List,
17 | val timestamp: Long,
18 | val closed: Boolean,
19 | val ratingPositive: List,
20 | val ratingNegative: List
21 | ) : Dto {
22 | override fun asDomain(): Topic = Topic(
23 | id = id,
24 | author = author,
25 | title = title,
26 | text = text,
27 | replies = replies,
28 | attachments = attachments,
29 | tags = tags,
30 | timestamp = timestamp,
31 | closed = closed,
32 | ratingPositive = ratingPositive,
33 | ratingNegative = ratingNegative
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/utils/ConnectionUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.utils
2 |
3 | import android.content.Context
4 | import android.content.Context.CONNECTIVITY_SERVICE
5 | import android.net.ConnectivityManager
6 | import android.net.NetworkCapabilities
7 | import android.os.Build
8 |
9 | object ConnectionUtils {
10 | fun Context.isOnline(): Boolean {
11 | (getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager).apply {
12 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
13 | getNetworkCapabilities(activeNetwork)
14 | ?.run {
15 | listOf(
16 | hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR),
17 | hasTransport(NetworkCapabilities.TRANSPORT_WIFI),
18 | hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET),
19 | ).any { it }
20 | } ?: false
21 | } else @Suppress("DEPRECATION") {
22 | activeNetworkInfo != null && activeNetworkInfo?.isConnected == true
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/recipe_post_creation/components/FabSize.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.recipe_post_creation.components
2 |
3 | import androidx.compose.material3.FloatingActionButtonDefaults
4 | import androidx.compose.ui.graphics.Shape
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.dp
7 | import ru.tech.cookhelper.presentation.ui.theme.SquircleShape
8 |
9 | sealed class FabSize(
10 | val horizontalPadding: Dp,
11 | val spacerPadding: Dp,
12 | val iconSize: Dp,
13 | val shape: Shape
14 | ) {
15 | object Small : FabSize(
16 | iconSize = 24.dp,
17 | spacerPadding = 4.dp,
18 | horizontalPadding = 8.dp,
19 | shape = SquircleShape(12.dp)
20 | )
21 |
22 | object Common : FabSize(
23 | iconSize = 24.dp,
24 | spacerPadding = 12.dp,
25 | horizontalPadding = 16.dp,
26 | shape = SquircleShape(16.dp)
27 | )
28 |
29 | object Large : FabSize(
30 | iconSize = FloatingActionButtonDefaults.LargeIconSize,
31 | spacerPadding = 20.dp,
32 | horizontalPadding = 24.dp,
33 | shape = SquircleShape(28.dp)
34 | )
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/fridge_screen/components/ProductUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.fridge_screen.components
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Egg
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import ru.tech.cookhelper.domain.model.Product
7 | import ru.tech.cookhelper.presentation.ui.theme.*
8 |
9 | fun Product.getIcon(): ImageVector = when (this.category) {
10 | 1 -> Icons.Filled.Steak
11 | 2 -> Icons.Filled.Fish
12 | 3 -> Icons.Filled.Milk
13 | 4 -> Icons.Filled.Egg
14 | 5 -> Icons.Filled.Carrot
15 | 6 -> Icons.Filled.Apple
16 | 7 -> Icons.Filled.Baguette
17 | 8 -> Icons.Filled.Barley
18 | 9 -> Icons.Filled.Shaker
19 | 10 -> Icons.Filled.Candy
20 | 11 -> Icons.Filled.Cup
21 | 12 -> Icons.Filled.Bean
22 | 13 -> Icons.Filled.Mushroom
23 | 14 -> Icons.Filled.Jellyfish
24 | 15 -> Icons.Filled.Flavour
25 | 16 -> Icons.Filled.Peanut
26 | 17 -> Icons.Filled.DriedGrape
27 | 18 -> Icons.Filled.Cheese
28 | 19 -> Icons.Filled.Cherry
29 | 20 -> Icons.Filled.Oil
30 | else -> Icons.Filled.BorderRadius
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/api/ingredients/FridgeApi.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.api.ingredients
2 |
3 | import retrofit2.http.*
4 | import ru.tech.cookhelper.data.remote.dto.MatchedRecipeDto
5 | import ru.tech.cookhelper.data.remote.dto.ProductDto
6 | import ru.tech.cookhelper.data.remote.dto.UserDto
7 | import ru.tech.cookhelper.data.remote.utils.Response
8 |
9 | interface FridgeApi {
10 |
11 | @GET("api/ingredient/get/all/")
12 | suspend fun getAvailableProducts(): Result>>
13 |
14 | @Multipart
15 | @POST("api/user/post/fridge/insert/")
16 | suspend fun addProductsToFridge(
17 | @Part("token") token: String,
18 | @Part("fridge") fridge: String
19 | ): Result>
20 |
21 | @Multipart
22 | @POST("api/user/post/fridge/remove/")
23 | suspend fun removeProductsFromFridge(
24 | @Part("token") token: String,
25 | @Part("fridge") fridge: String
26 | ): Result>
27 |
28 | @GET("api/user/get/fridge/recipe/")
29 | suspend fun getMatchedRecipes(
30 | @Query("token") token: String
31 | ): Result>>
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/settings/components/RotationButton.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.settings.components
2 |
3 | import androidx.compose.animation.core.animateFloatAsState
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.rounded.KeyboardArrowDown
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.material3.IconButton
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.rotate
13 | import androidx.compose.ui.unit.dp
14 |
15 | @Composable
16 | fun RotationButton(
17 | modifier: Modifier = Modifier,
18 | rotated: Boolean = false,
19 | onClick: () -> Unit,
20 | ) {
21 | val rotation: Float by animateFloatAsState(if (rotated) 180f else 0f)
22 | IconButton(
23 | onClick = onClick,
24 | modifier = modifier.rotate(rotation),
25 | ) {
26 | Icon(
27 | imageVector = Icons.Rounded.KeyboardArrowDown,
28 | contentDescription = null,
29 | modifier = Modifier.size(26.dp)
30 | )
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/event/EventUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.event
2 |
3 | import androidx.compose.runtime.*
4 | import androidx.compose.ui.platform.LocalLifecycleOwner
5 | import androidx.lifecycle.Lifecycle
6 | import androidx.lifecycle.LifecycleOwner
7 | import androidx.lifecycle.flowWithLifecycle
8 | import androidx.lifecycle.lifecycleScope
9 | import kotlinx.coroutines.Job
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.launch
12 |
13 | @Composable
14 | inline fun Flow.collectWithLifecycle(
15 | lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
16 | minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
17 | noinline action: suspend (T) -> Unit
18 | ) {
19 | var job by remember { mutableStateOf(null) }
20 | LaunchedEffect(Unit) {
21 | job = lifecycleOwner.lifecycleScope.launch {
22 | flowWithLifecycle(
23 | lifecycle = lifecycleOwner.lifecycle,
24 | minActiveState = minActiveState
25 | ).collect(collector = action)
26 | }
27 | }
28 | DisposableEffect(Unit) {
29 | onDispose { job?.cancel() }
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/model/Recipe.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.model
2 |
3 | import ru.tech.cookhelper.core.constants.Constants.BASE_URL
4 | import ru.tech.cookhelper.domain.utils.Domain
5 |
6 | data class Recipe(
7 | val id: Long = 0,
8 | val author: User,
9 | val title: String,
10 | val cookSteps: List,
11 | val time: Long,
12 | val category: String,
13 | val ingredients: List,
14 | val measures: List,
15 | val proteins: Double,
16 | val carbohydrates: Double,
17 | val fats: Double,
18 | val calories: Double,
19 | val image: FileData,
20 | val comments: List = listOf(),
21 | val reposts: List = listOf(),
22 | val likes: List = listOf(),
23 | val timestamp: Long
24 | ) : Domain {
25 | fun toShareValue(): String {
26 | val n = "\n\n"
27 | return "$title${n}Категория - $category${n}Время приготовления - $time мин${n}Б/Ж/У - $proteins/$fats/${carbohydrates}${n}Калории - ${calories}$n${
28 | ingredients.joinToString(
29 | ", "
30 | ) { it.title }
31 | }${n}${
32 | cookSteps.joinToString(n)
33 | }${BASE_URL}recipe/$id"
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/api/user/UserApi.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.api.user
2 |
3 | import okhttp3.MultipartBody
4 | import retrofit2.Call
5 | import retrofit2.http.*
6 | import ru.tech.cookhelper.data.remote.dto.PostDto
7 | import ru.tech.cookhelper.data.remote.dto.RecipeDto
8 | import ru.tech.cookhelper.data.remote.dto.TopicDto
9 | import ru.tech.cookhelper.data.remote.utils.Response
10 |
11 | interface UserApi {
12 |
13 | @GET("api/user/get/feed/")
14 | suspend fun getFeed(
15 | @Query("token") token: String
16 | ): Result>>
17 |
18 | @Multipart
19 | @POST("api/feed/post/create/")
20 | fun createPost(
21 | @Part("token") token: String,
22 | @Part("label") label: String,
23 | @Part("text") text: String,
24 | @Part image: MultipartBody.Part?
25 | ): Call>
26 |
27 | @Multipart
28 | @POST("api/forum/post/topic/create/")
29 | fun createTopic(
30 | @Part("token") token: String,
31 | @Part("title") title: String,
32 | @Part("text") text: String,
33 | @Part files: MultipartBody.Part?,
34 | @Part("tags") tags: List
35 | ): Call>
36 |
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/user/UserServiceImpl.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket.user
2 |
3 | import com.squareup.moshi.Types
4 | import kotlinx.coroutines.flow.Flow
5 | import ru.tech.cookhelper.core.constants.Constants
6 | import ru.tech.cookhelper.data.remote.dto.UserDto
7 | import ru.tech.cookhelper.data.remote.utils.Response
8 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketClient
9 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketState
10 | import ru.tech.cookhelper.data.utils.JsonParser
11 | import javax.inject.Inject
12 |
13 | class UserServiceImpl @Inject constructor(
14 | jsonParser: JsonParser
15 | ) : WebSocketClient>(jsonParser = jsonParser), UserService {
16 |
17 | override operator fun invoke(
18 | id: Long,
19 | token: String
20 | ): Flow>> = setBaseUrl(
21 | newBaseUrl = "${Constants.WS_BASE_URL}websocket/user/?id=$id&token=$token"
22 | ).setType(
23 | Types.newParameterizedType(Response::class.java, UserDto::class.java)
24 | ).openWebSocket().receiveAsFlow()
25 |
26 | override fun sendMessage(data: String) = send(data)
27 |
28 | override fun closeService() = close()
29 |
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/provider/LocalWindowSizeClass.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.provider
2 |
3 | import android.app.Activity
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
7 | import androidx.compose.material3.windowsizeclass.WindowSizeClass
8 | import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.CompositionLocalProvider
11 | import androidx.compose.runtime.compositionLocalOf
12 |
13 | val LocalWindowSizeClass = compositionLocalOf { error("SizeClass not present") }
14 |
15 | @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
16 | @Composable
17 | fun Activity.provideWindowSizeClass(content: @Composable () -> Unit) {
18 | CompositionLocalProvider(
19 | LocalWindowSizeClass provides calculateWindowSizeClass(this),
20 | content = content
21 | )
22 | }
23 |
24 | fun ComponentActivity.setContentWithWindowSizeClass(
25 | content: @Composable () -> Unit
26 | ) = setContent {
27 | provideWindowSizeClass(content = content)
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/web_socket/feed/FeedServiceImpl.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.web_socket.feed
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.flow
5 | import ru.tech.cookhelper.data.remote.dto.RecipeDto
6 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketClient
7 | import ru.tech.cookhelper.data.remote.web_socket.WebSocketState
8 | import ru.tech.cookhelper.data.utils.JsonParser
9 | import javax.inject.Inject
10 |
11 | class FeedServiceImpl @Inject constructor(
12 | jsonParser: JsonParser
13 | ) : WebSocketClient>(jsonParser = jsonParser), FeedService {
14 |
15 | override operator fun invoke(
16 | token: String
17 | ): Flow>> = flow {
18 | emit(WebSocketState.Opening())
19 | }
20 |
21 | override fun sendMessage(data: String) = send(data)
22 |
23 | override fun closeService() = close()
24 |
25 | //updateBaseUrl(
26 | // newBaseUrl = "${Constants.WS_BASE_URL}ws/feed/?token=$token"
27 | // ).setType(
28 | // Types.newParameterizedType(
29 | // List::class.java,
30 | // RecipePostDto::class.java
31 | // )
32 | // ).openWebSocket().receiveAsFlow()
33 |
34 |
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/di/RoomModule.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.di
2 |
3 | import android.content.Context
4 | import androidx.room.Room
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.android.qualifiers.ApplicationContext
9 | import dagger.hilt.components.SingletonComponent
10 | import ru.tech.cookhelper.data.local.database.Database
11 | import ru.tech.cookhelper.data.local.database.TypeConverters
12 | import ru.tech.cookhelper.data.utils.JsonParser
13 | import javax.inject.Singleton
14 |
15 |
16 | @Module
17 | @InstallIn(SingletonComponent::class)
18 | object RoomModule {
19 |
20 | @Provides
21 | @Singleton
22 | fun provideDatabase(
23 | @ApplicationContext applicationContext: Context,
24 | typeConverters: TypeConverters
25 | ): Database = Room.databaseBuilder(
26 | context = applicationContext,
27 | klass = Database::class.java,
28 | name = "CookHelperDatabase"
29 | ).addTypeConverter(typeConverters)
30 | .fallbackToDestructiveMigration()
31 | .fallbackToDestructiveMigrationOnDowngrade()
32 | .build()
33 |
34 | @Singleton
35 | @Provides
36 | fun provideTypeConverters(
37 | jsonParser: JsonParser
38 | ): TypeConverters = TypeConverters(jsonParser)
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/recipe_details/viewModel/RecipeDetailsViewModel.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.recipe_details.viewModel
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.lifecycle.SavedStateHandle
7 | import androidx.lifecycle.ViewModel
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import ru.tech.cookhelper.domain.model.Recipe
10 | import ru.tech.cookhelper.domain.use_case.get_user.GetUserUseCase
11 | import ru.tech.cookhelper.presentation.ui.utils.compose.StateUtils.update
12 | import ru.tech.cookhelper.presentation.ui.utils.event.Event
13 | import ru.tech.cookhelper.presentation.ui.utils.event.ViewModelEvents
14 | import ru.tech.cookhelper.presentation.ui.utils.event.ViewModelEventsImpl
15 | import javax.inject.Inject
16 |
17 | @HiltViewModel
18 | class RecipeDetailsViewModel @Inject constructor(
19 | private val getUserUseCase: GetUserUseCase,
20 | savedStateHandle: SavedStateHandle
21 | ) : ViewModel(), ViewModelEvents by ViewModelEventsImpl() {
22 |
23 | private val _recipe: MutableState = mutableStateOf(null)
24 | val recipe: Recipe? by _recipe
25 |
26 | fun updateRecipe(recipe: Recipe?) {
27 | _recipe.update { recipe }
28 | }
29 |
30 |
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/compose/SnackbarUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.compose
2 |
3 | import androidx.compose.material3.SnackbarDuration
4 | import androidx.compose.material3.SnackbarHostState
5 | import androidx.compose.material3.SnackbarResult
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.launch
9 |
10 | object SnackbarUtils {
11 |
12 | fun SnackbarHostState.show(
13 | scope: CoroutineScope = CoroutineScope(Dispatchers.Main),
14 | message: String,
15 | actionLabel: String? = null,
16 | duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite,
17 | result: (SnackbarResult) -> Unit = {}
18 | ) = scope.launch {
19 | result(
20 | showSnackbar(
21 | message = message,
22 | actionLabel = actionLabel,
23 | duration = duration
24 | )
25 | )
26 | }
27 |
28 |
29 | inline val SnackbarResult.actionPerformed: Boolean
30 | get() {
31 | return this == SnackbarResult.ActionPerformed
32 | }
33 |
34 | inline val SnackbarResult.dismissed: Boolean
35 | get() {
36 | return this == SnackbarResult.Dismissed
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/app/components/ExitDialog.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.app.components
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.outlined.DoorBack
5 | import androidx.compose.material3.*
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.res.stringResource
8 | import androidx.compose.ui.text.style.TextAlign
9 | import ru.tech.cookhelper.R
10 | import ru.tech.cookhelper.presentation.ui.theme.DialogShape
11 |
12 | @Composable
13 | fun ExitDialog(onExit: () -> Unit, onDismissRequest: () -> Unit) {
14 | AlertDialog(
15 | title = { Text(stringResource(R.string.app_closing)) },
16 | text = {
17 | Text(
18 | stringResource(R.string.app_closing_message),
19 | textAlign = TextAlign.Center
20 | )
21 | },
22 | shape = DialogShape,
23 | onDismissRequest = onDismissRequest,
24 | icon = { Icon(Icons.Outlined.DoorBack, null) },
25 | confirmButton = {
26 | Button(onClick = onDismissRequest) {
27 | Text(stringResource(R.string.stay))
28 | }
29 | },
30 | dismissButton = {
31 | FilledTonalButton(onClick = onExit) {
32 | Text(stringResource(R.string.exit))
33 | }
34 | }
35 | )
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/widgets/Placeholder.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.widgets
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.vector.ImageVector
13 | import androidx.compose.ui.text.style.TextAlign
14 | import androidx.compose.ui.unit.dp
15 | import ru.tech.cookhelper.presentation.ui.utils.compose.navigationBarsLandscapePadding
16 |
17 | @Composable
18 | fun Placeholder(
19 | icon: ImageVector,
20 | text: String,
21 | modifier: Modifier = Modifier
22 | ) {
23 | Column(
24 | modifier = if (modifier == Modifier) Modifier
25 | .fillMaxSize()
26 | .padding(horizontal = 8.dp)
27 | .navigationBarsLandscapePadding()
28 | else modifier,
29 | verticalArrangement = Arrangement.Center,
30 | horizontalAlignment = Alignment.CenterHorizontally
31 | ) {
32 | Icon(icon, null, modifier = Modifier.fillMaxSize(0.3f))
33 | Text(text, textAlign = TextAlign.Center)
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/profile/components/LogoutDialog.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.profile.components
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.outlined.Logout
5 | import androidx.compose.material3.*
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.res.stringResource
8 | import androidx.compose.ui.text.style.TextAlign
9 | import ru.tech.cookhelper.R
10 | import ru.tech.cookhelper.presentation.ui.theme.DialogShape
11 |
12 | @Composable
13 | fun LogoutDialog(onLogout: () -> Unit, onDismissRequest: () -> Unit) {
14 | AlertDialog(
15 | title = { Text(stringResource(R.string.account_log_out)) },
16 | text = {
17 | Text(
18 | stringResource(R.string.log_out_message),
19 | textAlign = TextAlign.Center
20 | )
21 | },
22 | shape = DialogShape,
23 | onDismissRequest = { onDismissRequest() },
24 | icon = { Icon(Icons.Outlined.Logout, null) },
25 | confirmButton = {
26 | Button(onClick = { onDismissRequest() }) {
27 | Text(stringResource(R.string.stay))
28 | }
29 | },
30 | dismissButton = {
31 | FilledTonalButton(onClick = {
32 | onLogout()
33 | onDismissRequest()
34 | }) {
35 | Text(stringResource(R.string.exit))
36 | }
37 | }
38 | )
39 | }
--------------------------------------------------------------------------------
/dynamic_theme/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "com.cookhelper.dynamic.dynamictheme"
8 | compileSdk = 33
9 |
10 | defaultConfig {
11 | minSdk = 21
12 | targetSdk = 33
13 |
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = false
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro"
24 | )
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility = JavaVersion.VERSION_1_8
29 | targetCompatibility = JavaVersion.VERSION_1_8
30 | }
31 | kotlinOptions {
32 | jvmTarget = "1.8"
33 | freeCompilerArgs += "-Xexplicit-api=strict"
34 | }
35 | buildFeatures {
36 | compose = true
37 | }
38 | composeOptions {
39 | kotlinCompilerExtensionVersion = "1.4.2"
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation(platform("androidx.compose:compose-bom:2023.01.00"))
45 | implementation("androidx.core:core-ktx:1.9.0")
46 | implementation("androidx.compose.material3:material3")
47 | implementation(files("libs/material-color-util.jar"))
48 | implementation("androidx.palette:palette:1.0.0")
49 | }
50 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | org.gradle.caching=true
25 | org.gradle.parallel=true
26 | org.gradle.daemon=true
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/recipe_post_creation/components/LeaveUnsavedDataDialog.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.recipe_post_creation.components
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.outlined.Save
5 | import androidx.compose.material3.*
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.res.stringResource
8 | import androidx.compose.ui.text.style.TextAlign
9 | import ru.tech.cookhelper.R
10 | import ru.tech.cookhelper.presentation.ui.theme.DialogShape
11 |
12 | @Composable
13 | fun LeaveUnsavedDataDialog(
14 | title: Int,
15 | message: Int,
16 | onLeave: () -> Unit,
17 | onDismissRequest: () -> Unit
18 | ) {
19 | AlertDialog(
20 | shape = DialogShape,
21 | title = { Text(stringResource(title), textAlign = TextAlign.Center) },
22 | text = { Text(stringResource(message), textAlign = TextAlign.Center) },
23 | onDismissRequest = { onDismissRequest() },
24 | icon = { Icon(Icons.Outlined.Save, null) },
25 | confirmButton = {
26 | Button(onClick = { onDismissRequest() }) {
27 | Text(stringResource(R.string.cancel))
28 | }
29 | },
30 | dismissButton = {
31 | FilledTonalButton(onClick = {
32 | onDismissRequest()
33 | onLeave()
34 | }) {
35 | Text(stringResource(R.string.leave_without_saving))
36 | }
37 | }
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/RecipeDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.FileData
5 | import ru.tech.cookhelper.domain.model.Product
6 | import ru.tech.cookhelper.domain.model.Recipe
7 | import ru.tech.cookhelper.domain.model.User
8 |
9 | data class RecipeDto(
10 | val id: Long = 0,
11 | val author: User,
12 | val title: String,
13 | val cookSteps: List,
14 | val time: Long,
15 | val category: String,
16 | val ingredients: List,
17 | val measures: List,
18 | val proteins: Double,
19 | val carbohydrates: Double,
20 | val fats: Double,
21 | val calories: Double,
22 | val image: FileData,
23 | val comments: List = listOf(),
24 | val reposts: List = listOf(),
25 | val likes: List = listOf(),
26 | val timestamp: Long
27 | ) : Dto {
28 | override fun asDomain(): Recipe = Recipe(
29 | id = id,
30 | author = author,
31 | title = title,
32 | cookSteps = cookSteps,
33 | time = time,
34 | category = category,
35 | ingredients = ingredients,
36 | measures = measures,
37 | proteins = proteins,
38 | carbohydrates = carbohydrates,
39 | fats = fats,
40 | calories = calories,
41 | image = image,
42 | comments = comments,
43 | reposts = reposts,
44 | likes = likes,
45 | timestamp = timestamp
46 | )
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/constants/Status.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.constants
2 |
3 | object Status {
4 | const val SUCCESS: Int = 100
5 | const val WRONG_DATA: Int = 99
6 | const val PERMISSION_DENIED: Int = 98
7 | const val PARAMETER_MISSED: Int = 97
8 | const val EXCEPTION: Int = 0
9 | const val NO_INTERNET = -1
10 | const val CONNECTION_TIMED_OUT = -2
11 | const val READ_TIMEOUT = -3
12 |
13 | const val USER_NOT_FOUND: Int = 101
14 | const val WRONG_CREDENTIALS: Int = 102
15 | const val USER_NOT_VERIFIED: Int = 103
16 | const val USER_TOKEN_INVALID: Int = 104
17 | const val USER_DELETED: Int = 105
18 | const val NICKNAME_REJECTED: Int = 106
19 | const val EMAIL_REJECTED: Int = 107
20 | const val PASSWORD_REJECTED: Int = 108
21 | const val TOKEN_EXPIRED: Int = 109
22 | const val USER_UPLOAD_FAILED: Int = 110
23 |
24 | const val RECIPE_NOT_FOUND: Int = 201
25 | const val RECIPE_DELETED: Int = 202
26 | const val RECIPE_NOT_CREATED: Int = 204
27 |
28 | const val CHAT_NOT_FOUND: Int = 301
29 | const val CHAT_DELETED: Int = 302
30 | const val CHAT_NOT_CREATED: Int = 304
31 |
32 | const val TOPIC_NOT_FOUND: Int = 401
33 | const val TOPIC_DELETED: Int = 402
34 | const val ANSWER_NOT_ADDED: Int = 403
35 | const val TOPIC_NOT_CREATED: Int = 404
36 |
37 | const val COMMENT_NOT_FOUND: Int = 801
38 | const val COMMENT_DELETED: Int = 802
39 |
40 | const val POST_NOT_FOUND: Int = 901
41 | const val POST_DELETED: Int = 902
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/core/di/RetrofitModule.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.core.di
2 |
3 | import com.skydoves.retrofit.adapters.result.ResultCallAdapterFactory
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import okhttp3.OkHttpClient
9 | import okhttp3.logging.HttpLoggingInterceptor
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.moshi.MoshiConverterFactory
12 | import ru.tech.cookhelper.core.constants.Constants
13 | import ru.tech.cookhelper.core.utils.RetrofitUtils.setTimeout
14 | import java.util.concurrent.TimeUnit
15 | import javax.inject.Singleton
16 |
17 |
18 | @Module
19 | @InstallIn(SingletonComponent::class)
20 | object RetrofitModule {
21 |
22 | @Provides
23 | @Singleton
24 | fun provideRetrofit(): Retrofit = Retrofit.Builder()
25 | .baseUrl(Constants.BASE_URL)
26 | .addConverterFactory(MoshiConverterFactory.create())
27 | .addCallAdapterFactory(ResultCallAdapterFactory.create())
28 | .let {
29 | val httpClient = OkHttpClient.Builder()
30 | .setTimeout(60, TimeUnit.SECONDS)
31 | val logging = HttpLoggingInterceptor()
32 | logging.setLevel(HttpLoggingInterceptor.Level.BODY)
33 | httpClient
34 | .addInterceptor(logging)
35 | // .addInterceptor(RetrofitUtils.RetryInterceptor { !it.isSuccessful })
36 | return@let it.client(httpClient.build())
37 | }
38 | .build()
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/forum_discussion/viewModel/ForumDiscussionViewModel.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.forum_discussion.viewModel
2 |
3 | import android.graphics.Bitmap
4 | import androidx.compose.runtime.MutableState
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.setValue
8 | import androidx.lifecycle.SavedStateHandle
9 | import androidx.lifecycle.ViewModel
10 | import androidx.lifecycle.viewModelScope
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import ru.tech.cookhelper.presentation.ui.utils.android.ImageUtils.AsyncBlur.blur
13 | import ru.tech.cookhelper.presentation.ui.utils.android.ImageUtils.signature
14 | import ru.tech.cookhelper.presentation.ui.utils.compose.StateUtils.update
15 | import javax.inject.Inject
16 |
17 | @HiltViewModel
18 | class ForumDiscussionViewModel @Inject constructor(
19 | savedStateHandle: SavedStateHandle
20 | ) : ViewModel() {
21 |
22 | private val _blurredBitmap: MutableState = mutableStateOf(null)
23 | var blurredBitmap: Bitmap? by _blurredBitmap
24 |
25 | private var bitmapSignature: String = ""
26 |
27 | fun blur(bitmap: Bitmap) {
28 | bitmap.blur(scope = viewModelScope) {
29 | val sign = it?.signature() ?: ""
30 | if (bitmapSignature != sign) {
31 | _blurredBitmap.update { it }
32 | bitmapSignature = it?.signature() ?: ""
33 | } else it?.recycle()
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/chat/components/MessageHeader.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.chat.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.shape.CircleShape
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.Surface
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.text.font.FontWeight
14 | import androidx.compose.ui.unit.dp
15 | import androidx.compose.ui.unit.sp
16 | import ru.tech.cookhelper.presentation.ui.utils.compose.ColorUtils.createInverseSecondaryColor
17 |
18 | @Composable
19 | fun MessageHeader(text: String) {
20 | Row(
21 | modifier = Modifier
22 | .fillMaxWidth()
23 | .padding(8.dp),
24 | horizontalArrangement = Arrangement.Center
25 | ) {
26 | Surface(
27 | shape = CircleShape,
28 | color = MaterialTheme.colorScheme.surfaceVariant.createInverseSecondaryColor(0.3f)
29 | .copy(alpha = 0.5f)
30 | ) {
31 | Text(
32 | text = text,
33 | modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
34 | fontSize = 14.sp,
35 | fontWeight = FontWeight.SemiBold
36 | )
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/chat_list/components/ChatPicture.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.chat_list.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.shape.CircleShape
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.text.font.FontWeight
12 | import androidx.compose.ui.unit.sp
13 | import ru.tech.cookhelper.presentation.ui.utils.compose.widgets.Picture
14 |
15 | @Composable
16 | fun ChatPicture(
17 | modifier: Modifier,
18 | image: String?,
19 | title: String
20 | ) {
21 | if (image != null) {
22 | Picture(
23 | model = image,
24 | modifier = modifier
25 | )
26 | } else {
27 | Box(
28 | modifier = Modifier
29 | .then(modifier)
30 | .background(
31 | color = MaterialTheme.colorScheme.tertiaryContainer,
32 | shape = CircleShape
33 | ),
34 | contentAlignment = Alignment.Center
35 | ) {
36 | Text(
37 | text = (title.getOrNull(0)?.toString() ?: "#"),
38 | fontWeight = FontWeight.Bold,
39 | fontSize = 24.sp,
40 | color = MaterialTheme.colorScheme.onTertiaryContainer
41 | )
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/home_screen/components/BottomNavigationBar.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.home_screen.components
2 |
3 | import androidx.compose.foundation.layout.WindowInsets
4 | import androidx.compose.material3.*
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.text.style.TextOverflow
7 | import ru.tech.cookhelper.presentation.ui.utils.compose.ResUtils.getIcon
8 | import ru.tech.cookhelper.presentation.ui.utils.navigation.Screen
9 |
10 | @Composable
11 | fun BottomNavigationBar(
12 | windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
13 | selectedItem: T,
14 | items: List,
15 | onClick: (screen: T) -> Unit
16 | ) {
17 | NavigationBar(windowInsets = windowInsets) {
18 | items.forEach { screen ->
19 | NavigationBarItem(
20 | icon = {
21 | Icon(
22 | screen.getIcon(selectedItem == screen),
23 | null
24 | )
25 | },
26 | alwaysShowLabel = false,
27 | label = {
28 | Text(
29 | screen.shortTitle.asString(),
30 | maxLines = 1,
31 | overflow = TextOverflow.Ellipsis
32 | )
33 | },
34 | selected = selectedItem == screen,
35 | onClick = {
36 | if (selectedItem != screen) onClick(screen)
37 | }
38 | )
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/crash_screen/viewModel/CrashViewModel.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.crash_screen.viewModel
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.launchIn
10 | import kotlinx.coroutines.flow.onEach
11 | import kotlinx.coroutines.runBlocking
12 | import ru.tech.cookhelper.domain.use_case.get_settings_list.GetSettingsListUseCase
13 | import ru.tech.cookhelper.domain.use_case.get_settings_list.GetSettingsListUseCaseFlow
14 | import ru.tech.cookhelper.presentation.settings.components.SettingsState
15 | import ru.tech.cookhelper.presentation.settings.components.mapToState
16 | import ru.tech.cookhelper.presentation.ui.utils.compose.StateUtils.update
17 | import javax.inject.Inject
18 |
19 | @HiltViewModel
20 | class CrashViewModel @Inject constructor(
21 | getSettingsListUseCase: GetSettingsListUseCase,
22 | getSettingsListUseCaseFlow: GetSettingsListUseCaseFlow
23 | ) : ViewModel() {
24 |
25 | private val _settingsState: MutableState = mutableStateOf(
26 | runBlocking { getSettingsListUseCase().mapToState() }
27 | )
28 | val settingsState: SettingsState by _settingsState
29 |
30 | init {
31 | getSettingsListUseCaseFlow().onEach {
32 | _settingsState.update { it.mapToState() }
33 | }.launchIn(viewModelScope)
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/forum_discussion/components/TagItem.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.forum_discussion.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.text.style.TextAlign
15 | import androidx.compose.ui.unit.dp
16 |
17 | @Composable
18 | fun TagItem(modifier: Modifier = Modifier, text: String, onClick: () -> Unit) {
19 | Box(
20 | modifier = modifier
21 | .border(
22 | width = 1.dp,
23 | color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.3f),
24 | shape = RoundedCornerShape(8.dp)
25 | )
26 | .clip(RoundedCornerShape(8.dp))
27 | .background(MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.4f))
28 | .clickable { onClick() }
29 | .padding(10.dp)
30 | ) {
31 | Text(
32 | text = text,
33 | color = MaterialTheme.colorScheme.onTertiaryContainer,
34 | textAlign = TextAlign.Center,
35 | style = MaterialTheme.typography.bodySmall,
36 | maxLines = 1
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/provider/LocalScreenController.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.provider
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.ReadOnlyComposable
5 | import androidx.compose.runtime.compositionLocalOf
6 | import dev.olshevski.navigation.reimagined.NavController
7 | import dev.olshevski.navigation.reimagined.pop
8 | import dev.olshevski.navigation.reimagined.popAll
9 | import ru.tech.cookhelper.core.utils.ReflectionUtils.name
10 | import ru.tech.cookhelper.presentation.ui.utils.navigation.Screen
11 | import dev.olshevski.navigation.reimagined.navigate as libNavigate
12 |
13 | val LocalScreenController = compositionLocalOf> {
14 | error("ScreenController not present")
15 | }
16 |
17 | inline val T.isCurrentDestination: Boolean
18 | @ReadOnlyComposable
19 | @Composable
20 | get() = LocalScreenController.current.currentDestination == this
21 |
22 | inline val NavController.currentDestination: T? get() = this.backstack.entries.lastOrNull()?.destination
23 |
24 | fun NavController.navigate(destination: T) = apply {
25 | if ((currentDestination ?: "")::class.name != destination::class.name) libNavigate(destination)
26 | }
27 |
28 | fun NavController.navigateAndPopAll(destination: T) = apply {
29 | if ((currentDestination ?: "")::class.name != destination::class.name) {
30 | popAll()
31 | libNavigate(destination)
32 | }
33 | }
34 |
35 | fun NavController.goBack(): Boolean {
36 | if (backstack.entries.size == 1) return false
37 | return pop()
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/compose/UIText.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.compose
2 |
3 | import android.content.Context
4 | import androidx.annotation.StringRes
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.res.stringResource
7 |
8 | sealed class UIText {
9 | data class DynamicString(val value: String) : UIText()
10 | class StringResource(
11 | @StringRes val resId: Int, vararg val args: Any
12 | ) : UIText()
13 |
14 | @Composable
15 | fun asString(): String {
16 | return when (this) {
17 | is DynamicString -> value
18 | is StringResource -> stringResource(resId, *args)
19 | }
20 | }
21 |
22 | fun asString(context: Context): String {
23 | return when (this) {
24 | is DynamicString -> value
25 | is StringResource -> context.getString(resId, *args)
26 | }
27 | }
28 |
29 | fun isEmpty(): Boolean {
30 | return when (this) {
31 | is DynamicString -> value.isEmpty()
32 | is StringResource -> false
33 | }
34 | }
35 |
36 | fun isNotEmpty(): Boolean = !isEmpty()
37 |
38 | @Suppress("FunctionName")
39 | companion object {
40 | fun Empty() = UIText.DynamicString("")
41 | fun String.asUIText() = UIText.DynamicString(this)
42 | fun Int.asUIText() = UIText.StringResource(this)
43 | fun UIText(value: String?) = UIText.DynamicString(value)
44 | fun UIText(@StringRes value: Int) = UIText.StringResource(value)
45 | fun DynamicString(value: String?) = UIText.DynamicString(value ?: "")
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/dto/UserDto.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.dto
2 |
3 | import ru.tech.cookhelper.data.remote.utils.Dto
4 | import ru.tech.cookhelper.domain.model.FileData
5 | import ru.tech.cookhelper.domain.model.Product
6 | import ru.tech.cookhelper.domain.model.User
7 |
8 | data class UserDto(
9 | val id: Long?,
10 | val avatar: List,
11 | val bannedIngredients: List?,
12 | val bannedRecipes: List?,
13 | val email: String?,
14 | val forums: List?,
15 | val fridge: List,
16 | val name: String?,
17 | val nickname: String?,
18 | val userPosts: List? = null,
19 | val userRecipes: List? = null,
20 | val starredIngredients: List?,
21 | val starredRecipes: List?,
22 | val status: String?,
23 | val verified: Boolean?,
24 | val surname: String?,
25 | val lastSeen: Long?,
26 | val token: String?
27 | ) : Dto {
28 | override fun asDomain(): User = User(
29 | id = id ?: 0,
30 | avatar = avatar,
31 | bannedIngredients = bannedIngredients,
32 | bannedRecipes = bannedRecipes,
33 | email = email ?: "",
34 | forums = forums,
35 | fridge = fridge,
36 | name = name ?: "",
37 | nickname = nickname ?: "",
38 | starredIngredients = userPosts,
39 | userPosts = userRecipes,
40 | userRecipes = starredIngredients,
41 | starredRecipes = starredRecipes,
42 | status = status,
43 | verified = verified ?: false,
44 | surname = surname ?: "",
45 | lastSeen = lastSeen ?: 0L,
46 | token = token ?: ""
47 | )
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/forum_screen/components/SearchBox.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.forum_screen.components
2 |
3 | import androidx.activity.compose.BackHandler
4 | import androidx.compose.foundation.text.BasicTextField
5 | import androidx.compose.foundation.text.KeyboardActions
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.SolidColor
10 | import androidx.compose.ui.platform.LocalFocusManager
11 | import androidx.compose.ui.text.TextStyle
12 | import androidx.compose.ui.text.style.TextAlign
13 | import androidx.compose.ui.unit.sp
14 |
15 | @Composable
16 | fun SearchBox(
17 | modifier: Modifier = Modifier,
18 | value: String,
19 | textStyle: TextStyle = TextStyle(
20 | fontSize = 22.sp,
21 | color = MaterialTheme.colorScheme.onBackground,
22 | textAlign = TextAlign.Start,
23 | ),
24 | hint: @Composable () -> Unit,
25 | onValueChange: (String) -> Unit
26 | ) {
27 | val localFocusManager = LocalFocusManager.current
28 | BasicTextField(
29 | modifier = modifier,
30 | value = value,
31 | textStyle = textStyle,
32 | keyboardActions = KeyboardActions(
33 | onDone = { localFocusManager.clearFocus() }
34 | ),
35 | singleLine = true,
36 | cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
37 | onValueChange = onValueChange
38 | )
39 | if (value.isEmpty()) {
40 | hint()
41 | } else {
42 | BackHandler {
43 | onValueChange("")
44 | localFocusManager.clearFocus()
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/profile/components/PostActionButton.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.profile.components
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.foundation.shape.CircleShape
5 | import androidx.compose.material3.*
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.vector.ImageVector
11 | import androidx.compose.ui.text.TextStyle
12 | import androidx.compose.ui.text.font.FontWeight
13 | import androidx.compose.ui.unit.dp
14 | import ru.tech.cookhelper.presentation.ui.theme.Gray
15 |
16 | @OptIn(ExperimentalMaterial3Api::class)
17 | @Composable
18 | fun PostActionButton(
19 | onClick: () -> Unit,
20 | icon: ImageVector,
21 | text: String = "",
22 | containerColor: Color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.25f),
23 | contentColor: Color = Gray
24 | ) {
25 | Surface(
26 | modifier = Modifier.defaultMinSize(32.dp, 32.dp),
27 | shape = CircleShape,
28 | color = containerColor,
29 | onClick = onClick,
30 | contentColor = contentColor
31 | ) {
32 | ProvideTextStyle(value = TextStyle(fontWeight = FontWeight.Bold)) {
33 | Row(
34 | Modifier.padding(horizontal = 12.dp),
35 | horizontalArrangement = Arrangement.Center,
36 | verticalAlignment = Alignment.CenterVertically,
37 | ) {
38 | Icon(icon, null)
39 | if (text != "") {
40 | Spacer(Modifier.size(8.dp))
41 | Text(text)
42 | }
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/compose/ResUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.compose
2 |
3 | import android.content.Context
4 | import androidx.annotation.PluralsRes
5 | import androidx.annotation.StringRes
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.ExperimentalComposeUiApi
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.res.stringResource
10 | import ru.tech.cookhelper.presentation.ui.utils.navigation.Screen
11 |
12 | object ResUtils {
13 |
14 | fun Int.asString(context: Context, vararg formatArgs: Any = emptyArray()): String {
15 | return context.getString(this, formatArgs)
16 | }
17 |
18 | fun Screen.getIcon(selected: Boolean): ImageVector =
19 | if (selected) this.selectedIcon else this.baseIcon
20 |
21 | @Composable
22 | fun stringResourceListOf(
23 | @StringRes vararg ids: Int
24 | ): List = ids.map {
25 | stringResource(it)
26 | }
27 |
28 | @OptIn(ExperimentalComposeUiApi::class)
29 | @Composable
30 | fun pluralStringResource(
31 | @PluralsRes id: Int,
32 | count: Int,
33 | onZero: @Composable () -> String,
34 | ): String = if (count == 0) {
35 | onZero()
36 | } else {
37 | androidx.compose.ui.res.pluralStringResource(id, count, count)
38 | }
39 |
40 | @OptIn(ExperimentalComposeUiApi::class)
41 | @Composable
42 | fun pluralStringResource(
43 | @PluralsRes id: Int,
44 | count: Int,
45 | onZero: @Composable () -> String,
46 | vararg formatArgs: Any
47 | ): String = if (count == 0) {
48 | onZero()
49 | } else {
50 | androidx.compose.ui.res.pluralStringResource(id, count, formatArgs)
51 | }
52 |
53 |
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/compose/ColorUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.compose
2 |
3 | import androidx.annotation.FloatRange
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.graphics.toArgb
8 | import ru.tech.cookhelper.presentation.ui.theme.isDarkMode
9 | import androidx.core.graphics.ColorUtils as AndroidColorUtils
10 |
11 | object ColorUtils {
12 |
13 | fun Color.blend(
14 | color: Color,
15 | @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f
16 | ): Color = AndroidColorUtils.blendARGB(this.toArgb(), color.toArgb(), fraction).toColor()
17 |
18 | fun Color.darken(
19 | @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f
20 | ): Color = blend(color = Color.Black, fraction = fraction)
21 |
22 | @Composable
23 | fun Color.createSecondaryColor(
24 | @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f
25 | ): Color = if (isDarkMode()) lighten(fraction) else darken(fraction)
26 |
27 | @Composable
28 | fun Color.createInverseSecondaryColor(
29 | @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f
30 | ): Color = if (!isDarkMode()) lighten(fraction) else darken(fraction)
31 |
32 | fun Color.lighten(
33 | @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f
34 | ): Color = blend(color = Color.White, fraction = fraction)
35 |
36 | fun Int.toColor(): Color = Color(color = this)
37 |
38 | @Composable
39 | fun Color.harmonizeWithPrimary(
40 | @FloatRange(
41 | from = 0.0,
42 | to = 1.0
43 | ) fraction: Float = 0.2f
44 | ): Color = blend(MaterialTheme.colorScheme.primary, fraction)
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | #### OkHttp, Retrofit and Moshi
24 | -dontwarn okhttp3.**
25 | -dontwarn retrofit2.PlatformJava8
26 | -dontwarn okio.**
27 | -dontwarn javax.annotation.**
28 | -keepclasseswithmembers class * {
29 | @retrofit2.http.* ;
30 | }
31 | -keepclasseswithmembers class * {
32 | @com.squareup.moshi.* ;
33 | }
34 | -keep @com.squareup.moshi.JsonQualifier interface *
35 | -dontwarn org.jetbrains.annotations.**
36 | -keep class kotlin.Metadata { *; }
37 | -keepclassmembers class kotlin.Metadata {
38 | public ;
39 | }
40 |
41 | -keepclassmembers class * {
42 | @com.squareup.moshi.FromJson ;
43 | @com.squareup.moshi.ToJson ;
44 | }
45 |
46 | -keepnames @kotlin.Metadata class ru.tech.cookhelper.data.**
47 | -keep class ru.tech.cookhelper.data.** { *; }
48 | -keepclassmembers class ru.tech.cookhelper.data.** { *; }
49 |
50 | -keepnames @kotlin.Metadata class ru.tech.cookhelper.domain.**
51 | -keep class ru.tech.cookhelper.domain.** { *; }
52 | -keepclassmembers class ru.tech.cookhelper.domain.** { *; }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/all_images/components/AdaptiveVerticalGrid.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.all_images.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.grid.GridCells
6 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
7 | import androidx.compose.foundation.lazy.grid.items
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalConfiguration
12 | import androidx.compose.ui.unit.dp
13 | import ru.tech.cookhelper.domain.model.FileData
14 | import ru.tech.cookhelper.presentation.ui.utils.compose.PaddingUtils.addPadding
15 | import ru.tech.cookhelper.presentation.ui.utils.compose.widgets.Picture
16 |
17 | @Composable
18 | fun AdaptiveVerticalGrid(images: List, onImageClick: (id: String) -> Unit) {
19 | val configuration = LocalConfiguration.current
20 |
21 | val portrait = configuration.screenWidthDp < configuration.screenHeightDp
22 | val count = if (portrait) 3 else 4
23 |
24 | LazyVerticalGrid(
25 | columns = GridCells.Fixed(count),
26 | contentPadding = WindowInsets.navigationBars.asPaddingValues().addPadding(
27 | start = 2.dp, end = 2.dp, top = 4.dp, bottom = 80.dp
28 | ),
29 | modifier = Modifier.fillMaxSize()
30 | ) {
31 | items(images) { item ->
32 | Picture(
33 | model = item.link,
34 | modifier = Modifier
35 | .size(configuration.screenWidthDp.dp / count)
36 | .padding(horizontal = 2.dp, vertical = 2.dp)
37 | .clickable { onImageClick(item.id) },
38 | shape = RoundedCornerShape(4.dp),
39 | )
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/android/SystemBarUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.android
2 |
3 | import android.app.Activity
4 | import androidx.core.view.WindowInsetsCompat
5 | import androidx.core.view.WindowInsetsControllerCompat
6 |
7 | object SystemBarUtils {
8 |
9 | val Activity.isSystemBarsHidden: Boolean
10 | get() {
11 | return _isSystemBarsHidden
12 | }
13 |
14 | private var _isSystemBarsHidden = false
15 |
16 | val Activity.isNavigationBarsHidden: Boolean
17 | get() {
18 | return _isNavigationBarsHidden
19 | }
20 |
21 | private var _isNavigationBarsHidden = false
22 |
23 | fun Activity.hideSystemBars() = WindowInsetsControllerCompat(
24 | window,
25 | window.decorView
26 | ).let { controller ->
27 | controller.systemBarsBehavior =
28 | WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
29 | controller.hide(WindowInsetsCompat.Type.systemBars())
30 | _isSystemBarsHidden = true
31 | }
32 |
33 | fun Activity.hideNavigationBars() = WindowInsetsControllerCompat(
34 | window,
35 | window.decorView
36 | ).let { controller ->
37 | controller.systemBarsBehavior =
38 | WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
39 | controller.hide(WindowInsetsCompat.Type.navigationBars())
40 | _isNavigationBarsHidden = true
41 | }
42 |
43 | fun Activity.showNavigationBars() = WindowInsetsControllerCompat(
44 | window,
45 | window.decorView
46 | ).show(WindowInsetsCompat.Type.navigationBars()).also {
47 | _isNavigationBarsHidden = false
48 | }
49 |
50 | fun Activity.showSystemBars() = WindowInsetsControllerCompat(
51 | window,
52 | window.decorView
53 | ).show(WindowInsetsCompat.Type.systemBars()).also {
54 | _isSystemBarsHidden = false
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/app/components/MainModalDrawerHeader.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.app.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.unit.dp
14 | import ru.tech.cookhelper.domain.model.getLastAvatar
15 | import ru.tech.cookhelper.presentation.ui.utils.compose.widgets.Picture
16 |
17 | @Composable
18 | fun MainModalDrawerHeader(userState: UserState, onClick: () -> Unit) {
19 | Column(
20 | Modifier.clickable(
21 | interactionSource = MutableInteractionSource(),
22 | indication = null,
23 | onClick = onClick
24 | )
25 | ) {
26 | Picture(
27 | model = userState.user?.getLastAvatar(),
28 | modifier = Modifier
29 | .padding(start = 15.dp, top = 15.dp)
30 | .size(64.dp)
31 | )
32 | Spacer(Modifier.size(10.dp))
33 | Column(
34 | Modifier
35 | .padding(horizontal = 15.dp)
36 | ) {
37 | Text(
38 | userState.user?.let { "${it.name} ${it.surname}" }.toString(),
39 | style = MaterialTheme.typography.headlineSmall
40 | )
41 | Spacer(Modifier.weight(1f))
42 | Text(
43 | "@${userState.user?.nickname}",
44 | style = MaterialTheme.typography.bodyMedium,
45 | color = MaterialTheme.colorScheme.onSurfaceVariant
46 | )
47 | }
48 | Spacer(Modifier.size(30.dp))
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/compose/ScrollUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.compose
2 |
3 | import androidx.compose.foundation.ScrollState
4 | import androidx.compose.foundation.lazy.LazyListState
5 | import androidx.compose.runtime.*
6 |
7 | object ScrollUtils {
8 |
9 | /**
10 | * Returns whether the scrolling object is currently scrolling up.
11 | */
12 | @Composable
13 | fun ScrollState.isScrollingUp(): Boolean {
14 | var previousScrollOffset by remember(this) { mutableStateOf(value) }
15 | return remember(this) {
16 | derivedStateOf {
17 | (previousScrollOffset >= value).also {
18 | previousScrollOffset = value
19 | }
20 | }
21 | }.value
22 | }
23 |
24 | /**
25 | * Returns whether the lazy list is currently scrolling up.
26 | */
27 | @Composable
28 | fun LazyListState.isScrollingUp(): Boolean {
29 | var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) }
30 | var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
31 | return remember(this) {
32 | derivedStateOf {
33 | if (previousIndex != firstVisibleItemIndex) {
34 | previousIndex > firstVisibleItemIndex
35 | } else {
36 | previousScrollOffset >= firstVisibleItemScrollOffset
37 | }.also {
38 | previousIndex = firstVisibleItemIndex
39 | previousScrollOffset = firstVisibleItemScrollOffset
40 | }
41 | }
42 | }.value
43 | }
44 |
45 | @Composable
46 | fun LazyListState.isLastItemVisible(): Boolean {
47 | return remember(this) {
48 | derivedStateOf {
49 | layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
50 | }
51 | }.value
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/ui/utils/compose/TopAppBarUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.ui.utils.compose
2 |
3 | import androidx.compose.material3.*
4 | import androidx.compose.runtime.Composable
5 |
6 | object TopAppBarUtils {
7 |
8 | /**
9 | * Returns a [TopAppBarScrollBehavior]. A top app bar that is set up with this
10 | *
11 | * @param state the state object to be used to control or observe the top app bar's scroll
12 | * state. See [rememberTopAppBarState] for a state that is remembered across compositions.
13 | * @param canScroll a callback used to determine whether scroll events are to be
14 | * handled by this [EnterAlwaysScrollBehavior]
15 | */
16 | @OptIn(ExperimentalMaterial3Api::class)
17 | @Composable
18 | fun topAppBarScrollBehavior(
19 | scrollBehavior: ScrollBehavior = ScrollBehavior.Pinned,
20 | canScroll: () -> Boolean = { true },
21 | state: TopAppBarState = rememberTopAppBarState()
22 | ): TopAppBarScrollBehavior = when (scrollBehavior) {
23 | is ScrollBehavior.EnterAlways -> {
24 | TopAppBarDefaults.enterAlwaysScrollBehavior(
25 | state = state,
26 | canScroll = canScroll,
27 | snapAnimationSpec = scrollBehavior.snapAnimationSpec,
28 | flingAnimationSpec = scrollBehavior.flingAnimationSpec()
29 | )
30 | }
31 | is ScrollBehavior.ExitUntilCollapsed -> {
32 | TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
33 | state = state,
34 | canScroll = canScroll,
35 | snapAnimationSpec = scrollBehavior.snapAnimationSpec,
36 | flingAnimationSpec = scrollBehavior.flingAnimationSpec()
37 | )
38 | }
39 | is ScrollBehavior.Pinned -> {
40 | TopAppBarDefaults.pinnedScrollBehavior(
41 | state = state,
42 | canScroll = canScroll
43 | )
44 | }
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
41 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/remote/api/auth/AuthService.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.remote.api.auth
2 |
3 | import retrofit2.Call
4 | import retrofit2.http.*
5 | import ru.tech.cookhelper.data.remote.dto.UserDto
6 | import ru.tech.cookhelper.data.remote.utils.Response
7 |
8 | interface AuthService {
9 |
10 | @Multipart
11 | @POST("api/user/post/auth/")
12 | fun loginWith(
13 | @Part("login") login: String,
14 | @Part("password") password: String
15 | ): Call>
16 |
17 | @Multipart
18 | @POST("api/user/post/reg/")
19 | fun registerWith(
20 | @Part("name") name: String,
21 | @Part("surname") surname: String,
22 | @Part("nickname") nickname: String,
23 | @Part("email") email: String,
24 | @Part("password") password: String
25 | ): Call>
26 |
27 | @Multipart
28 | @POST("api/user/post/verify/")
29 | fun verifyEmail(
30 | @Part("code") code: String,
31 | @Part("token") token: String
32 | ): Call>
33 |
34 | @GET("api/user/get/verification/")
35 | suspend fun requestCode(
36 | @Query("token") token: String
37 | ): Result>
38 |
39 | @GET("api/user/get/recover-password/")
40 | suspend fun requestPasswordRestoreCode(
41 | @Query("login") login: String
42 | ): Result>
43 |
44 | @Multipart
45 | @POST("api/user/post/recover-password/")
46 | fun restorePasswordBy(
47 | @Part("login") login: String,
48 | @Part("code") code: String,
49 | @Part("password") password: String
50 | ): Call>
51 |
52 | @GET("api/user/get/nickname-availability/")
53 | suspend fun checkNicknameForAvailability(
54 | @Query("nickname") nickname: String
55 | ): Result>
56 |
57 | @GET("api/user/get/email-availability/")
58 | suspend fun checkEmailForAvailability(
59 | @Query("email") email: String
60 | ): Result>
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/fridge_screen/components/ProductItem.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.fridge_screen.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.shape.CircleShape
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.rounded.DeleteOutline
8 | import androidx.compose.material3.Icon
9 | import androidx.compose.material3.IconButton
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import ru.tech.cookhelper.domain.model.Product
17 |
18 | @Composable
19 | fun ProductItem(
20 | modifier: Modifier,
21 | product: Product,
22 | onDelete: () -> Unit
23 | ) {
24 | Row(
25 | modifier = modifier,
26 | verticalAlignment = Alignment.CenterVertically
27 | ) {
28 | Box(
29 | modifier = Modifier
30 | .size(36.dp)
31 | .background(
32 | color = MaterialTheme.colorScheme.secondaryContainer,
33 | shape = CircleShape
34 | ),
35 | contentAlignment = Alignment.Center
36 | ) {
37 | Icon(
38 | imageVector = product.getIcon(),
39 | contentDescription = null,
40 | tint = MaterialTheme.colorScheme.onSecondaryContainer
41 | )
42 | }
43 | Spacer(
44 | Modifier
45 | .weight(1f)
46 | .padding(end = 8.dp)
47 | )
48 | Text(text = product.title)
49 | Spacer(
50 | Modifier
51 | .weight(1f)
52 | .padding(end = 8.dp)
53 | )
54 | IconButton(onClick = onDelete) {
55 | Icon(Icons.Rounded.DeleteOutline, null)
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/data/local/database/TypeConverters.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.data.local.database
2 |
3 | import androidx.room.ProvidedTypeConverter
4 | import androidx.room.TypeConverter
5 | import com.squareup.moshi.Types
6 | import ru.tech.cookhelper.data.utils.JsonParser
7 | import ru.tech.cookhelper.domain.model.FileData
8 | import ru.tech.cookhelper.domain.model.Product
9 | import java.lang.reflect.Type
10 | import javax.inject.Inject
11 |
12 | @ProvidedTypeConverter
13 | class TypeConverters @Inject constructor(private val jsonParser: JsonParser) {
14 |
15 | private fun getListType(type: Type): Type {
16 | return Types.newParameterizedType(
17 | List::class.java, type
18 | )
19 | }
20 |
21 | @TypeConverter
22 | fun fromStringList(data: List): String {
23 | return jsonParser.toJson(
24 | data, getListType(String::class.java)
25 | ) ?: ""
26 | }
27 |
28 | @TypeConverter
29 | fun toStringList(data: String): List {
30 | return jsonParser.fromJson>(
31 | data, getListType(String::class.java)
32 | ) ?: emptyList()
33 | }
34 |
35 | @TypeConverter
36 | fun fromFileDataList(data: List): String {
37 | return jsonParser.toJson(
38 | data, getListType(FileData::class.java)
39 | ) ?: ""
40 | }
41 |
42 | @TypeConverter
43 | fun toFileDataList(data: String): List {
44 | return jsonParser.fromJson>(
45 | data, getListType(FileData::class.java)
46 | ) ?: emptyList()
47 | }
48 |
49 | @TypeConverter
50 | fun fromProductsList(data: List): String {
51 | return jsonParser.toJson(
52 | data, getListType(Product::class.java)
53 | ) ?: ""
54 | }
55 |
56 | @TypeConverter
57 | fun toProductsList(data: String): List {
58 | return jsonParser.fromJson>(
59 | data, getListType(Product::class.java)
60 | ) ?: emptyList()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/presentation/profile/components/AuthorBubble.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.presentation.profile.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.foundation.shape.CircleShape
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.unit.sp
17 | import ru.tech.cookhelper.domain.model.User
18 | import ru.tech.cookhelper.domain.model.getLastAvatar
19 | import ru.tech.cookhelper.presentation.ui.theme.Gray
20 | import ru.tech.cookhelper.presentation.ui.utils.compose.widgets.Picture
21 |
22 | @Composable
23 | fun AuthorBubble(
24 | modifier: Modifier = Modifier,
25 | pictureModifier: Modifier = Modifier.size(54.dp),
26 | author: User?,
27 | timestamp: String,
28 | onClick: () -> Unit
29 | ) {
30 | Row(
31 | modifier = modifier
32 | .clip(CircleShape)
33 | .clickable { onClick() },
34 | verticalAlignment = Alignment.CenterVertically
35 | ) {
36 | Picture(
37 | model = author.getLastAvatar(),
38 | modifier = pictureModifier
39 | )
40 | Spacer(Modifier.size(8.dp))
41 | Column {
42 | Text(
43 | text = "${author?.name} ${author?.surname}",
44 | fontWeight = FontWeight.SemiBold,
45 | fontSize = 16.sp
46 | )
47 | Spacer(Modifier.size(5.dp))
48 | Text(
49 | text = timestamp,
50 | color = Gray,
51 | fontSize = 14.sp
52 | )
53 | }
54 | Spacer(Modifier.size(8.dp))
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/tech/cookhelper/domain/repository/UserRepository.kt:
--------------------------------------------------------------------------------
1 | package ru.tech.cookhelper.domain.repository
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.tech.cookhelper.core.Action
5 | import ru.tech.cookhelper.domain.model.Post
6 | import ru.tech.cookhelper.domain.model.Recipe
7 | import ru.tech.cookhelper.domain.model.Topic
8 | import ru.tech.cookhelper.domain.model.User
9 | import java.io.File
10 |
11 | interface UserRepository {
12 |
13 | fun loginWith(login: String, password: String): Flow>
14 |
15 | fun registerWith(
16 | name: String,
17 | surname: String,
18 | nickname: String,
19 | email: String,
20 | password: String
21 | ): Flow>
22 |
23 | suspend fun requestPasswordRestoreCode(login: String): Action
24 |
25 | fun restorePasswordBy(
26 | login: String,
27 | code: String,
28 | newPassword: String
29 | ): Flow>
30 |
31 | suspend fun requestCode(token: String): Result
32 |
33 | fun checkCode(code: String, token: String): Flow>
34 |
35 | suspend fun cacheUser(user: User)
36 |
37 | fun getUser(): Flow
38 |
39 | suspend fun checkLoginForAvailability(login: String): Action
40 |
41 | suspend fun checkEmailForAvailability(email: String): Action
42 |
43 | suspend fun logOut()
44 |
45 | suspend fun loadUserById(id: String): User?
46 |
47 | fun getFeed(token: String): Flow>>
48 |
49 | fun stopAwaitingFeed()
50 |
51 | fun createPost(
52 | token: String,
53 | label: String,
54 | content: String,
55 | imageFile: File?,
56 | type: String
57 | ): Flow>
58 |
59 | fun createTopic(
60 | token: String,
61 | title: String,
62 | text: String,
63 | attachments: List>,
64 | tags: List