From b63eca8440d74d757752a55a50490905ec6f1e74 Mon Sep 17 00:00:00 2001 From: busya Date: Wed, 6 Aug 2025 11:28:28 +0300 Subject: [PATCH] initial commit --- .gitignore | 34 +++ app/build.gradle.kts | 99 +++++++ app/src/main/AndroidManifest.xml | 22 ++ .../java/com/homebox/lens/MainActivity.kt | 62 +++++ .../java/com/homebox/lens/MainApplication.kt | 28 ++ .../java/com/homebox/lens/di/AppModule.kt | 63 +++++ .../com/homebox/lens/navigation/NavGraph.kt | 30 +++ .../com/homebox/lens/navigation/Screen.kt | 15 ++ .../ui/screen/dashboard/DashboardScreen.kt | 94 +++++++ .../ui/screen/dashboard/DashboardViewModel.kt | 83 ++++++ .../java/com/homebox/lens/ui/theme/Color.kt | 16 ++ .../java/com/homebox/lens/ui/theme/Theme.kt | 64 +++++ .../com/homebox/lens/ui/theme/Typography.kt | 23 ++ app/src/main/res/values/colors.xml | 4 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 7 + build.gradle.kts | 13 + buildSrc/build.gradle.kts | 12 + buildSrc/src/main/java/Dependencies.kt | 100 +++++++ data/build.gradle.kts | 73 +++++ .../lens/data/api/HomeboxApiService.kt | 63 +++++ .../lens/data/api/dto/CustomFieldDto.kt | 30 +++ .../lens/data/api/dto/GroupStatisticsDto.kt | 32 +++ .../com/homebox/lens/data/api/dto/ImageDto.kt | 33 +++ .../lens/data/api/dto/ItemAttachmentDto.kt | 36 +++ .../lens/data/api/dto/ItemCreateDto.kt | 50 ++++ .../com/homebox/lens/data/api/dto/ItemDto.kt | 66 +++++ .../homebox/lens/data/api/dto/ItemOutDto.kt | 68 +++++ .../lens/data/api/dto/ItemSummaryDto.kt | 44 +++ .../lens/data/api/dto/ItemUpdateDto.kt | 52 ++++ .../com/homebox/lens/data/api/dto/LabelDto.kt | 20 ++ .../homebox/lens/data/api/dto/LabelOutDto.kt | 36 +++ .../homebox/lens/data/api/dto/LocationDto.kt | 31 +++ .../lens/data/api/dto/LocationOutCountDto.kt | 38 +++ .../lens/data/api/dto/LocationOutDto.kt | 36 +++ .../lens/data/api/dto/MaintenanceEntryDto.kt | 40 +++ .../lens/data/api/dto/PaginationDto.kt | 23 ++ .../lens/data/api/dto/PaginationResultDto.kt | 33 +++ .../lens/data/api/dto/StatisticsDto.kt | 23 ++ .../com/homebox/lens/data/db/Converters.kt | 26 ++ .../homebox/lens/data/db/HomeboxDatabase.kt | 41 +++ .../com/homebox/lens/data/db/dao/ItemDao.kt | 40 +++ .../com/homebox/lens/data/db/dao/LabelDao.kt | 27 ++ .../homebox/lens/data/db/dao/LocationDao.kt | 27 ++ .../homebox/lens/data/db/entity/ItemEntity.kt | 26 ++ .../lens/data/db/entity/ItemLabelCrossRef.kt | 19 ++ .../lens/data/db/entity/ItemWithLabels.kt | 29 ++ .../lens/data/db/entity/LabelEntity.kt | 20 ++ .../lens/data/db/entity/LocationEntity.kt | 20 ++ .../com/homebox/lens/data/di/ApiModule.kt | 77 ++++++ .../homebox/lens/data/di/DatabaseModule.kt | 50 ++++ .../homebox/lens/data/di/RepositoryModule.kt | 31 +++ .../data/repository/ItemRepositoryImpl.kt | 105 ++++++++ domain/build.gradle.kts | 25 ++ .../homebox/lens/domain/model/CustomField.kt | 18 ++ .../lens/domain/model/GroupStatistics.kt | 20 ++ .../com/homebox/lens/domain/model/Image.kt | 18 ++ .../com/homebox/lens/domain/model/Item.kt | 32 +++ .../lens/domain/model/ItemAttachment.kt | 24 ++ .../homebox/lens/domain/model/ItemCreate.kt | 38 +++ .../com/homebox/lens/domain/model/ItemOut.kt | 56 ++++ .../homebox/lens/domain/model/ItemSummary.kt | 32 +++ .../homebox/lens/domain/model/ItemUpdate.kt | 40 +++ .../com/homebox/lens/domain/model/Label.kt | 18 ++ .../com/homebox/lens/domain/model/LabelOut.kt | 24 ++ .../com/homebox/lens/domain/model/Location.kt | 18 ++ .../homebox/lens/domain/model/LocationOut.kt | 24 ++ .../lens/domain/model/LocationOutCount.kt | 26 ++ .../lens/domain/model/MaintenanceEntry.kt | 28 ++ .../lens/domain/model/PaginationResult.kt | 21 ++ .../com/homebox/lens/domain/model/Result.kt | 28 ++ .../homebox/lens/domain/model/Statistics.kt | 24 ++ .../lens/domain/repository/ItemRepository.kt | 25 ++ .../lens/domain/usecase/CreateItemUseCase.kt | 44 +++ .../lens/domain/usecase/DeleteItemUseCase.kt | 34 +++ .../domain/usecase/GetAllLabelsUseCase.kt | 29 ++ .../domain/usecase/GetAllLocationsUseCase.kt | 29 ++ .../domain/usecase/GetItemDetailsUseCase.kt | 43 +++ .../domain/usecase/GetStatisticsUseCase.kt | 29 ++ .../lens/domain/usecase/SearchItemsUseCase.kt | 32 +++ .../domain/usecase/SyncInventoryUseCase.kt | 38 +++ .../lens/domain/usecase/UpdateItemUseCase.kt | 45 ++++ gradle.properties | 17 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 251 ++++++++++++++++++ gradlew.bat | 94 +++++++ settings.gradle.kts | 25 ++ 88 files changed, 3392 insertions(+) create mode 100644 .gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/homebox/lens/MainActivity.kt create mode 100644 app/src/main/java/com/homebox/lens/MainApplication.kt create mode 100644 app/src/main/java/com/homebox/lens/di/AppModule.kt create mode 100644 app/src/main/java/com/homebox/lens/navigation/NavGraph.kt create mode 100644 app/src/main/java/com/homebox/lens/navigation/Screen.kt create mode 100644 app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardScreen.kt create mode 100644 app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardViewModel.kt create mode 100644 app/src/main/java/com/homebox/lens/ui/theme/Color.kt create mode 100644 app/src/main/java/com/homebox/lens/ui/theme/Theme.kt create mode 100644 app/src/main/java/com/homebox/lens/ui/theme/Typography.kt create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/java/Dependencies.kt create mode 100644 data/build.gradle.kts create mode 100644 data/src/main/java/com/homebox/lens/data/api/HomeboxApiService.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/CustomFieldDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/GroupStatisticsDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/ImageDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/ItemAttachmentDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/ItemDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/ItemSummaryDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/LabelDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/LabelOutDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/LocationDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/LocationOutCountDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/LocationOutDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/MaintenanceEntryDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/PaginationDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/PaginationResultDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/api/dto/StatisticsDto.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/Converters.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/HomeboxDatabase.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/dao/ItemDao.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/dao/LabelDao.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/dao/LocationDao.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/entity/ItemLabelCrossRef.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/entity/ItemWithLabels.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/entity/LabelEntity.kt create mode 100644 data/src/main/java/com/homebox/lens/data/db/entity/LocationEntity.kt create mode 100644 data/src/main/java/com/homebox/lens/data/di/ApiModule.kt create mode 100644 data/src/main/java/com/homebox/lens/data/di/DatabaseModule.kt create mode 100644 data/src/main/java/com/homebox/lens/data/di/RepositoryModule.kt create mode 100644 data/src/main/java/com/homebox/lens/data/repository/ItemRepositoryImpl.kt create mode 100644 domain/build.gradle.kts create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/CustomField.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/GroupStatistics.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/Image.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/Item.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/ItemAttachment.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/ItemCreate.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/ItemOut.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/ItemSummary.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/ItemUpdate.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/Label.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/LabelOut.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/Location.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/LocationOut.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/LocationOutCount.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/MaintenanceEntry.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/PaginationResult.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/Result.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/model/Statistics.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/repository/ItemRepository.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/CreateItemUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/DeleteItemUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/GetAllLabelsUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/GetAllLocationsUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/GetItemDetailsUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/GetStatisticsUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/SearchItemsUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/SyncInventoryUseCase.kt create mode 100644 domain/src/main/java/com/homebox/lens/domain/usecase/UpdateItemUseCase.kt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a3d57d --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Gradle +.gradle/ +build/ +!gradle/wrapper/gradle-wrapper.jar + +# Local configuration +local.properties + +# IDE files +.idea/ +*.iml +*.ipr +*.iws +.DS_Store + +# Keystore files +*.jks +*.keystore + +# Google Services +app/google-services.json + +# Captures +captures/ +*.apk +*.aab +output.json + +# Log files +*.log + +# Gemini files +GEMINI.md +tech_spec/ diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..4636843 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,99 @@ +// [FILE] app/build.gradle.kts +// [PURPOSE] Build script for the app module. + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.dagger.hilt.android") + id("kotlin-kapt") +} + +android { + namespace = "com.homebox.lens" + compileSdk = Versions.compileSdk + + defaultConfig { + applicationId = "com.homebox.lens" + minSdk = Versions.minSdk + targetSdk = Versions.targetSdk + versionCode = Versions.versionCode + versionName = Versions.versionName + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + buildConfig = true + } + composeOptions { + kotlinCompilerExtensionVersion = Versions.composeCompiler + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + // [MODULE_DEPENDENCY] Data module + implementation(project(":data")) + // [MODULE_DEPENDENCY] Domain module (transitively included via data, but explicit for clarity) + implementation(project(":domain")) + + // [DEPENDENCY] AndroidX + implementation(Libs.coreKtx) + implementation(Libs.lifecycleRuntime) + implementation(Libs.activityCompose) + + // [DEPENDENCY] Compose + implementation(platform(Libs.composeBom)) + implementation(Libs.composeUi) + implementation(Libs.composeUiGraphics) + implementation(Libs.composeUiToolingPreview) + implementation(Libs.composeMaterial3) + implementation(Libs.navigationCompose) + implementation(Libs.hiltNavigationCompose) + + // [DEPENDENCY] DI (Hilt) + implementation(Libs.hiltAndroid) + kapt(Libs.hiltCompiler) + + // [DEPENDENCY] Logging + implementation(Libs.timber) + + // [DEPENDENCY] Testing + testImplementation(Libs.junit) + androidTestImplementation(Libs.extJunit) + androidTestImplementation(Libs.espressoCore) + androidTestImplementation(platform(Libs.composeBom)) + androidTestImplementation(Libs.composeUiTestJunit4) + debugImplementation(Libs.composeUiTooling) + debugImplementation(Libs.composeUiTestManifest) +} + +kapt { + correctErrorTypes = true +} + +// [END_FILE_app/build.gradle.kts] diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a2ca449 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/app/src/main/java/com/homebox/lens/MainActivity.kt b/app/src/main/java/com/homebox/lens/MainActivity.kt new file mode 100644 index 0000000..30cf331 --- /dev/null +++ b/app/src/main/java/com/homebox/lens/MainActivity.kt @@ -0,0 +1,62 @@ +// [PACKAGE] com.homebox.lens +// [FILE] MainActivity.kt + +package com.homebox.lens + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.homebox.lens.navigation.NavGraph +import com.homebox.lens.ui.theme.HomeboxLensTheme +import dagger.hilt.android.AndroidEntryPoint + +// [CONTRACT] +/** + * [ENTITY: Activity('MainActivity')] + * [PURPOSE] Главная и единственная Activity в приложении. + */ +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + // [LIFECYCLE] + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + HomeboxLensTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + NavGraph() + } + } + } + } +} + +// [HELPER] +@Composable +fun Greeting(name: String, modifier: Modifier = Modifier) { + Text( + text = "Hello $name!", + modifier = modifier + ) +} + +// [PREVIEW] +@Preview(showBackground = true) +@Composable +fun GreetingPreview() { + HomeboxLensTheme { + Greeting("Android") + } +} + +// [END_FILE_MainActivity.kt] diff --git a/app/src/main/java/com/homebox/lens/MainApplication.kt b/app/src/main/java/com/homebox/lens/MainApplication.kt new file mode 100644 index 0000000..cb631d5 --- /dev/null +++ b/app/src/main/java/com/homebox/lens/MainApplication.kt @@ -0,0 +1,28 @@ +// [PACKAGE] com.homebox.lens +// [FILE] MainApplication.kt + +package com.homebox.lens + +import android.app.Application +import com.homebox.lens.BuildConfig +import dagger.hilt.android.HiltAndroidApp +import timber.log.Timber + +// [CONTRACT] +/** + * [ENTITY: Application('MainApplication')] + * [PURPOSE] Точка входа в приложение. Инициализирует Hilt и Timber. + */ +@HiltAndroidApp +class MainApplication : Application() { + // [LIFECYCLE] + override fun onCreate() { + super.onCreate() + // [ACTION] Initialize Timber for logging + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } + } +} + +// [END_FILE_MainApplication.kt] diff --git a/app/src/main/java/com/homebox/lens/di/AppModule.kt b/app/src/main/java/com/homebox/lens/di/AppModule.kt new file mode 100644 index 0000000..d1a0f50 --- /dev/null +++ b/app/src/main/java/com/homebox/lens/di/AppModule.kt @@ -0,0 +1,63 @@ +// [PACKAGE] com.homebox.lens.di +// [FILE] AppModule.kt +// [SEMANTICS] dependency_injection, hilt, configuration + +// [IMPORTS] +import com.homebox.lens.data.api.HomeboxApiService +import com.homebox.lens.data.repository.ItemRepositoryImpl +import com.homebox.lens.domain.repository.ItemRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +// [CORE-LOGIC] +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + + private const val BASE_URL = "https://homebox.fly.dev/api/" // Заглушка, заменить на реальный URL + + /** + * [CONTRACT] + * Предоставляет синглтон-экземпляр Retrofit. + */ + @Provides + @Singleton + fun provideRetrofit(): Retrofit { + // [PRECONDITION] BASE_URL должен быть валидным URL. + require(BASE_URL.startsWith("https://") || BASE_URL.startsWith("http://")) { + "[PRECONDITION_FAILED] BASE_URL must be a valid URL." + } + return Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + /** + * [CONTRACT] + * Предоставляет синглтон-экземпляр HomeboxApiService. + * @param retrofit Экземпляр Retrofit. + */ + @Provides + @Singleton + fun provideHomeboxApiService(retrofit: Retrofit): HomeboxApiService { + return retrofit.create(HomeboxApiService::class.java) + } + + /** + * [CONTRACT] + * Предоставляет реализацию ItemRepository. + * @param apiService Экземпляр HomeboxApiService. + */ + @Provides + @Singleton + fun provideItemRepository(apiService: HomeboxApiService): ItemRepository { + return ItemRepositoryImpl(apiService) + } +} +// [END_FILE_AppModule.kt] diff --git a/app/src/main/java/com/homebox/lens/navigation/NavGraph.kt b/app/src/main/java/com/homebox/lens/navigation/NavGraph.kt new file mode 100644 index 0000000..91e2ead --- /dev/null +++ b/app/src/main/java/com/homebox/lens/navigation/NavGraph.kt @@ -0,0 +1,30 @@ +// [PACKAGE] com.homebox.lens.navigation +// [FILE] NavGraph.kt +// [SEMANTICS] navigation, compose, nav_host + +// [IMPORTS] +import androidx.compose.runtime.Composable +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.homebox.lens.ui.screen.dashboard.DashboardScreen + +// [CORE-LOGIC] +/** + * [CONTRACT] + * Определяет граф навигации для приложения. + */ +@Composable +fun NavGraph() { + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = Screen.Dashboard.route + ) { + composable(route = Screen.Dashboard.route) { + DashboardScreen() + } + // TODO: Добавить остальные экраны в граф навигации + } +} +// [END_FILE_NavGraph.kt] \ No newline at end of file diff --git a/app/src/main/java/com/homebox/lens/navigation/Screen.kt b/app/src/main/java/com/homebox/lens/navigation/Screen.kt new file mode 100644 index 0000000..dfd1060 --- /dev/null +++ b/app/src/main/java/com/homebox/lens/navigation/Screen.kt @@ -0,0 +1,15 @@ +// [PACKAGE] com.homebox.lens.navigation +// [FILE] Screen.kt +// [SEMANTICS] navigation, routes, constants + +// [CORE-LOGIC] +/** + * [CONTRACT] + * Запечатанный класс для определения навигационных маршрутов в приложении. + * @property route Строковый идентификатор маршрута. + */ +sealed class Screen(val route: String) { + object Dashboard : Screen("dashboard_screen") + // TODO: Добавить остальные экраны по мере их создания +} +// [END_FILE_Screen.kt] diff --git a/app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardScreen.kt b/app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardScreen.kt new file mode 100644 index 0000000..44d96cf --- /dev/null +++ b/app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardScreen.kt @@ -0,0 +1,94 @@ +// [PACKAGE] com.homebox.lens.ui.screen.dashboard +// [FILE] DashboardScreen.kt +// [SEMANTICS] ui, screen, dashboard, compose + +// [IMPORTS] +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel + +// [CORE-LOGIC] +/** + * [CONTRACT] + * Главный Composable для экрана "Дэшборд". + * @param viewModel ViewModel для этого экрана. + */ +@Composable +fun DashboardScreen( + viewModel: DashboardViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + + Scaffold { paddingValues -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + when (val state = uiState) { + is DashboardUiState.Loading -> { + // [UI-ACTION] Показываем индикатор загрузки + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } + is DashboardUiState.Error -> { + // [UI-ACTION] Показываем сообщение об ошибке + Text( + text = "Error: ${state.message}", + modifier = Modifier.align(Alignment.Center) + ) + } + is DashboardUiState.Success -> { + // [UI-ACTION] Отображаем основной контент + DashboardContent(state) + } + } + } + } +} + +/** + * [CONTRACT] + * Composable для отображения успешного состояния дэшборда. + * @param state Состояние UI с данными. + */ +@Composable +fun DashboardContent(state: DashboardUiState.Success) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // [UI-COMPONENT] Статистика + Text(text = "Statistics:") + Text(text = " Items: ${state.statistics.items}") + Text(text = " Locations: ${state.statistics.locations}") + Text(text = " Labels: ${state.statistics.labels}") + Text(text = " Total Value: ${state.statistics.totalValue}") + + // [UI-COMPONENT] Локации + Text(text = "Locations:") + state.locations.forEach { location -> + Text(text = " - ${location.name} (${location.itemCount})") + } + + // [UI-COMPONENT] Метки + Text(text = "Labels:") + state.labels.forEach { label -> + Text(text = " - ${label.name}") + } + } +} +// [END_FILE_DashboardScreen.kt] \ No newline at end of file diff --git a/app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardViewModel.kt b/app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardViewModel.kt new file mode 100644 index 0000000..67a7c4b --- /dev/null +++ b/app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardViewModel.kt @@ -0,0 +1,83 @@ +// [PACKAGE] com.homebox.lens.ui.screen.dashboard +// [FILE] DashboardViewModel.kt +// [SEMANTICS] view_model, dashboard, state_management + +// [IMPORTS] +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.homebox.lens.domain.model.GroupStatistics +import com.homebox.lens.domain.model.LabelOut +import com.homebox.lens.domain.model.LocationOutCount +import com.homebox.lens.domain.usecase.GetAllLabelsUseCase +import com.homebox.lens.domain.usecase.GetAllLocationsUseCase +import com.homebox.lens.domain.usecase.GetStatisticsUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +// [CORE-LOGIC] +/** + * [CONTRACT] + * ViewModel для экрана "Дэшборд". + * @param getStatisticsUseCase Use case для получения статистики. + * @param getAllLocationsUseCase Use case для получения местоположений. + * @param getAllLabelsUseCase Use case для получения меток. + */ +@HiltViewModel +class DashboardViewModel @Inject constructor( + private val getStatisticsUseCase: GetStatisticsUseCase, + private val getAllLocationsUseCase: GetAllLocationsUseCase, + private val getAllLabelsUseCase: GetAllLabelsUseCase +) : ViewModel() { + + // [STATE] UI State + private val _uiState = MutableStateFlow(DashboardUiState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + // [ACTION] Загрузка всех данных при инициализации. + loadDashboardData() + } + + /** + * [CONTRACT] + * Загружает все необходимые для экрана данные. + * @sideeffect Обновляет _uiState. + */ + fun loadDashboardData() { + viewModelScope.launch { + _uiState.value = DashboardUiState.Loading + try { + val statistics = getStatisticsUseCase() + val locations = getAllLocationsUseCase() + val labels = getAllLabelsUseCase() + + _uiState.value = DashboardUiState.Success( + statistics = statistics, + locations = locations, + labels = labels + ) + } catch (e: Exception) { + _uiState.value = DashboardUiState.Error(e.message ?: "Unknown error") + } + } + } +} + +/** + * [CONTRACT] + * Запечатанный интерфейс для представления состояний UI дэшборда. + */ +sealed interface DashboardUiState { + data class Success( + val statistics: GroupStatistics, + val locations: List, + val labels: List + ) : DashboardUiState + data class Error(val message: String) : DashboardUiState + object Loading : DashboardUiState +} +// [END_FILE_DashboardViewModel.kt] \ No newline at end of file diff --git a/app/src/main/java/com/homebox/lens/ui/theme/Color.kt b/app/src/main/java/com/homebox/lens/ui/theme/Color.kt new file mode 100644 index 0000000..4618929 --- /dev/null +++ b/app/src/main/java/com/homebox/lens/ui/theme/Color.kt @@ -0,0 +1,16 @@ +// [PACKAGE] com.homebox.lens.ui.theme +// [FILE] Color.kt + +package com.homebox.lens.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) + +// [END_FILE_Color.kt] diff --git a/app/src/main/java/com/homebox/lens/ui/theme/Theme.kt b/app/src/main/java/com/homebox/lens/ui/theme/Theme.kt new file mode 100644 index 0000000..897e3d4 --- /dev/null +++ b/app/src/main/java/com/homebox/lens/ui/theme/Theme.kt @@ -0,0 +1,64 @@ +// [PACKAGE] com.homebox.lens.ui.theme +// [FILE] Theme.kt + +package com.homebox.lens.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 +) + +@Composable +fun HomeboxLensTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} +// [END_FILE_Theme.kt] diff --git a/app/src/main/java/com/homebox/lens/ui/theme/Typography.kt b/app/src/main/java/com/homebox/lens/ui/theme/Typography.kt new file mode 100644 index 0000000..b737dae --- /dev/null +++ b/app/src/main/java/com/homebox/lens/ui/theme/Typography.kt @@ -0,0 +1,23 @@ +// [PACKAGE] com.homebox.lens.ui.theme +// [FILE] Typography.kt + +package com.homebox.lens.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) + +// [END_FILE_Typography.kt] diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..b97c64b --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #FF3700B3 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..a66be8f --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Homebox Lens + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..1fe5c04 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..65fbedd --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,13 @@ +// [FILE] build.gradle.kts +// [PURPOSE] Root build file for the project, configures plugins for all modules. + +plugins { + // [PLUGIN] Android Application plugin + id("com.android.application") version "8.11.0" apply false + // [PLUGIN] Kotlin Android plugin + id("org.jetbrains.kotlin.android") version "1.9.22" apply false + // [PLUGIN] Hilt Android plugin + id("com.google.dagger.hilt.android") version "2.48.1" apply false +} + +// [END_FILE_build.gradle.kts] diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..2e7f0ff --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,12 @@ +// [FILE] buildSrc/build.gradle.kts +// [PURPOSE] Build file for the buildSrc module, enabling access to Gradle APIs. + +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} + +// [END_FILE_buildSrc/build.gradle.kts] \ No newline at end of file diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt new file mode 100644 index 0000000..0997c84 --- /dev/null +++ b/buildSrc/src/main/java/Dependencies.kt @@ -0,0 +1,100 @@ +// [FILE] Dependencies.kt +// [PURPOSE] Centralized dependency management for the entire project. + +object Versions { + // Build + const val compileSdk = 34 + const val minSdk = 26 + const val targetSdk = 34 + const val versionCode = 1 + const val versionName = "1.0" + + // Kotlin + const val kotlin = "1.9.22" + const val coroutines = "1.7.3" + + // Jetpack Compose + const val composeCompiler = "1.5.8" + const val composeBom = "2023.10.01" + const val activityCompose = "1.8.2" + const val navigationCompose = "2.7.6" + const val hiltNavigationCompose = "1.1.0" + + // AndroidX + const val coreKtx = "1.12.0" + const val lifecycle = "2.6.2" + const val appcompat = "1.6.1" + + // Networking + const val retrofit = "2.9.0" + const val okhttp = "4.12.0" + const val moshi = "1.15.0" + + // Database + const val room = "2.6.1" + + // DI + const val hilt = "2.48.1" + const val hiltCompiler = "1.1.0" + + // Logging + const val timber = "5.0.1" + + // Testing + const val junit = "4.13.2" + const val extJunit = "1.1.5" + const val espresso = "3.5.1" +} + +object Libs { + // Kotlin + const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}" + const val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}" + + // AndroidX + const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx}" + const val lifecycleRuntime = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}" + const val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}" + + // Compose + const val composeBom = "androidx.compose:compose-bom:${Versions.composeBom}" + const val composeUi = "androidx.compose.ui:ui" + const val composeUiGraphics = "androidx.compose.ui:ui-graphics" + const val composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview" + const val composeMaterial3 = "androidx.compose.material3:material3" + const val activityCompose = "androidx.activity:activity-compose:${Versions.activityCompose}" + const val navigationCompose = "androidx.navigation:navigation-compose:${Versions.navigationCompose}" + const val hiltNavigationCompose = "androidx.hilt:hilt-navigation-compose:${Versions.hiltNavigationCompose}" + + // Networking (Retrofit, OkHttp, Moshi) + const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}" + const val converterMoshi = "com.squareup.retrofit2:converter-moshi:${Versions.retrofit}" + const val converterGson = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}" + const val okhttp = "com.squareup.okhttp3:okhttp:${Versions.okhttp}" + const val okhttpLoggingInterceptor = "com.squareup.okhttp3:logging-interceptor:${Versions.okhttp}" + const val moshi = "com.squareup.moshi:moshi:${Versions.moshi}" + const val moshiKotlin = "com.squareup.moshi:moshi-kotlin:${Versions.moshi}" + const val moshiCodegen = "com.squareup.moshi:moshi-kotlin-codegen:${Versions.moshi}" + + // Database (Room) + const val roomRuntime = "androidx.room:room-runtime:${Versions.room}" + const val roomKtx = "androidx.room:room-ktx:${Versions.room}" + const val roomCompiler = "androidx.room:room-compiler:${Versions.room}" + + // Dependency Injection (Hilt) + const val hiltAndroid = "com.google.dagger:hilt-android:${Versions.hilt}" + const val hiltCompiler = "com.google.dagger:hilt-android-compiler:${Versions.hilt}" + + // Logging + const val timber = "com.jakewharton.timber:timber:${Versions.timber}" + + // Testing + const val junit = "junit:junit:${Versions.junit}" + const val extJunit = "androidx.test.ext:junit:${Versions.extJunit}" + const val espressoCore = "androidx.test.espresso:espresso-core:${Versions.espresso}" + const val composeUiTestJunit4 = "androidx.compose.ui:ui-test-junit4" + const val composeUiTooling = "androidx.compose.ui:ui-tooling" + const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest" +} + +// [END_FILE_Dependencies.kt] diff --git a/data/build.gradle.kts b/data/build.gradle.kts new file mode 100644 index 0000000..427a461 --- /dev/null +++ b/data/build.gradle.kts @@ -0,0 +1,73 @@ +// [FILE] data/build.gradle.kts +// [PURPOSE] Build script for the data module. + +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") + id("com.google.dagger.hilt.android") + id("kotlin-kapt") +} + +android { + namespace = "com.homebox.lens.data" + compileSdk = Versions.compileSdk + + defaultConfig { + minSdk = Versions.minSdk + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + // [MODULE_DEPENDENCY] Domain module + implementation(project(":domain")) + + // [DEPENDENCY] AndroidX + implementation(Libs.coreKtx) + + // [DEPENDENCY] Coroutines + implementation(Libs.coroutinesCore) + + // [DEPENDENCY] Networking (Retrofit, Gson) + implementation(Libs.retrofit) + implementation(Libs.converterGson) + implementation(Libs.okhttp) + implementation(Libs.okhttpLoggingInterceptor) + + // [DEPENDENCY] Database (Room) + implementation(Libs.roomRuntime) + implementation(Libs.roomKtx) + kapt(Libs.roomCompiler) + + // [DEPENDENCY] DI (Hilt) + implementation(Libs.hiltAndroid) + kapt(Libs.hiltCompiler) + + // [DEPENDENCY] Testing + testImplementation(Libs.junit) + androidTestImplementation(Libs.extJunit) + androidTestImplementation(Libs.espressoCore) +} + +kapt { + correctErrorTypes = true +} + +// [END_FILE_data/build.gradle.kts] diff --git a/data/src/main/java/com/homebox/lens/data/api/HomeboxApiService.kt b/data/src/main/java/com/homebox/lens/data/api/HomeboxApiService.kt new file mode 100644 index 0000000..f52c669 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/HomeboxApiService.kt @@ -0,0 +1,63 @@ +// [PACKAGE] com.homebox.lens.data.api +// [FILE] HomeboxApiService.kt + +package com.homebox.lens.data.api + +import com.homebox.lens.data.api.dto.GroupStatisticsDto +import com.homebox.lens.data.api.dto.ItemCreateDto +import com.homebox.lens.data.api.dto.ItemOutDto +import com.homebox.lens.data.api.dto.ItemSummaryDto +import com.homebox.lens.data.api.dto.ItemUpdateDto +import com.homebox.lens.data.api.dto.LabelOutDto +import com.homebox.lens.data.api.dto.LocationOutCountDto +import com.homebox.lens.data.api.dto.PaginationResultDto +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path +import retrofit2.http.Query + +// [CONTRACT] +/** + * [ENTITY: Interface('HomeboxApiService')] + * [PURPOSE] Определяет эндпоинты для взаимодействия с Homebox API, используя DTO. + */ +interface HomeboxApiService { + + // [ENDPOINT] Items + @GET("v1/items") + suspend fun getItems( + @Query("q") query: String? = null, + @Query("page") page: Int? = null, + @Query("pageSize") pageSize: Int? = null + ): PaginationResultDto + + @POST("v1/items") + suspend fun createItem(@Body item: ItemCreateDto): ItemSummaryDto + + @GET("v1/items/{id}") + suspend fun getItem(@Path("id") itemId: String): ItemOutDto + + @PUT("v1/items/{id}") + suspend fun updateItem(@Path("id") itemId: String, @Body item: ItemUpdateDto): ItemOutDto + + @DELETE("v1/items/{id}") + suspend fun deleteItem(@Path("id") itemId: String): Response + + // [ENDPOINT] Locations + @GET("v1/locations") + suspend fun getLocations(): List + + // [ENDPOINT] Labels + @GET("v1/labels") + suspend fun getLabels(): List + + // [ENDPOINT] Statistics + @GET("v1/groups/statistics") + suspend fun getStatistics(): GroupStatisticsDto +} + +// [END_FILE_HomeboxApiService.kt] diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/CustomFieldDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/CustomFieldDto.kt new file mode 100644 index 0000000..44bc4fd --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/CustomFieldDto.kt @@ -0,0 +1,30 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] CustomFieldDto.kt +// [SEMANTICS] data_transfer_object, custom_field + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.CustomField + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для кастомного поля. + */ +data class CustomFieldDto( + @SerializedName("name") val name: String, + @SerializedName("value") val value: String, + @SerializedName("type") val type: String +) + +/** + * [CONTRACT] + * Маппер из CustomFieldDto в доменную модель CustomField. + */ +fun CustomFieldDto.toDomain(): CustomField { + return CustomField( + name = this.name, + value = this.value, + type = this.type + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/GroupStatisticsDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/GroupStatisticsDto.kt new file mode 100644 index 0000000..aa32a0e --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/GroupStatisticsDto.kt @@ -0,0 +1,32 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] GroupStatisticsDto.kt +// [SEMANTICS] data_transfer_object, statistics + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.GroupStatistics + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для статистики. + */ +data class GroupStatisticsDto( + @SerializedName("items") val items: Int, + @SerializedName("labels") val labels: Int, + @SerializedName("locations") val locations: Int, + @SerializedName("totalValue") val totalValue: Double +) + +/** + * [CONTRACT] + * Маппер из GroupStatisticsDto в доменную модель GroupStatistics. + */ +fun GroupStatisticsDto.toDomain(): GroupStatistics { + return GroupStatistics( + items = this.items, + labels = this.labels, + locations = this.locations, + totalValue = this.totalValue + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ImageDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ImageDto.kt new file mode 100644 index 0000000..2de2cd3 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ImageDto.kt @@ -0,0 +1,33 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] ImageDto.kt +// [SEMANTICS] data_transfer_object, image + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.Image + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для изображения. + * @property id Уникальный идентификатор. + * @property path Путь к файлу. + * @property isPrimary Является ли основным. + */ +data class ImageDto( + @SerializedName("id") val id: String, + @SerializedName("path") val path: String, + @SerializedName("isPrimary") val isPrimary: Boolean +) + +/** + * [CONTRACT] + * Маппер из ImageDto в доменную модель Image. + */ +fun ImageDto.toDomain(): Image { + return Image( + id = this.id, + path = this.path, + isPrimary = this.isPrimary + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemAttachmentDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemAttachmentDto.kt new file mode 100644 index 0000000..f176991 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemAttachmentDto.kt @@ -0,0 +1,36 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] ItemAttachmentDto.kt +// [SEMANTICS] data_transfer_object, attachment + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.ItemAttachment + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для вложения. + */ +data class ItemAttachmentDto( + @SerializedName("id") val id: String, + @SerializedName("name") val name: String, + @SerializedName("path") val path: String, + @SerializedName("type") val type: String, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("updatedAt") val updatedAt: String +) + +/** + * [CONTRACT] + * Маппер из ItemAttachmentDto в доменную модель ItemAttachment. + */ +fun ItemAttachmentDto.toDomain(): ItemAttachment { + return ItemAttachment( + id = this.id, + name = this.name, + path = this.path, + type = this.type, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt new file mode 100644 index 0000000..49c34ea --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt @@ -0,0 +1,50 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] ItemCreateDto.kt +// [SEMANTICS] data_transfer_object, item_creation + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.ItemCreate + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для создания вещи. + */ +data class ItemCreateDto( + @SerializedName("name") val name: String, + @SerializedName("assetId") val assetId: String?, + @SerializedName("description") val description: String?, + @SerializedName("notes") val notes: String?, + @SerializedName("serialNumber") val serialNumber: String?, + @SerializedName("quantity") val quantity: Int?, + @SerializedName("value") val value: Double?, + @SerializedName("purchasePrice") val purchasePrice: Double?, + @SerializedName("purchaseDate") val purchaseDate: String?, + @SerializedName("warrantyUntil") val warrantyUntil: String?, + @SerializedName("locationId") val locationId: String?, + @SerializedName("parentId") val parentId: String?, + @SerializedName("labelIds") val labelIds: List? +) + +/** + * [CONTRACT] + * Маппер из доменной модели ItemCreate в ItemCreateDto. + */ +fun ItemCreate.toDto(): ItemCreateDto { + return ItemCreateDto( + name = this.name, + assetId = this.assetId, + description = this.description, + notes = this.notes, + serialNumber = this.serialNumber, + quantity = this.quantity, + value = this.value, + purchasePrice = this.purchasePrice, + purchaseDate = this.purchaseDate, + warrantyUntil = this.warrantyUntil, + locationId = this.locationId, + parentId = this.parentId, + labelIds = this.labelIds + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemDto.kt new file mode 100644 index 0000000..a0901c0 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemDto.kt @@ -0,0 +1,66 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] ItemDto.kt + +package com.homebox.lens.data.api.dto + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +// [CONTRACT] +/** + * [ENTITY: DataClass('ItemOut')] + * [PURPOSE] DTO для полной информации о вещи (GET /v1/items/{id}). + */ +@JsonClass(generateAdapter = true) +data class ItemOut( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "description") val description: String?, + @Json(name = "image") val image: String?, + @Json(name = "location") val location: LocationOut?, + @Json(name = "labels") val labels: List, + @Json(name = "value") val value: BigDecimal?, + @Json(name = "createdAt") val createdAt: String? +) + +/** + * [ENTITY: DataClass('ItemSummary')] + * [PURPOSE] DTO для краткой информации о вещи в списках (GET /v1/items). + */ +@JsonClass(generateAdapter = true) +data class ItemSummary( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "image") val image: String?, + @Json(name = "location") val location: LocationOut?, + @Json(name = "createdAt") val createdAt: String? +) + +/** + * [ENTITY: DataClass('ItemCreate')] + * [PURPOSE] DTO для создания новой вещи (POST /v1/items). + */ +@JsonClass(generateAdapter = true) +data class ItemCreate( + @Json(name = "name") val name: String, + @Json(name = "description") val description: String?, + @Json(name = "locationId") val locationId: String?, + @Json(name = "labelIds") val labelIds: List?, + @Json(name = "value") val value: BigDecimal? +) + +/** + * [ENTITY: DataClass('ItemUpdate')] + * [PURPOSE] DTO для обновления вещи (PUT /v1/items/{id}). + */ +@JsonClass(generateAdapter = true) +data class ItemUpdate( + @Json(name = "name") val name: String, + @Json(name = "description") val description: String?, + @Json(name = "locationId") val locationId: String?, + @Json(name = "labelIds") val labelIds: List?, + @Json(name = "value") val value: BigDecimal? +) + +// [END_FILE_ItemDto.kt] diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt new file mode 100644 index 0000000..e60db1e --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt @@ -0,0 +1,68 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] ItemOutDto.kt +// [SEMANTICS] data_transfer_object, item_detailed + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.ItemOut + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для полной модели вещи. + */ +data class ItemOutDto( + @SerializedName("id") val id: String, + @SerializedName("name") val name: String, + @SerializedName("assetId") val assetId: String?, + @SerializedName("description") val description: String?, + @SerializedName("notes") val notes: String?, + @SerializedName("serialNumber") val serialNumber: String?, + @SerializedName("quantity") val quantity: Int, + @SerializedName("isArchived") val isArchived: Boolean, + @SerializedName("value") val value: Double, + @SerializedName("purchasePrice") val purchasePrice: Double?, + @SerializedName("purchaseDate") val purchaseDate: String?, + @SerializedName("warrantyUntil") val warrantyUntil: String?, + @SerializedName("location") val location: LocationOutDto?, + @SerializedName("parent") val parent: ItemSummaryDto?, + @SerializedName("children") val children: List, + @SerializedName("labels") val labels: List, + @SerializedName("attachments") val attachments: List, + @SerializedName("images") val images: List, + @SerializedName("fields") val fields: List, + @SerializedName("maintenance") val maintenance: List, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("updatedAt") val updatedAt: String +) + +/** + * [CONTRACT] + * Маппер из ItemOutDto в доменную модель ItemOut. + */ +fun ItemOutDto.toDomain(): ItemOut { + return ItemOut( + id = this.id, + name = this.name, + assetId = this.assetId, + description = this.description, + notes = this.notes, + serialNumber = this.serialNumber, + quantity = this.quantity, + isArchived = this.isArchived, + value = this.value, + purchasePrice = this.purchasePrice, + purchaseDate = this.purchaseDate, + warrantyUntil = this.warrantyUntil, + location = this.location?.toDomain(), + parent = this.parent?.toDomain(), + children = this.children.map { it.toDomain() }, + labels = this.labels.map { it.toDomain() }, + attachments = this.attachments.map { it.toDomain() }, + images = this.images.map { it.toDomain() }, + fields = this.fields.map { it.toDomain() }, + maintenance = this.maintenance.map { it.toDomain() }, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemSummaryDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemSummaryDto.kt new file mode 100644 index 0000000..4bcf45b --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemSummaryDto.kt @@ -0,0 +1,44 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] ItemSummaryDto.kt +// [SEMANTICS] data_transfer_object, item_summary + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.ItemSummary + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для сокращенной модели вещи. + */ +data class ItemSummaryDto( + @SerializedName("id") val id: String, + @SerializedName("name") val name: String, + @SerializedName("assetId") val assetId: String?, + @SerializedName("image") val image: ImageDto?, + @SerializedName("isArchived") val isArchived: Boolean, + @SerializedName("labels") val labels: List, + @SerializedName("location") val location: LocationOutDto?, + @SerializedName("value") val value: Double, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("updatedAt") val updatedAt: String +) + +/** + * [CONTRACT] + * Маппер из ItemSummaryDto в доменную модель ItemSummary. + */ +fun ItemSummaryDto.toDomain(): ItemSummary { + return ItemSummary( + id = this.id, + name = this.name, + assetId = this.assetId, + image = this.image?.toDomain(), + isArchived = this.isArchived, + labels = this.labels.map { it.toDomain() }, + location = this.location?.toDomain(), + value = this.value, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt new file mode 100644 index 0000000..86d44af --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt @@ -0,0 +1,52 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] ItemUpdateDto.kt +// [SEMANTICS] data_transfer_object, item_update + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.ItemUpdate + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для обновления вещи. + */ +data class ItemUpdateDto( + @SerializedName("name") val name: String?, + @SerializedName("assetId") val assetId: String?, + @SerializedName("description") val description: String?, + @SerializedName("notes") val notes: String?, + @SerializedName("serialNumber") val serialNumber: String?, + @SerializedName("quantity") val quantity: Int?, + @SerializedName("isArchived") val isArchived: Boolean?, + @SerializedName("value") val value: Double?, + @SerializedName("purchasePrice") val purchasePrice: Double?, + @SerializedName("purchaseDate") val purchaseDate: String?, + @SerializedName("warrantyUntil") val warrantyUntil: String?, + @SerializedName("locationId") val locationId: String?, + @SerializedName("parentId") val parentId: String?, + @SerializedName("labelIds") val labelIds: List? +) + +/** + * [CONTRACT] + * Маппер из доменной модели ItemUpdate в ItemUpdateDto. + */ +fun ItemUpdate.toDto(): ItemUpdateDto { + return ItemUpdateDto( + name = this.name, + assetId = this.assetId, + description = this.description, + notes = this.notes, + serialNumber = this.serialNumber, + quantity = this.quantity, + isArchived = this.isArchived, + value = this.value, + purchasePrice = this.purchasePrice, + purchaseDate = this.purchaseDate, + warrantyUntil = this.warrantyUntil, + locationId = this.locationId, + parentId = this.parentId, + labelIds = this.labelIds + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/LabelDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/LabelDto.kt new file mode 100644 index 0000000..5e3aa28 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/LabelDto.kt @@ -0,0 +1,20 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] LabelDto.kt + +package com.homebox.lens.data.api.dto + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +// [CONTRACT] +/** + * [ENTITY: DataClass('LabelOut')] + * [PURPOSE] DTO для информации о метке. + */ +@JsonClass(generateAdapter = true) +data class LabelOut( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String +) + +// [END_FILE_LabelDto.kt] diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/LabelOutDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/LabelOutDto.kt new file mode 100644 index 0000000..e7c4886 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/LabelOutDto.kt @@ -0,0 +1,36 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] LabelOutDto.kt +// [SEMANTICS] data_transfer_object, label + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.LabelOut + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для метки. + */ +data class LabelOutDto( + @SerializedName("id") val id: String, + @SerializedName("name") val name: String, + @SerializedName("color") val color: String, + @SerializedName("isArchived") val isArchived: Boolean, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("updatedAt") val updatedAt: String +) + +/** + * [CONTRACT] + * Маппер из LabelOutDto в доменную модель LabelOut. + */ +fun LabelOutDto.toDomain(): LabelOut { + return LabelOut( + id = this.id, + name = this.name, + color = this.color, + isArchived = this.isArchived, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/LocationDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/LocationDto.kt new file mode 100644 index 0000000..7ce4676 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/LocationDto.kt @@ -0,0 +1,31 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] LocationDto.kt + +package com.homebox.lens.data.api.dto + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +// [CONTRACT] +/** + * [ENTITY: DataClass('LocationOut')] + * [PURPOSE] DTO для информации о местоположении. + */ +@JsonClass(generateAdapter = true) +data class LocationOut( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String +) + +/** + * [ENTITY: DataClass('LocationOutCount')] + * [PURPOSE] DTO для информации о местоположении со счетчиком вещей. + */ +@JsonClass(generateAdapter = true) +data class LocationOutCount( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "itemCount") val itemCount: Int +) + +// [END_FILE_LocationDto.kt] diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/LocationOutCountDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/LocationOutCountDto.kt new file mode 100644 index 0000000..5ce0b76 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/LocationOutCountDto.kt @@ -0,0 +1,38 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] LocationOutCountDto.kt +// [SEMANTICS] data_transfer_object, location, count + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.LocationOutCount + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для местоположения со счетчиком. + */ +data class LocationOutCountDto( + @SerializedName("id") val id: String, + @SerializedName("name") val name: String, + @SerializedName("color") val color: String, + @SerializedName("isArchived") val isArchived: Boolean, + @SerializedName("itemCount") val itemCount: Int, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("updatedAt") val updatedAt: String +) + +/** + * [CONTRACT] + * Маппер из LocationOutCountDto в доменную модель LocationOutCount. + */ +fun LocationOutCountDto.toDomain(): LocationOutCount { + return LocationOutCount( + id = this.id, + name = this.name, + color = this.color, + isArchived = this.isArchived, + itemCount = this.itemCount, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/LocationOutDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/LocationOutDto.kt new file mode 100644 index 0000000..2ce5922 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/LocationOutDto.kt @@ -0,0 +1,36 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] LocationOutDto.kt +// [SEMANTICS] data_transfer_object, location + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.LocationOut + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для местоположения. + */ +data class LocationOutDto( + @SerializedName("id") val id: String, + @SerializedName("name") val name: String, + @SerializedName("color") val color: String, + @SerializedName("isArchived") val isArchived: Boolean, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("updatedAt") val updatedAt: String +) + +/** + * [CONTRACT] + * Маппер из LocationOutDto в доменную модель LocationOut. + */ +fun LocationOutDto.toDomain(): LocationOut { + return LocationOut( + id = this.id, + name = this.name, + color = this.color, + isArchived = this.isArchived, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/MaintenanceEntryDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/MaintenanceEntryDto.kt new file mode 100644 index 0000000..b948985 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/MaintenanceEntryDto.kt @@ -0,0 +1,40 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] MaintenanceEntryDto.kt +// [SEMANTICS] data_transfer_object, maintenance + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.MaintenanceEntry + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для записи об обслуживании. + */ +data class MaintenanceEntryDto( + @SerializedName("id") val id: String, + @SerializedName("itemId") val itemId: String, + @SerializedName("title") val title: String, + @SerializedName("details") val details: String?, + @SerializedName("dueAt") val dueAt: String?, + @SerializedName("completedAt") val completedAt: String?, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("updatedAt") val updatedAt: String +) + +/** + * [CONTRACT] + * Маппер из MaintenanceEntryDto в доменную модель MaintenanceEntry. + */ +fun MaintenanceEntryDto.toDomain(): MaintenanceEntry { + return MaintenanceEntry( + id = this.id, + itemId = this.itemId, + title = this.title, + details = this.details, + dueAt = this.dueAt, + completedAt = this.completedAt, + createdAt = this.createdAt, + updatedAt = this.updatedAt + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/PaginationDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/PaginationDto.kt new file mode 100644 index 0000000..ad03045 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/PaginationDto.kt @@ -0,0 +1,23 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] PaginationDto.kt + +package com.homebox.lens.data.api.dto + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +// [CONTRACT] +/** + * [ENTITY: DataClass('PaginationResult')] + * [PURPOSE] DTO для пагинированных результатов от API. + */ +@JsonClass(generateAdapter = true) +data class PaginationResult( + @Json(name = "items") val items: List, + @Json(name = "page") val page: Int, + @Json(name = "pages") val pages: Int, + @Json(name = "total") val total: Int, + @Json(name = "pageSize") val pageSize: Int +) + +// [END_FILE_PaginationDto.kt] diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/PaginationResultDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/PaginationResultDto.kt new file mode 100644 index 0000000..aedf37d --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/PaginationResultDto.kt @@ -0,0 +1,33 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] PaginationResultDto.kt +// [SEMANTICS] data_transfer_object, pagination + +// [IMPORTS] +import com.google.gson.annotations.SerializedName +import com.homebox.lens.domain.model.PaginationResult + +// [CORE-LOGIC] +/** + * [CONTRACT] + * DTO для постраничных результатов. + */ +data class PaginationResultDto( + @SerializedName("items") val items: List, + @SerializedName("page") val page: Int, + @SerializedName("pageSize") val pageSize: Int, + @SerializedName("total") val total: Int +) + +/** + * [CONTRACT] + * Маппер из PaginationResultDto в доменную модель PaginationResult. + * @param transform Функция для преобразования каждого элемента из DTO в доменную модель. + */ +fun PaginationResultDto.toDomain(transform: (T) -> R): PaginationResult { + return PaginationResult( + items = this.items.map(transform), + page = this.page, + pageSize = this.pageSize, + total = this.total + ) +} diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/StatisticsDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/StatisticsDto.kt new file mode 100644 index 0000000..95c13d6 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/api/dto/StatisticsDto.kt @@ -0,0 +1,23 @@ +// [PACKAGE] com.homebox.lens.data.api.dto +// [FILE] StatisticsDto.kt + +package com.homebox.lens.data.api.dto + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +// [CONTRACT] +/** + * [ENTITY: DataClass('GroupStatistics')] + * [PURPOSE] DTO для статистической информации. + */ +@JsonClass(generateAdapter = true) +data class GroupStatistics( + @Json(name = "totalValue") val totalValue: BigDecimal, + @Json(name = "totalItems") val totalItems: Int, + @Json(name = "locations") val locations: Int, + @Json(name = "labels") val labels: Int +) + +// [END_FILE_StatisticsDto.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/Converters.kt b/data/src/main/java/com/homebox/lens/data/db/Converters.kt new file mode 100644 index 0000000..5df4165 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/Converters.kt @@ -0,0 +1,26 @@ +// [PACKAGE] com.homebox.lens.data.db +// [FILE] Converters.kt + +package com.homebox.lens.data.db + +import androidx.room.TypeConverter +import java.math.BigDecimal + +// [CONTRACT] +/** + * [ENTITY: Class('Converters')] + * [PURPOSE] Предоставляет TypeConverters для Room для типов, не поддерживаемых по умолчанию. + */ +class Converters { + @TypeConverter + fun fromString(value: String?): BigDecimal? { + return value?.let { BigDecimal(it) } + } + + @TypeConverter + fun bigDecimalToString(bigDecimal: BigDecimal?): String? { + return bigDecimal?.toPlainString() + } +} + +// [END_FILE_Converters.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/HomeboxDatabase.kt b/data/src/main/java/com/homebox/lens/data/db/HomeboxDatabase.kt new file mode 100644 index 0000000..2fdeee4 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/HomeboxDatabase.kt @@ -0,0 +1,41 @@ +// [PACKAGE] com.homebox.lens.data.db +// [FILE] HomeboxDatabase.kt + +package com.homebox.lens.data.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.homebox.lens.data.db.dao.ItemDao +import com.homebox.lens.data.db.dao.LabelDao +import com.homebox.lens.data.db.dao.LocationDao +import com.homebox.lens.data.db.entity.* + +// [CONTRACT] +/** + * [ENTITY: RoomDatabase('HomeboxDatabase')] + * [PURPOSE] Основной класс для работы с локальной базой данных Room. + */ +@Database( + entities = [ + ItemEntity::class, + LabelEntity::class, + LocationEntity::class, + ItemLabelCrossRef::class + ], + version = 1, + exportSchema = false +) +@TypeConverters(Converters::class) +abstract class HomeboxDatabase : RoomDatabase() { + + abstract fun itemDao(): ItemDao + abstract fun labelDao(): LabelDao + abstract fun locationDao(): LocationDao + + companion object { + const val DATABASE_NAME = "homebox_lens_db" + } +} + +// [END_FILE_HomeboxDatabase.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/dao/ItemDao.kt b/data/src/main/java/com/homebox/lens/data/db/dao/ItemDao.kt new file mode 100644 index 0000000..cbd01a6 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/dao/ItemDao.kt @@ -0,0 +1,40 @@ +// [PACKAGE] com.homebox.lens.data.db.dao +// [FILE] ItemDao.kt + +package com.homebox.lens.data.db.dao + +import androidx.room.* +import com.homebox.lens.data.db.entity.ItemEntity +import com.homebox.lens.data.db.entity.ItemLabelCrossRef +import com.homebox.lens.data.db.entity.ItemWithLabels + +// [CONTRACT] +/** + * [ENTITY: RoomDao('ItemDao')] + * [PURPOSE] Предоставляет методы для работы с 'items' в локальной БД. + */ +@Dao +interface ItemDao { + + @Transaction + @Query("SELECT * FROM items") + suspend fun getItems(): List + + @Transaction + @Query("SELECT * FROM items WHERE id = :itemId") + suspend fun getItem(itemId: String): ItemWithLabels? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertItems(items: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertItem(item: ItemEntity) + + @Query("DELETE FROM items WHERE id = :itemId") + suspend fun deleteItem(itemId: String) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertItemLabelCrossRefs(crossRefs: List) +} + +// [END_FILE_ItemDao.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/dao/LabelDao.kt b/data/src/main/java/com/homebox/lens/data/db/dao/LabelDao.kt new file mode 100644 index 0000000..1f18367 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/dao/LabelDao.kt @@ -0,0 +1,27 @@ +// [PACKAGE] com.homebox.lens.data.db.dao +// [FILE] LabelDao.kt + +package com.homebox.lens.data.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.homebox.lens.data.db.entity.LabelEntity + +// [CONTRACT] +/** + * [ENTITY: RoomDao('LabelDao')] + * [PURPOSE] Предоставляет методы для работы с 'labels' в локальной БД. + */ +@Dao +interface LabelDao { + + @Query("SELECT * FROM labels") + suspend fun getLabels(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertLabels(labels: List) +} + +// [END_FILE_LabelDao.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/dao/LocationDao.kt b/data/src/main/java/com/homebox/lens/data/db/dao/LocationDao.kt new file mode 100644 index 0000000..a5ae616 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/dao/LocationDao.kt @@ -0,0 +1,27 @@ +// [PACKAGE] com.homebox.lens.data.db.dao +// [FILE] LocationDao.kt + +package com.homebox.lens.data.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.homebox.lens.data.db.entity.LocationEntity + +// [CONTRACT] +/** + * [ENTITY: RoomDao('LocationDao')] + * [PURPOSE] Предоставляет методы для работы с 'locations' в локальной БД. + */ +@Dao +interface LocationDao { + + @Query("SELECT * FROM locations") + suspend fun getLocations(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertLocations(locations: List) +} + +// [END_FILE_LocationDao.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt b/data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt new file mode 100644 index 0000000..3bdc3f0 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt @@ -0,0 +1,26 @@ +// [PACKAGE] com.homebox.lens.data.db.entity +// [FILE] ItemEntity.kt + +package com.homebox.lens.data.db.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.math.BigDecimal + +// [CONTRACT] +/** + * [ENTITY: RoomEntity('ItemEntity')] + * [PURPOSE] Представляет собой строку в таблице 'items' в локальной БД. + */ +@Entity(tableName = "items") +data class ItemEntity( + @PrimaryKey val id: String, + val name: String, + val description: String?, + val image: String?, + val locationId: String?, + val value: BigDecimal?, + val createdAt: String? +) + +// [END_FILE_ItemEntity.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/entity/ItemLabelCrossRef.kt b/data/src/main/java/com/homebox/lens/data/db/entity/ItemLabelCrossRef.kt new file mode 100644 index 0000000..819748e --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/entity/ItemLabelCrossRef.kt @@ -0,0 +1,19 @@ +// [PACKAGE] com.homebox.lens.data.db.entity +// [FILE] ItemLabelCrossRef.kt + +package com.homebox.lens.data.db.entity + +import androidx.room.Entity + +// [CONTRACT] +/** + * [ENTITY: RoomEntity('ItemLabelCrossRef')] + * [PURPOSE] Таблица для связи "многие-ко-многим" между ItemEntity и LabelEntity. + */ +@Entity(primaryKeys = ["itemId", "labelId"]) +data class ItemLabelCrossRef( + val itemId: String, + val labelId: String +) + +// [END_FILE_ItemLabelCrossRef.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/entity/ItemWithLabels.kt b/data/src/main/java/com/homebox/lens/data/db/entity/ItemWithLabels.kt new file mode 100644 index 0000000..a649480 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/entity/ItemWithLabels.kt @@ -0,0 +1,29 @@ +// [PACKAGE] com.homebox.lens.data.db.entity +// [FILE] ItemWithLabels.kt + +package com.homebox.lens.data.db.entity + +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation + +// [CONTRACT] +/** + * [ENTITY: Pojo('ItemWithLabels')] + * [PURPOSE] POJO для получения ItemEntity вместе со связанными LabelEntity. + */ +data class ItemWithLabels( + @Embedded val item: ItemEntity, + @Relation( + parentColumn = "id", + entityColumn = "id", + associateBy = Junction( + value = ItemLabelCrossRef::class, + parentColumn = "itemId", + entityColumn = "labelId" + ) + ) + val labels: List +) + +// [END_FILE_ItemWithLabels.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/entity/LabelEntity.kt b/data/src/main/java/com/homebox/lens/data/db/entity/LabelEntity.kt new file mode 100644 index 0000000..d7e6640 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/entity/LabelEntity.kt @@ -0,0 +1,20 @@ +// [PACKAGE] com.homebox.lens.data.db.entity +// [FILE] LabelEntity.kt + +package com.homebox.lens.data.db.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +// [CONTRACT] +/** + * [ENTITY: RoomEntity('LabelEntity')] + * [PURPOSE] Представляет собой строку в таблице 'labels' в локальной БД. + */ +@Entity(tableName = "labels") +data class LabelEntity( + @PrimaryKey val id: String, + val name: String +) + +// [END_FILE_LabelEntity.kt] diff --git a/data/src/main/java/com/homebox/lens/data/db/entity/LocationEntity.kt b/data/src/main/java/com/homebox/lens/data/db/entity/LocationEntity.kt new file mode 100644 index 0000000..e9f27fd --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/db/entity/LocationEntity.kt @@ -0,0 +1,20 @@ +// [PACKAGE] com.homebox.lens.data.db.entity +// [FILE] LocationEntity.kt + +package com.homebox.lens.data.db.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +// [CONTRACT] +/** + * [ENTITY: RoomEntity('LocationEntity')] + * [PURPOSE] Представляет собой строку в таблице 'locations' в локальной БД. + */ +@Entity(tableName = "locations") +data class LocationEntity( + @PrimaryKey val id: String, + val name: String +) + +// [END_FILE_LocationEntity.kt] diff --git a/data/src/main/java/com/homebox/lens/data/di/ApiModule.kt b/data/src/main/java/com/homebox/lens/data/di/ApiModule.kt new file mode 100644 index 0000000..e377bb1 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/di/ApiModule.kt @@ -0,0 +1,77 @@ +// [PACKAGE] com.homebox.lens.data.di +// [FILE] ApiModule.kt + +package com.homebox.lens.data.di + +import com.homebox.lens.data.api.HomeboxApiService +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +// [CONTRACT] +/** + * [MODULE: DaggerHilt('ApiModule')] + * [PURPOSE] Предоставляет зависимости для работы с сетью (Retrofit, OkHttp, Moshi). + */ +@Module +@InstallIn(SingletonComponent::class) +object ApiModule { + + // [HELPER] + private const val BASE_URL = "https://api.homebox.app/" + + // [PROVIDER] + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient { + // [ACTION] Create logging interceptor + val logging = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + // [ACTION] Build OkHttpClient + return OkHttpClient.Builder() + .addInterceptor(logging) + // [TODO] Add AuthInterceptor for Bearer token + .build() + } + + // [PROVIDER] + @Provides + @Singleton + fun provideMoshi(): Moshi { + // [ACTION] Build Moshi with Kotlin adapter + return Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + } + + // [PROVIDER] + @Provides + @Singleton + fun provideRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): Retrofit { + // [ACTION] Build Retrofit instance + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + } + + // [PROVIDER] + @Provides + @Singleton + fun provideHomeboxApiService(retrofit: Retrofit): HomeboxApiService { + // [ACTION] Create ApiService from Retrofit instance + return retrofit.create(HomeboxApiService::class.java) + } +} + +// [END_FILE_ApiModule.kt] diff --git a/data/src/main/java/com/homebox/lens/data/di/DatabaseModule.kt b/data/src/main/java/com/homebox/lens/data/di/DatabaseModule.kt new file mode 100644 index 0000000..f3c43f4 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/di/DatabaseModule.kt @@ -0,0 +1,50 @@ +// [PACKAGE] com.homebox.lens.data.di +// [FILE] DatabaseModule.kt + +package com.homebox.lens.data.di + +import android.content.Context +import androidx.room.Room +import com.homebox.lens.data.db.HomeboxDatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +// [CONTRACT] +/** + * [MODULE: DaggerHilt('DatabaseModule')] + * [PURPOSE] Предоставляет зависимости для работы с базой данных Room. + */ +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + // [PROVIDER] + @Provides + @Singleton + fun provideHomeboxDatabase(@ApplicationContext context: Context): HomeboxDatabase { + // [ACTION] Build Room database instance + return Room.databaseBuilder( + context, + HomeboxDatabase::class.java, + HomeboxDatabase.DATABASE_NAME + ).build() + } + + // [PROVIDER] + @Provides + fun provideItemDao(database: HomeboxDatabase) = database.itemDao() + + // [PROVIDER] + @Provides + fun provideLabelDao(database: HomeboxDatabase) = database.labelDao() + + // [PROVIDER] + @Provides + fun provideLocationDao(database: HomeboxDatabase) = database.locationDao() +} + +// [END_FILE_DatabaseModule.kt] diff --git a/data/src/main/java/com/homebox/lens/data/di/RepositoryModule.kt b/data/src/main/java/com/homebox/lens/data/di/RepositoryModule.kt new file mode 100644 index 0000000..98e5c60 --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/di/RepositoryModule.kt @@ -0,0 +1,31 @@ +// [PACKAGE] com.homebox.lens.data.di +// [FILE] RepositoryModule.kt + +package com.homebox.lens.data.di + +import com.homebox.lens.data.repository.ItemRepositoryImpl +import com.homebox.lens.domain.repository.ItemRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +// [CONTRACT] +/** + * [MODULE: DaggerHilt('RepositoryModule')] + * [PURPOSE] Предоставляет реализацию для интерфейса ItemRepository. + */ +@Module +@InstallIn(SingletonComponent::class) +abstract class RepositoryModule { + + // [PROVIDER] + @Binds + @Singleton + abstract fun bindItemRepository( + itemRepositoryImpl: ItemRepositoryImpl + ): ItemRepository +} + +// [END_FILE_RepositoryModule.kt] diff --git a/data/src/main/java/com/homebox/lens/data/repository/ItemRepositoryImpl.kt b/data/src/main/java/com/homebox/lens/data/repository/ItemRepositoryImpl.kt new file mode 100644 index 0000000..63d8f6b --- /dev/null +++ b/data/src/main/java/com/homebox/lens/data/repository/ItemRepositoryImpl.kt @@ -0,0 +1,105 @@ +// [PACKAGE] com.homebox.lens.data.repository +// [FILE] ItemRepositoryImpl.kt +// [SEMANTICS] data_repository, implementation, network + +// [IMPORTS] +import com.homebox.lens.data.api.HomeboxApiService +import com.homebox.lens.data.api.dto.toDomain +import com.homebox.lens.data.api.dto.toDto +import com.homebox.lens.domain.model.* +import com.homebox.lens.domain.repository.ItemRepository +import javax.inject.Inject + +// [CORE-LOGIC] +/** + * [CONTRACT] + * Реализация репозитория для работы с данными о вещах. + * @param apiService Сервис для взаимодействия с Homebox API. + */ +class ItemRepositoryImpl @Inject constructor( + private val apiService: HomeboxApiService +) : ItemRepository { + + /** + * [CONTRACT] @see ItemRepository.createItem + */ + override suspend fun createItem(newItemData: ItemCreate): ItemSummary { + // [ACTION] + val itemDto = newItemData.toDto() + val resultDto = apiService.createItem(itemDto) + return resultDto.toDomain() + } + + /** + * [CONTRACT] @see ItemRepository.getItemDetails + */ + override suspend fun getItemDetails(itemId: String): ItemOut { + // [ACTION] + val resultDto = apiService.getItem(itemId) + return resultDto.toDomain() + } + + /** + * [CONTRACT] @see ItemRepository.updateItem + */ + override suspend fun updateItem(itemId: String, item: ItemUpdate): ItemOut { + // [ACTION] + val itemDto = item.toDto() + val resultDto = apiService.updateItem(itemId, itemDto) + return resultDto.toDomain() + } + + /** + * [CONTRACT] @see ItemRepository.deleteItem + */ + override suspend fun deleteItem(itemId: String) { + // [ACTION] + apiService.deleteItem(itemId) + } + + /** + * [CONTRACT] @see ItemRepository.syncInventory + */ + override suspend fun syncInventory(page: Int, pageSize: Int): PaginationResult { + // [ACTION] + val resultDto = apiService.getItems(page = page, pageSize = pageSize) + return resultDto.toDomain { it.toDomain() } + } + + /** + * [CONTRACT] @see ItemRepository.getStatistics + */ + override suspend fun getStatistics(): GroupStatistics { + // [ACTION] + val resultDto = apiService.getStatistics() + return resultDto.toDomain() + } + + /** + * [CONTRACT] @see ItemRepository.getAllLocations + */ + override suspend fun getAllLocations(): List { + // [ACTION] + val resultDto = apiService.getLocations() + return resultDto.map { it.toDomain() } + } + + /** + * [CONTRACT] @see ItemRepository.getAllLabels + */ + override suspend fun getAllLabels(): List { + // [ACTION] + val resultDto = apiService.getLabels() + return resultDto.map { it.toDomain() } + } + + /** + * [CONTRACT] @see ItemRepository.searchItems + */ + override suspend fun searchItems(query: String): PaginationResult { + // [ACTION] + val resultDto = apiService.getItems(query = query) + return resultDto.toDomain { it.toDomain() } + } +} +// [END_FILE_ItemRepositoryImpl.kt] \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts new file mode 100644 index 0000000..b05e5be --- /dev/null +++ b/domain/build.gradle.kts @@ -0,0 +1,25 @@ +// [FILE] domain/build.gradle.kts +// [PURPOSE] Build script for the domain module. + +plugins { + id("org.jetbrains.kotlin.jvm") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType { + kotlinOptions.jvmTarget = "1.8" +} + +dependencies { + // [DEPENDENCY] KotlinX Coroutines for asynchronous operations + implementation(Libs.coroutinesCore) + + // [DEPENDENCY] Javax Inject for DI annotations + implementation("javax.inject:javax.inject:1") +} + +// [END_FILE_domain/build.gradle.kts] diff --git a/domain/src/main/java/com/homebox/lens/domain/model/CustomField.kt b/domain/src/main/java/com/homebox/lens/domain/model/CustomField.kt new file mode 100644 index 0000000..e845cec --- /dev/null +++ b/domain/src/main/java/com/homebox/lens/domain/model/CustomField.kt @@ -0,0 +1,18 @@ +// [PACKAGE] com.homebox.lens.domain.model +// [FILE] CustomField.kt +// [SEMANTICS] data_structure, entity, custom_field + +// [CORE-LOGIC] +/** + * [CONTRACT] + * Модель данных для представления кастомного поля. + * @property name Имя поля. + * @property value Значение поля. + * @property type Тип поля (например, "text", "number"). + */ +data class CustomField( + val name: String, + val value: String, + val type: String +) +// [END_FILE_CustomField.kt] diff --git a/domain/src/main/java/com/homebox/lens/domain/model/GroupStatistics.kt b/domain/src/main/java/com/homebox/lens/domain/model/GroupStatistics.kt new file mode 100644 index 0000000..7f07f91 --- /dev/null +++ b/domain/src/main/java/com/homebox/lens/domain/model/GroupStatistics.kt @@ -0,0 +1,20 @@ +// [PACKAGE] com.homebox.lens.domain.model +// [FILE] GroupStatistics.kt +// [SEMANTICS] data_structure, statistics + +// [CORE-LOGIC] +/** + * [CONTRACT] + * Модель данных для представления агрегированной статистики. + * @property items Общее количество вещей. + * @property labels Общее количество меток. + * @property locations Общее количество местоположений. + * @property totalValue Общая стоимость всех вещей. + */ +data class GroupStatistics( + val items: Int, + val labels: Int, + val locations: Int, + val totalValue: Double +) +// [END_FILE_GroupStatistics.kt] diff --git a/domain/src/main/java/com/homebox/lens/domain/model/Image.kt b/domain/src/main/java/com/homebox/lens/domain/model/Image.kt new file mode 100644 index 0000000..b2e5a06 --- /dev/null +++ b/domain/src/main/java/com/homebox/lens/domain/model/Image.kt @@ -0,0 +1,18 @@ +// [PACKAGE] com.homebox.lens.domain.model +// [FILE] Image.kt +// [SEMANTICS] data_structure, entity, image + +// [CORE-LOGIC] +/** + * [CONTRACT] + * Модель данных для представления изображения, привязанного к вещи. + * @property id Уникальный идентификатор изображения. + * @property path Путь к файлу изображения. + * @property isPrimary Является ли это изображение основным для вещи. + */ +data class Image( + val id: String, + val path: String, + val isPrimary: Boolean +) +// [END_FILE_Image.kt] diff --git a/domain/src/main/java/com/homebox/lens/domain/model/Item.kt b/domain/src/main/java/com/homebox/lens/domain/model/Item.kt new file mode 100644 index 0000000..b55ffe3 --- /dev/null +++ b/domain/src/main/java/com/homebox/lens/domain/model/Item.kt @@ -0,0 +1,32 @@ +// [PACKAGE] com.homebox.lens.domain.model +// [FILE] Item.kt + +package com.homebox.lens.domain.model + +import java.math.BigDecimal + +// [CONTRACT] +/** + * [ENTITY: DataClass('Item')] + * [PURPOSE] Представляет собой вещь в инвентаре. + * @property id Уникальный идентификатор вещи. + * @property name Название вещи. + * @property description Описание вещи. + * @property image Url изображения. + * @property location Местоположение вещи. + * @property labels Список меток, присвоенных вещи. + * @property value Стоимость вещи. + * @property createdAt Дата создания. + */ +data class Item( + val id: String, + val name: String, + val description: String?, + val image: String?, + val location: Location?, + val labels: List