diff --git a/api-core/.gitignore b/api-core/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/api-core/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/api-core/build.gradle.kts b/api-core/build.gradle.kts
new file mode 100644
index 0000000..5cd996d
--- /dev/null
+++ b/api-core/build.gradle.kts
@@ -0,0 +1,52 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.serialization)
+}
+
+android {
+ namespace = "com.example.api.core"
+ compileSdk = 35
+
+ defaultConfig {
+ minSdk = 33
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.datastore.preferences)
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.retrofit2.kotlinx.serialization.converter)
+ implementation(libs.retrofit2)
+ implementation(libs.okhttp)
+ implementation(libs.logging.interceptor)
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ testImplementation(libs.mockwebserver)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+}
\ No newline at end of file
diff --git a/api-core/consumer-rules.pro b/api-core/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/api-core/proguard-rules.pro b/api-core/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/api-core/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/api-core/src/androidTest/java/com/example/api/core/ApiRepositoryTest.kt b/api-core/src/androidTest/java/com/example/api/core/ApiRepositoryTest.kt
new file mode 100644
index 0000000..39d3d28
--- /dev/null
+++ b/api-core/src/androidTest/java/com/example/api/core/ApiRepositoryTest.kt
@@ -0,0 +1,24 @@
+package com.example.api.core
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ApiRepositoryTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.api.core.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/api-core/src/main/AndroidManifest.xml b/api-core/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/api-core/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/data/core/ApiFactory.kt b/api-core/src/main/java/com/example/api/core/data/core/ApiFactory.kt
new file mode 100644
index 0000000..6424735
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/core/ApiFactory.kt
@@ -0,0 +1,27 @@
+package com.example.api.core.data.core
+
+import com.example.api.core.domain.ApiRepository
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.create
+
+object ApiFactory {
+
+ fun provideRepository(dataStoreRepository: DataStoreRepository): ApiRepository {
+ val json = Json { ignoreUnknownKeys = true }
+ val retrofit = Retrofit.Builder()
+ .baseUrl("https://api.matule.ru/api/")
+ .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
+ .client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }).build())
+ .build()
+
+
+ return BaseApiRepository(retrofit.create(), dataStoreRepository)
+ }
+}
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/data/core/ApiService.kt b/api-core/src/main/java/com/example/api/core/data/core/ApiService.kt
new file mode 100644
index 0000000..fa2a6e8
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/core/ApiService.kt
@@ -0,0 +1,30 @@
+package com.example.api.core.data.core
+
+import com.example.api.core.data.dto.AuthUserResponseDto
+import com.example.api.core.data.dto.RegisterUserDto
+import com.example.api.core.data.dto.SalesAndActionsDto
+import com.example.api.core.data.dto.SignInUserDto
+import com.example.api.core.data.dto.UserResponseDto
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+internal interface ApiService {
+
+ @POST("collections/users/records")
+ suspend fun register(@Body registerUserDto: RegisterUserDto): UserResponseDto
+
+ @POST("collections/users/auth-with-password")
+ suspend fun auth(@Body signInUserDto: SignInUserDto): AuthUserResponseDto
+
+ @GET("collections/project/records")
+ suspend fun salesAndProjects(): SalesAndActionsDto
+
+// @GET("collections/products/records")
+// suspend fun products():
+}
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/data/core/BaseApiRepository.kt b/api-core/src/main/java/com/example/api/core/data/core/BaseApiRepository.kt
new file mode 100644
index 0000000..63f22d9
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/core/BaseApiRepository.kt
@@ -0,0 +1,35 @@
+package com.example.api.core.data.core
+
+import com.example.api.core.domain.ApiRepository
+import com.example.api.core.domain.AuthUserResponse
+import com.example.api.core.domain.FetchResult
+import com.example.api.core.domain.RegisterUser
+import retrofit2.HttpException
+import java.net.UnknownHostException
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+internal class BaseApiRepository(
+ private val service: ApiService,
+ private val dataStoreRepository: DataStoreRepository
+) : RemoteMapper(), ApiRepository {
+
+ override suspend fun auth(registerUser: RegisterUser): FetchResult {
+ return try {
+ service.register(registerUser.toRegisterUserDto())
+ val response = service.auth(registerUser.toSignInUserDto())
+ dataStoreRepository.saveToken(response.token)
+
+ FetchResult.Success(response.toUserResponse())
+ } catch (e: UnknownHostException) {
+ FetchResult.Error(null, "no internet connection")
+ } catch (e: HttpException) {
+ FetchResult.Error(null, e.message())
+ } catch (e: Exception) {
+ FetchResult.Error(null, e.message!!)
+ }
+ }
+}
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/data/core/DataStoreRepository.kt b/api-core/src/main/java/com/example/api/core/data/core/DataStoreRepository.kt
new file mode 100644
index 0000000..9dbfdc4
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/core/DataStoreRepository.kt
@@ -0,0 +1,38 @@
+package com.example.api.core.data.core
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+interface DataStoreRepository {
+
+ suspend fun saveToken(token: String)
+
+ fun token(): Flow
+
+ class Base(private val preferences: DataStore) : DataStoreRepository {
+ override suspend fun saveToken(token: String) {
+ preferences.edit { settings ->
+ settings[TOKEN_KEY] = token
+ }
+ }
+
+ override fun token(): Flow {
+ return preferences.data.map { preferences ->
+ preferences[TOKEN_KEY] ?: ""
+ }
+ }
+ }
+
+ private companion object {
+ val TOKEN_KEY = stringPreferencesKey("token_key")
+ }
+}
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/data/core/RemoteMapper.kt b/api-core/src/main/java/com/example/api/core/data/core/RemoteMapper.kt
new file mode 100644
index 0000000..23a27a9
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/core/RemoteMapper.kt
@@ -0,0 +1,58 @@
+package com.example.api.core.data.core
+
+import com.example.api.core.data.dto.AuthUserResponseDto
+import com.example.api.core.data.dto.RegisterUserDto
+import com.example.api.core.data.dto.SignInUserDto
+import com.example.api.core.data.dto.SignInUserResponseDto
+import com.example.api.core.data.dto.UserResponseDto
+import com.example.api.core.domain.AuthUserResponse
+import com.example.api.core.domain.RegisterUser
+import com.example.api.core.domain.SignInUserResponse
+import com.example.api.core.domain.UserResponse
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+internal abstract class RemoteMapper {
+ protected fun RegisterUser.toRegisterUserDto(): RegisterUserDto {
+ return RegisterUserDto(
+ email = email,
+ password = password,
+ passwordConfirm = passwordConfirm
+ )
+ }
+
+ protected fun RegisterUser.toSignInUserDto(): SignInUserDto {
+ return SignInUserDto(
+ identity = email,
+ password = password
+ )
+ }
+
+ protected fun AuthUserResponseDto.toUserResponse(): AuthUserResponse {
+ return AuthUserResponse(
+ record = record.toUserResponse(),
+ token = token
+ )
+ }
+
+ protected fun SignInUserResponseDto.toUserResponse(): SignInUserResponse {
+ return SignInUserResponse(
+ collectionId = collectionId,
+ collectionName = collectionName,
+ created = created,
+ dateBirthday = dateBirthday,
+ emailVisibility = emailVisibility,
+ email = email,
+ firstname = firstname,
+ gender = gender,
+ id = id,
+ lastname = lastname,
+ secondname = secondname,
+ updated = updated,
+ verified = verified
+ )
+ }
+}
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/AuthUserResponseDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/AuthUserResponseDto.kt
new file mode 100644
index 0000000..ce98916
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/AuthUserResponseDto.kt
@@ -0,0 +1,9 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class AuthUserResponseDto(
+ val record: SignInUserResponseDto,
+ val token: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/CatalogResponseDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/CatalogResponseDto.kt
new file mode 100644
index 0000000..067db6a
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/CatalogResponseDto.kt
@@ -0,0 +1,8 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class CatalogResponseDto(
+ val items: List
+)
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/ProductDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/ProductDto.kt
new file mode 100644
index 0000000..5522d7a
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/ProductDto.kt
@@ -0,0 +1,3 @@
+package com.example.api.core.data.dto
+
+//data class ProductDto()
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/RegisterUserDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/RegisterUserDto.kt
new file mode 100644
index 0000000..23568f6
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/RegisterUserDto.kt
@@ -0,0 +1,10 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class RegisterUserDto(
+ val email: String,
+ val password: String,
+ val passwordConfirm: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/SaleItemDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/SaleItemDto.kt
new file mode 100644
index 0000000..70f48e6
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/SaleItemDto.kt
@@ -0,0 +1,21 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class SaleItemDto(
+ val id: String,
+ val collectionId: String,
+ val collectionName: String,
+ val created: String,
+ val updated: String,
+ val title: String,
+ val dateStart: String,
+ val dateEnd: String,
+ val gender: String,
+ @SerialName("description_source") val descriptionSource: String,
+ val category: String,
+ val image: String,
+ @SerialName("user_id") val userId: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/SalesAndActionsDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/SalesAndActionsDto.kt
new file mode 100644
index 0000000..66741cc
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/SalesAndActionsDto.kt
@@ -0,0 +1,12 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class SalesAndActionsDto(
+ val page: Int,
+ val perPage: Int,
+ val totalPage: Int,
+ val totalItems: Int,
+ val items: List
+)
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/SignInUserDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/SignInUserDto.kt
new file mode 100644
index 0000000..9cd0b20
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/SignInUserDto.kt
@@ -0,0 +1,9 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class SignInUserDto(
+ val identity: String,
+ val password: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/SignInUserResponseDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/SignInUserResponseDto.kt
new file mode 100644
index 0000000..d7d36cf
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/SignInUserResponseDto.kt
@@ -0,0 +1,20 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SignInUserResponseDto(
+ val collectionId: String,
+ val collectionName: String,
+ val created: String,
+ val dateBirthday: String,
+ val email: String,
+ val emailVisibility: Boolean,
+ val firstname: String,
+ val gender: String,
+ val id: String,
+ val lastname: String,
+ val secondname: String,
+ val updated: String,
+ val verified: Boolean
+)
diff --git a/api-core/src/main/java/com/example/api/core/data/dto/UserResponseDto.kt b/api-core/src/main/java/com/example/api/core/data/dto/UserResponseDto.kt
new file mode 100644
index 0000000..9316d67
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/data/dto/UserResponseDto.kt
@@ -0,0 +1,19 @@
+package com.example.api.core.data.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class UserResponseDto(
+ val collectionId: String,
+ val collectionName: String,
+ val created: String,
+ val dateBirthday: String,
+ val emailVisibility: Boolean,
+ val firstname: String,
+ val gender: String,
+ val id: String,
+ val lastname: String,
+ val secondname: String,
+ val updated: String,
+ val verified: Boolean
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/ApiRepository.kt b/api-core/src/main/java/com/example/api/core/domain/ApiRepository.kt
new file mode 100644
index 0000000..45c10a7
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/ApiRepository.kt
@@ -0,0 +1,11 @@
+package com.example.api.core.domain
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+interface ApiRepository {
+
+ suspend fun auth(registerUser: RegisterUser): FetchResult
+}
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/domain/AuthUserResponse.kt b/api-core/src/main/java/com/example/api/core/domain/AuthUserResponse.kt
new file mode 100644
index 0000000..995215c
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/AuthUserResponse.kt
@@ -0,0 +1,6 @@
+package com.example.api.core.domain
+
+data class AuthUserResponse(
+ val record: SignInUserResponse,
+ val token: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/CatalogResponse.kt b/api-core/src/main/java/com/example/api/core/domain/CatalogResponse.kt
new file mode 100644
index 0000000..4a42c81
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/CatalogResponse.kt
@@ -0,0 +1,5 @@
+package com.example.api.core.domain
+
+data class CatalogResponse(
+ val items: List
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/FetchResult.kt b/api-core/src/main/java/com/example/api/core/domain/FetchResult.kt
new file mode 100644
index 0000000..40238d7
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/FetchResult.kt
@@ -0,0 +1,31 @@
+package com.example.api.core.domain
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+interface FetchResult {
+
+ fun map(mapper: Mapper): Ui
+
+ interface Mapper {
+
+ fun mapSuccess(data: D): Ui
+
+ fun mapError(data: D?, message: String): Ui
+ }
+
+ class Success(private val data: D) : FetchResult {
+ override fun map(mapper: Mapper): Ui {
+ return mapper.mapSuccess(data)
+ }
+ }
+
+ class Error(private val data: D?, private val message: String) : FetchResult {
+ override fun map(mapper: Mapper): Ui {
+ return mapper.mapError(data, message)
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/api-core/src/main/java/com/example/api/core/domain/RegisterUser.kt b/api-core/src/main/java/com/example/api/core/domain/RegisterUser.kt
new file mode 100644
index 0000000..44ca8f1
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/RegisterUser.kt
@@ -0,0 +1,7 @@
+package com.example.api.core.domain
+
+data class RegisterUser(
+ val email: String,
+ val password: String,
+ val passwordConfirm: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/SaleItem.kt b/api-core/src/main/java/com/example/api/core/domain/SaleItem.kt
new file mode 100644
index 0000000..3323300
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/SaleItem.kt
@@ -0,0 +1,17 @@
+package com.example.api.core.domain
+
+data class SaleItem(
+ val id: String,
+ val collectionId: String,
+ val collectionName: String,
+ val created: String,
+ val updated: String,
+ val title: String,
+ val dateStart: String,
+ val dateEnd: String,
+ val gender: String,
+ val descriptionSource: String,
+ val category: String,
+ val image: String,
+ val userId: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/SalesAndActions.kt b/api-core/src/main/java/com/example/api/core/domain/SalesAndActions.kt
new file mode 100644
index 0000000..ac1e13f
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/SalesAndActions.kt
@@ -0,0 +1,9 @@
+package com.example.api.core.domain
+
+internal data class SalesAndActions(
+ val page: Int,
+ val perPage: Int,
+ val totalPage: Int,
+ val totalItems: Int,
+ val items: List
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/SignInUser.kt b/api-core/src/main/java/com/example/api/core/domain/SignInUser.kt
new file mode 100644
index 0000000..5d18d0b
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/SignInUser.kt
@@ -0,0 +1,6 @@
+package com.example.api.core.domain
+
+data class SignInUser(
+ val identity: String,
+ val password: String
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/SignInUserResponse.kt b/api-core/src/main/java/com/example/api/core/domain/SignInUserResponse.kt
new file mode 100644
index 0000000..6b0705e
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/SignInUserResponse.kt
@@ -0,0 +1,17 @@
+package com.example.api.core.domain
+
+data class SignInUserResponse(
+ val collectionId: String,
+ val collectionName: String,
+ val created: String,
+ val dateBirthday: String,
+ val emailVisibility: Boolean,
+ val email: String,
+ val firstname: String,
+ val gender: String,
+ val id: String,
+ val lastname: String,
+ val secondname: String,
+ val updated: String,
+ val verified: Boolean
+)
diff --git a/api-core/src/main/java/com/example/api/core/domain/UserResponse.kt b/api-core/src/main/java/com/example/api/core/domain/UserResponse.kt
new file mode 100644
index 0000000..1b07e73
--- /dev/null
+++ b/api-core/src/main/java/com/example/api/core/domain/UserResponse.kt
@@ -0,0 +1,16 @@
+package com.example.api.core.domain
+
+data class UserResponse(
+ val collectionId: String,
+ val collectionName: String,
+ val created: String,
+ val dateBirthday: String,
+ val emailVisibility: Boolean,
+ val firstname: String,
+ val gender: String,
+ val id: String,
+ val lastname: String,
+ val secondname: String,
+ val updated: String,
+ val verified: Boolean
+)
diff --git a/api-core/src/test/java/com/example/api/core/ServiceTest.kt b/api-core/src/test/java/com/example/api/core/ServiceTest.kt
new file mode 100644
index 0000000..987584e
--- /dev/null
+++ b/api-core/src/test/java/com/example/api/core/ServiceTest.kt
@@ -0,0 +1,133 @@
+package com.example.api.core
+
+import com.example.api.core.data.core.ApiService
+import com.example.api.core.data.dto.RegisterUserDto
+import com.example.api.core.data.dto.SignInUserDto
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import junit.framework.TestCase.assertEquals
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import retrofit2.Retrofit
+import retrofit2.create
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+class ServiceTest {
+
+ private lateinit var mockWebServer: MockWebServer
+ private lateinit var service: ApiService
+
+ @Before
+ fun setup() {
+ mockWebServer = MockWebServer()
+ mockWebServer.start()
+
+ val json = Json { ignoreUnknownKeys = true }
+
+ service = Retrofit.Builder()
+ .baseUrl(mockWebServer.url("/"))
+ .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
+ .client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }).build())
+ .build().create()
+ }
+
+ @Test
+ fun test_registration() = runBlocking {
+ val response = """
+ {
+ "collectionId": "_pb_users_auth_",
+ "collectionName": "users",
+ "created": "2025-05-27 06:08:13.833Z",
+ "dateBirthday": "",
+ "emailVisibility": false,
+ "firstname": "",
+ "gender": "",
+ "id": "w20q0pj1e2824oz",
+ "lastname": "",
+ "secondname": "",
+ "updated": "2025-05-27 06:08:13.833Z",
+ "verified": false
+ }
+ """.trimIndent()
+
+ mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(response))
+
+ val responseResult = service.register(
+ RegisterUserDto(
+ email = "email@test.com",
+ password = "12345678",
+ passwordConfirm = "12345678"
+ )
+ )
+
+ val request = mockWebServer.takeRequest()
+
+ assertEquals("w20q0pj1e2824oz", responseResult.id)
+ assertEquals("2025-05-27 06:08:13.833Z", responseResult.created)
+
+ assertEquals("POST", request.method)
+ assertEquals("/collections/users/records", request.path)
+ }
+
+ @Test
+ fun test_sign_in() = runBlocking {
+ val response = """
+ {
+ "record": {
+ "collectionId": "_pb_users_auth_",
+ "collectionName": "users",
+ "created": "2025-05-27 06:08:13.833Z",
+ "dateBirthday": "",
+ "email": "example123@test.ru",
+ "emailVisibility": false,
+ "firstname": "",
+ "gender": "",
+ "id": "w20q0pj1e2824oz",
+ "lastname": "",
+ "secondname": "",
+ "updated": "2025-05-27 06:08:13.833Z",
+ "verified": false
+ },
+ "token": "eyJhbGciOiJIUzIqwwe1NiIsInR5cCI6IkpXVCJg9.eyJjb2xsZWN0aW9uSWheQiOiJfqcGggJfdXNlcnNfYXV0aFasd8iLCJleHAiOjE3NDg5MzA5MjAsImlkIjoidzIwcTBwajFlMjgyNG96IiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.7Sc_NB_CgnwEfXWp0NjsnDT35gGVXzFJwwLUVD730Gw"
+ }
+ """.trimIndent()
+
+ mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(response))
+
+ val responseResult = service.auth(
+ SignInUserDto(
+ identity = "example123@test.ru",
+ password = "12345678"
+ )
+ )
+
+ val request = mockWebServer.takeRequest()
+
+ assertEquals("example123@test.ru", responseResult.record.email)
+ assertEquals(
+ "eyJhbGciOiJIUzIqwwe1NiIsInR5cCI6IkpXVCJg9.eyJjb2xsZWN0aW9uSWheQiOiJfqcGggJfdXNlcnNfYXV0aFasd8iLCJleHAiOjE3NDg5MzA5MjAsImlkIjoidzIwcTBwajFlMjgyNG96IiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.7Sc_NB_CgnwEfXWp0NjsnDT35gGVXzFJwwLUVD730Gw",
+ responseResult.token
+ )
+
+ assertEquals("POST", request.method)
+ assertEquals("/collections/users/auth-with-password", request.path)
+ }
+
+ @After
+ fun teardown() {
+ mockWebServer.shutdown()
+ }
+}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8cb63ef..46b500e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,6 +2,8 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
+ alias(libs.plugins.dagger.hilt.android)
+ kotlin("kapt")
}
android {
@@ -40,6 +42,10 @@ android {
}
dependencies {
+ implementation(libs.androidx.datastore.preferences)
+ kapt(libs.hilt.android.compiler)
+ implementation(libs.hilt.android)
+ implementation(libs.hilt.navigation.compose)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
@@ -56,4 +62,6 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
+
+ implementation(project(":api-core"))
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 377af60..8967db4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,9 @@
+
{
+ return context.dataStore
+ }
+
+ @Provides
+ @Singleton
+ fun provideDataStoreRepository(preferences: DataStore): DataStoreRepository {
+ return DataStoreRepository.Base(preferences)
+ }
+
+ @Provides
+ @Singleton
+ fun provideRepository(dataStoreRepository: DataStoreRepository): ApiRepository {
+ return ApiFactory.provideRepository(dataStoreRepository)
+ }
+
+ companion object {
+ private val Context.dataStore by preferencesDataStore("settings")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/api/FetchResultMapper.kt b/app/src/main/java/com/example/api/FetchResultMapper.kt
new file mode 100644
index 0000000..d616898
--- /dev/null
+++ b/app/src/main/java/com/example/api/FetchResultMapper.kt
@@ -0,0 +1,18 @@
+package com.example.api
+
+import com.example.api.core.domain.FetchResult
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+class FetchResultMapper : FetchResult.Mapper> {
+ override fun mapSuccess(data: T): FetchResultUiState {
+ return FetchResultUiState.Success(data)
+ }
+
+ override fun mapError(data: T?, message: String): FetchResultUiState {
+ return FetchResultUiState.Error(data, message)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/api/FetchResultUiState.kt b/app/src/main/java/com/example/api/FetchResultUiState.kt
new file mode 100644
index 0000000..734c97c
--- /dev/null
+++ b/app/src/main/java/com/example/api/FetchResultUiState.kt
@@ -0,0 +1,62 @@
+package com.example.api
+
+import androidx.compose.runtime.Composable
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+interface FetchResultUiState {
+
+ @Composable
+ fun Show(
+ onSuccess: @Composable (T) -> Unit,
+ onError: @Composable (T?, String) -> Unit,
+ onLoading: @Composable (T?) -> Unit
+ )
+
+ class Success(val data: T) : FetchResultUiState {
+ @Composable
+ override fun Show(
+ onSuccess: @Composable (T) -> Unit,
+ onError: @Composable (T?, String) -> Unit,
+ onLoading: @Composable (T?) -> Unit
+ ) {
+ onSuccess(data)
+ }
+ }
+
+ class Error(private val data: T?, private val message: String) : FetchResultUiState {
+ @Composable
+ override fun Show(
+ onSuccess: @Composable (T) -> Unit,
+ onError: @Composable (T?, String) -> Unit,
+ onLoading: @Composable (T?) -> Unit
+ ) {
+ onError(data, message)
+ }
+
+ }
+
+ class Loading(private val data: T? = null) : FetchResultUiState {
+ @Composable
+ override fun Show(
+ onSuccess: @Composable (T) -> Unit,
+ onError: @Composable (T?, String) -> Unit,
+ onLoading: @Composable (T?) -> Unit
+ ) {
+ onLoading(data)
+ }
+ }
+
+ class Initial : FetchResultUiState {
+ @Composable
+ override fun Show(
+ onSuccess: @Composable (T) -> Unit,
+ onError: @Composable (T?, String) -> Unit,
+ onLoading: @Composable (T?) -> Unit
+ ) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/api/MainActivity.kt b/app/src/main/java/com/example/api/MainActivity.kt
index 76ce837..31fcbaf 100644
--- a/app/src/main/java/com/example/api/MainActivity.kt
+++ b/app/src/main/java/com/example/api/MainActivity.kt
@@ -4,44 +4,18 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
import com.example.api.ui.theme.ApiTheme
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ApiTheme {
- Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
- Greeting(
- name = "Android",
- modifier = Modifier.padding(innerPadding)
- )
- }
+ TestScreen()
}
}
}
}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- ApiTheme {
- Greeting("Android")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/api/MainViewModel.kt b/app/src/main/java/com/example/api/MainViewModel.kt
new file mode 100644
index 0000000..73d7fc0
--- /dev/null
+++ b/app/src/main/java/com/example/api/MainViewModel.kt
@@ -0,0 +1,47 @@
+package com.example.api
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.api.core.domain.ApiRepository
+import com.example.api.core.domain.AuthUserResponse
+import com.example.api.core.domain.RegisterUser
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+@HiltViewModel
+class MainViewModel @Inject constructor(
+ private val apiRepository: ApiRepository
+) : ViewModel() {
+
+ private val _loadResultUiState =
+ MutableStateFlow>(FetchResultUiState.Initial())
+ val loadResultUiState: StateFlow>
+ get() = _loadResultUiState.asStateFlow()
+
+ fun auth(email: String, password: String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val result = apiRepository.auth(
+ RegisterUser(
+ email = email,
+ password = password,
+ passwordConfirm = password
+ )
+ )
+
+ withContext(Dispatchers.Main) {
+ _loadResultUiState.value = result.map(FetchResultMapper())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/api/TestScreen.kt b/app/src/main/java/com/example/api/TestScreen.kt
new file mode 100644
index 0000000..51834fa
--- /dev/null
+++ b/app/src/main/java/com/example/api/TestScreen.kt
@@ -0,0 +1,41 @@
+package com.example.api
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+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.hilt.navigation.compose.hiltViewModel
+
+/**
+ * Автор: Манякин Дмитрий (user5)
+ * Дата создания: 27.05.2025
+ * */
+
+@Composable
+fun TestScreen(
+ modifier: Modifier = Modifier,
+ viewModel: MainViewModel = hiltViewModel()
+) {
+ val state by viewModel.loadResultUiState.collectAsState()
+
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Column {
+ Button(onClick = { viewModel.auth("someemael@te3st1.com", "12345678") }) {
+ Text("register")
+ }
+
+ state.Show(
+ onSuccess = { Text(text = it.token) },
+ onError = { _, message -> Text(text = message) },
+ onLoading = { CircularProgressIndicator() }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 952b930..f25b160 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,4 +3,6 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.dagger.hilt.android) apply false
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ec7314d..944e351 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,8 +8,30 @@ espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.9.0"
activityCompose = "1.10.1"
composeBom = "2024.09.00"
+appcompat = "1.7.0"
+material = "1.12.0"
+retrofit = "2.11.0"
+okhttp3 = "4.12.0"
+kotlinxSerializationConverter = "1.0.0"
+kotlinxSerializationJson = "1.8.0"
+kotlinxCoroutinesTest = "1.10.2"
+datastorePreferences = "1.1.6"
+dagger = "2.55"
+hiltNavigationCompose = "1.2.0"
[libraries]
+hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "dagger" }
+hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "dagger" }
+hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
+androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
+mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp3" }
+kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
+retrofit2-kotlinx-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "kotlinxSerializationConverter" }
+logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp3" }
+okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" }
+retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
+
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@@ -24,9 +46,14 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins]
+dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "dagger" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+android-library = { id = "com.android.library", version.ref = "agp" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b97e77a..dc65856 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -21,4 +21,4 @@ dependencyResolutionManagement {
rootProject.name = "api"
include(":app")
-
\ No newline at end of file
+include(":api-core")