initCommit

This commit is contained in:
parap 2023-12-23 09:39:23 +04:00
parent 1a2c2bca97
commit 6049cc8163
118 changed files with 3606 additions and 34 deletions

48
.gitignore vendored
View File

@ -1,35 +1,15 @@
# ---> Android
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
# Keystore files
*.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
My Application

View File

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

20
.idea/gradle.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,41 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.20" />
</component>
</project>

9
.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

90
app/build.gradle.kts Normal file
View File

@ -0,0 +1,90 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "com.example.myapplication"
compileSdk = 33
defaultConfig {
applicationId = "com.example.myapplication"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
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_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.5"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.0")
implementation("androidx.navigation:navigation-compose:2.6.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
// Room
val room_version = "2.5.2"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
implementation("androidx.room:room-paging:$room_version")
// retrofit
val retrofitVersion = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("androidx.paging:paging-compose:3.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation ("androidx.paging:paging-compose:3.2.1")
implementation ("androidx.paging:paging-runtime:3.2.1")
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -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

View File

@ -0,0 +1,24 @@
package com.example.myapplication
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 ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.myapplication", appContext.packageName)
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MyApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config"
>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyApplication">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,13 @@
package com.example.myapplication
class GlobalUser {
var userId: Int = 0
companion object {
private var INSTANCE: GlobalUser? = null
fun getInstance(): GlobalUser {
if(INSTANCE == null) INSTANCE = GlobalUser()
return INSTANCE!!
}
}
}

View File

@ -0,0 +1,43 @@
package com.example.myapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
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.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import com.example.myapplication.navigation.Navbar
import com.example.myapplication.ui.theme.MyApplicationTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Navbar()
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
MyApplicationTheme {
Navbar()
}
}

View File

@ -0,0 +1,18 @@
package com.example.myapplication
import android.app.Application
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.repository.OfflineCategoryRepository
import com.example.myapplication.database.repository.OfflineItemRepository
class MyApp : Application() {
val db by lazy { AppDb.getInstance(this) }
val itemRepository: OfflineItemRepository by lazy {
OfflineItemRepository(db.itemDao())
}
val categoryRepository: OfflineCategoryRepository by lazy {
OfflineCategoryRepository(db.categoryDao())
}
}

View File

@ -0,0 +1,118 @@
package ru.ulstu.`is`.pmu.api
import androidx.room.Insert
import com.example.myapplication.api.model.CategoryRemote
import com.example.myapplication.api.model.ItemRemote
import com.example.myapplication.api.model.UserItemListRemote
import com.example.myapplication.api.model.UserItemRemote
import com.example.myapplication.api.model.UserRemote
import com.example.myapplication.database.entities.Category
import com.example.myapplication.database.entities.Item
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.Interceptor.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
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
interface MyServerService {
@GET("items")
suspend fun getItems(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<ItemRemote>
@GET("categories")
suspend fun getCategories(): List<CategoryRemote>
@POST("items")
suspend fun createItem(
@Body item: ItemRemote,
): ItemRemote
@GET("users")
suspend fun getAllUsers() : List<UserRemote>
@GET("users/{id}")
suspend fun getUserById(@Path("id") id: Int) : UserRemote
@GET("users")
suspend fun getUserByAuth(
@Query("login") login: String,
@Query("pass") pass: String) : List<UserRemote>
@GET("users/{id}/userItemCart?_expand=item")
suspend fun getUserItemsCartById(
@Path("id") id: Int): List<UserItemListRemote>
@GET("users/{id}/userItemFavorite?_expand=item")
suspend fun getUserItemsFavoriteById(
@Path("id") id: Int): List<UserItemListRemote>
@GET("userItemCart")
suspend fun getItemCart(
@Query("userId") userId: Int,
@Query("itemId") itemId: Int
): List<UserItemRemote>
@GET("userItemFavorite")
suspend fun getItemFavorite(
@Query("userId") userId: Int,
@Query("itemId") itemId: Int
): List<UserItemRemote>
@POST("users")
suspend fun createUser(@Body user: UserRemote)
@POST("userItemCart")
suspend fun addItemCart(@Body userItem: UserItemRemote)
@POST("userItemFavorite")
suspend fun addItemFavorite(@Body userItem: UserItemRemote)
@DELETE("userItemCart/{id}")
suspend fun deleteCartItem(
@Path("id") id: Int,
)
@DELETE("userItemFavorite{id}")
suspend fun deleteFavoriteItem(
@Path("id") id: Int,
)
companion object {
private const val BASE_URL = "http://10.0.2.2:8079/"
@Volatile
private var INSTANCE: MyServerService? = null
fun getInstance(): MyServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(MyServerService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,20 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.entities.Category
import kotlinx.serialization.Serializable
@Serializable
data class CategoryRemote(
val id: Int = 0,
val name: String,
)
fun CategoryRemote.toCategory(): Category = Category(
id,
name,
)
fun Category.toCategoryRemote(): CategoryRemote = CategoryRemote(
id!!,
name,
)

View File

@ -0,0 +1,41 @@
package com.example.myapplication.api.model
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.example.myapplication.database.entities.Item
import kotlinx.serialization.Serializable
import java.io.ByteArrayOutputStream
@Serializable
data class ItemRemote(
val id: Int = 0,
val name: String,
val price: Double,
val img: ByteArray,
val categoryId: Int
)
fun ItemRemote.toItem(): Item {
val imgBitmap: Bitmap = BitmapFactory.decodeByteArray(img, 0, img.size)
return Item(
id,
name,
price,
imgBitmap,
categoryId
)
}
fun Item.toItemRemote(): ItemRemote {
val outputStream = ByteArrayOutputStream();
img.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
val imgByteArr: ByteArray = outputStream.toByteArray()
return ItemRemote(
0,
name,
price,
imgByteArr,
categoryId
)
}

View File

@ -0,0 +1,17 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.entities.Item
import kotlinx.serialization.Serializable
@Serializable
data class UserItemListRemote(
val id: Int,
val userId: Int,
val itemId: Int,
val item: ItemRemote
)
fun UserItemListRemote.toItemList(): Item {
return item.toItem()
}

View File

@ -0,0 +1,29 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import kotlinx.serialization.Serializable
@Serializable
data class UserItemRemote(
val id: Int,
val userId: Int,
val itemId: Int
)
fun UserItemCart.toUserItemRemote(): UserItemRemote {
return UserItemRemote(
0,
userId,
itemId,
)
}
fun UserItemFavorite.toUserItemRemote(): UserItemRemote {
return UserItemRemote(
0,
userId,
itemId
)
}

View File

@ -0,0 +1,30 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.entities.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
var id: Int,
val name: String,
val login: String,
val password: String
)
fun UserRemote.toUser(): User {
return User(
id,
name,
login,
password
)
}
fun User.toUserRemote(): UserRemote {
return UserRemote(
0,
name,
login,
password
)
}

View File

@ -0,0 +1,109 @@
package com.example.myapplication.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapplication.api.model.toItem
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Item
import com.example.myapplication.database.entities.RemoteKeyType
import com.example.myapplication.database.entities.RemoteKeys
import com.example.myapplication.database.repository.OfflineItemRepository
import com.example.myapplication.database.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import ru.ulstu.`is`.pmu.api.MyServerService
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class ItemRemoteMediator(
private val service: MyServerService,
private val dbItemRepository: OfflineItemRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val categoryRestRepository: RestCategoryRepository,
private val database: AppDb
) : RemoteMediator<Int, Item>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Item>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val items = service.getItems(page, state.config.pageSize)
val endOfPaginationReached = items.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ITEM)
dbItemRepository.clearItems()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = items.map {
RemoteKeys(
entityId = it.id,
type = RemoteKeyType.ITEM,
prevKey = prevKey,
nextKey = nextKey
)
}
categoryRestRepository.getAll()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbItemRepository.insertAll(items.map { it.toItem() })
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Item>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { item ->
dbRemoteKeyRepository.getAllRemoteKeys(item.itemId!!, RemoteKeyType.ITEM)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Item>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { item ->
dbRemoteKeyRepository.getAllRemoteKeys(item.itemId!!, RemoteKeyType.ITEM)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Item>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.itemId?.let { itemUid ->
dbRemoteKeyRepository.getAllRemoteKeys(itemUid, RemoteKeyType.ITEM)
}
}
}
}

View File

@ -0,0 +1,76 @@
package com.example.myapplication.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.api.model.toItemRemote
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.dao.ItemDao
import com.example.myapplication.database.entities.Item
import com.example.myapplication.database.repository.ItemRepository
import com.example.myapplication.database.repository.OfflineItemRepository
import com.example.myapplication.database.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.api.MyServerService
class ItemsRestRepository(
private val service: MyServerService,
private val itemDao: ItemDao,
private val dbItemRepository: OfflineItemRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val categoryRestRepository: RestCategoryRepository,
private val database: AppDb
): ItemRepository {
@OptIn(ExperimentalPagingApi::class)
override fun getAll(): Flow<PagingData<Item>> = Pager(
config = PagingConfig(
pageSize = 8,
prefetchDistance = 2,
enablePlaceholders = true,
initialLoadSize = 12,
maxSize = 24
),
remoteMediator = ItemRemoteMediator(
service,
dbItemRepository,
dbRemoteKeyRepository,
categoryRestRepository,
database,
),
pagingSourceFactory = {
itemDao.getAll()
}
).flow
override fun getById(id: Int): Flow<Item> = itemDao.getById(id)
@OptIn(ExperimentalPagingApi::class)
override fun getByCategory(category_id: Int): Flow<PagingData<Item>> = Pager(
config = PagingConfig(
pageSize = 8,
prefetchDistance = 2,
enablePlaceholders = true,
initialLoadSize = 12,
maxSize = 24
),
remoteMediator = ItemRemoteMediator(
service,
dbItemRepository,
dbRemoteKeyRepository,
categoryRestRepository,
database,
),
pagingSourceFactory = {
itemDao.getByCategory(category_id)
}
).flow
override suspend fun insert(item: Item) {
service.createItem(item.toItemRemote())
}
override suspend fun insertAll(items: List<Item>) = itemDao.insertAll(items)
override suspend fun clearItems() = itemDao.deleteAll()
}

View File

@ -0,0 +1,40 @@
package com.example.myapplication.api.repository
import android.util.Log
import androidx.compose.runtime.rememberCoroutineScope
import com.example.myapplication.api.model.toCategory
import com.example.myapplication.database.entities.Category
import com.example.myapplication.database.repository.CategoryRepository
import com.example.myapplication.database.repository.OfflineCategoryRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import ru.ulstu.`is`.pmu.api.MyServerService
class RestCategoryRepository(
private val service: MyServerService,
private val dbCategoryRepository: OfflineCategoryRepository,
): CategoryRepository {
override suspend fun getAll(): List<Category> {
val existCategories = dbCategoryRepository.getAll().associateBy { it.id }.toMutableMap()
kotlin.runCatching {
service.getCategories()
.map { it.toCategory() }
.forEach { category ->
val existCategory = existCategories[category.id]
if (existCategory == null) {
dbCategoryRepository.insert(category)
}
existCategories[category.id] = category
}
}
.onFailure {
}
return existCategories.map { it.value }.sortedBy { it.id }
}
override suspend fun insert(category: Category) {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,69 @@
package com.example.myapplication.api.repository
import com.example.myapplication.api.model.toItemList
import com.example.myapplication.api.model.toUser
import com.example.myapplication.api.model.toUserItemRemote
import com.example.myapplication.api.model.toUserRemote
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import com.example.myapplication.database.entities.UserWithCartItems
import com.example.myapplication.database.entities.UserWithFavoriteItems
import com.example.myapplication.database.repository.UserRepository
import ru.ulstu.`is`.pmu.api.MyServerService
class UserRestRepository(
private val service: MyServerService
): UserRepository {
override suspend fun getAll(): List<User> {
return service.getAllUsers().map { it.toUser() }
}
override suspend fun getById(id: Int): User {
return service.getUserById(id).toUser()
}
override suspend fun getByAuth(login: String, password: String): User? {
val ans = service.getUserByAuth(login, password)
return if(ans.isEmpty()) null
else ans[0].toUser()
}
override suspend fun getUserItemsCartById(id: Int): UserWithCartItems {
val items = service.getUserItemsCartById(id).map { it.toItemList() }
return UserWithCartItems(service.getUserById(id).toUser(), items)
}
override suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems {
val items = service.getUserItemsFavoriteById(id).map { it.toItemList() }
return UserWithFavoriteItems(service.getUserById(id).toUser(), items)
}
override suspend fun deleteCartItem(userId: Int, itemId: Int) {
val item = service.getItemCart(userId, itemId)[0]
service.deleteCartItem(item.id)
}
override suspend fun deleteFavoriteItem(userId: Int, itemId: Int) {
val item = service.getItemFavorite(userId, itemId)[0]
service.deleteFavoriteItem(item.id)
}
override suspend fun insert(user: User) {
service.createUser(user.toUserRemote())
}
override suspend fun addItemCart(userItem: UserItemCart) {
val item = service.getItemCart(userItem.userId, userItem.itemId)
if(!item.isEmpty()) throw Exception("Уже добавлено")
service.addItemCart(userItem.toUserItemRemote())
}
override suspend fun addItemFavorite(userItem: UserItemFavorite) {
val item = service.getItemFavorite(userItem.userId, userItem.itemId)
if(!item.isEmpty()) throw Exception("Уже добавлено")
service.addItemFavorite(userItem.toUserItemRemote())
}
}

View File

@ -0,0 +1,140 @@
package com.example.myapplication.components
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.components.common.DropDown
import com.example.myapplication.components.common.Input
import com.example.myapplication.database.entities.Category
import com.example.myapplication.database.entities.Item
import com.example.myapplication.database.entities.User
import com.example.myapplication.viewModels.CategoryViewModel
import com.example.myapplication.viewModels.ItemViewModel
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Dispatcher
@Composable
fun AddTovar(
navController: NavController,
id: Int = 0,
itemViewModel: ItemViewModel = viewModel(factory = ItemViewModel.factory),
categoryViewModel: CategoryViewModel = viewModel(factory = CategoryViewModel.factory)) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val img = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources,
R.drawable.smart1
)) }
val imageData = remember { mutableStateOf<Uri?>(null) }
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
imageData.value = uri
}
imageData.value?.let {
if (Build.VERSION.SDK_INT < 28) {
img.value = MediaStore.Images
.Media.getBitmap(context.contentResolver, imageData.value)
} else {
val source = ImageDecoder
.createSource(context.contentResolver, imageData.value!!)
img.value = ImageDecoder.decodeBitmap(source)
}
}
val categories = remember{ mutableListOf<Category>() }
val nameState = remember { mutableStateOf("") }
val priceState = remember { mutableStateOf("") }
val categoryState = remember { mutableStateOf<Category?>(null) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
categories.addAll(categoryViewModel.getAll())
}
if(id != 0) {
itemViewModel.getById(id).collect{
nameState.value = it.name
}
}
}
Column(modifier = Modifier
.padding(start = 30.dp, end = 30.dp, top = 100.dp)
.verticalScroll(rememberScrollState())) {
Input("Название", nameState)
Input("Цена", priceState)
DropDown(categories, categoryState)
Image(
bitmap = img.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(384.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
Button(
onClick = {
launcher.launch("image/*")
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp)) {
Text("Выбрать картинку", fontSize = 20.sp)
}
Button(
onClick = {
coroutineScope.launch {
itemViewModel.insert(Item(null, nameState.value, priceState.value.toDouble(), img.value, categoryState.value!!.id!!))
navController.navigate("main/0")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp)) {
Text("Сохранить", fontSize = 20.sp)
}
}
}

View File

@ -0,0 +1,81 @@
package com.example.myapplication.components
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.components.common.Input
import com.example.myapplication.ui.theme.MyApplicationTheme
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun Authorization(
navController: NavController,
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val loginState = remember { mutableStateOf("") }
val passState = remember { mutableStateOf("") }
Column(modifier = Modifier.padding(start=30.dp, end=30.dp, top=100.dp)) {
Input("Логин", loginState)
Input("Пароль", passState)
Button(
onClick = {
coroutineScope.launch {
val user = userViewModel.getByAuth(loginState.value, passState.value)
if(user == null) {
val toast = Toast.makeText(context, "Неправильный логин или пароль", Toast.LENGTH_SHORT)
toast.show()
return@launch
}
userViewModel.setUserId(user!!.userId!!)
navController.navigate("catalog")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp)) {
Text("Войти", fontSize = 20.sp)
}
Button(
onClick = {navController.navigate("registration")},
modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, Color.Black),
colors= ButtonDefaults.buttonColors(
containerColor=Color.White,
contentColor = Color.Gray
)) {
Text("Регистрация", fontSize = 20.sp, color=Color.Black)
}
}
}

View File

@ -0,0 +1,101 @@
package com.example.myapplication.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.components.common.CardOption
import com.example.myapplication.components.common.CardOptionType
import com.example.myapplication.components.common.TovarCard
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Item
import com.example.myapplication.viewModels.ItemViewModel
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Cart(navController: NavController, userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
val coroutineScope = rememberCoroutineScope()
val items = remember { mutableStateListOf<Item>() }
val sumPrice = remember { mutableStateOf(0.0) }
LaunchedEffect(Unit) {
if(userViewModel.getUserId() == 0) {
navController.navigate("authorization")
}
withContext(Dispatchers.IO) {
val data = userViewModel.getUserItemsCartById(userViewModel.getUserId())
items.clear()
sumPrice.value = 0.0;
data.items.forEach {
sumPrice.value += it.price
items.add(it)
}
}
}
Column {
Text("Корзина", fontSize = 35.sp, modifier = Modifier.padding(top=20.dp, bottom=20.dp))
FlowRow(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top
) {
items.forEach {
TovarCard(
img = it.img,
text = it.name,
price = "${it.price}$",
componentClick = { navController.navigate("tovar/${it.itemId}") },
options = arrayOf(
CardOption(CardOptionType.Minus) {
coroutineScope.launch {
userViewModel.deleteCartItem(userViewModel.getUserId(), it.itemId!!)
sumPrice.value -= it.price
items.remove(it)
}
}
)
)
}
}
Box(
modifier = Modifier
.fillMaxHeight()
.padding(start = 30.dp, end = 30.dp),
contentAlignment = Alignment.BottomStart,
) {
Column {
Text("Итого ${sumPrice.value}$", fontSize = 30.sp, fontWeight = FontWeight.Bold)
Button(onClick = { /*TODO*/ }, modifier = Modifier.fillMaxWidth()) {
Text("Купить", fontSize = 20.sp)
}
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.example.myapplication.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.components.common.CatalogItem
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Category
import com.example.myapplication.database.entities.Item
import com.example.myapplication.viewModels.CategoryViewModel
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun Catalog(
navController: NavController,
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory),
categoryViewModel: CategoryViewModel = viewModel(factory = CategoryViewModel.factory)) {
val categories = remember { mutableStateListOf<Category>() }
LaunchedEffect(Unit) {
if(userViewModel.getUserId() == 0) {
navController.navigate("authorization")
}
withContext(Dispatchers.IO) {
categories.addAll(categoryViewModel.getAll())
}
}
Column {
Text("Каталог", fontSize = 35.sp, modifier = Modifier.padding(top=20.dp, bottom=20.dp))
categories.forEach {
CatalogItem(it.name) {
navController.navigate("main/${it.id}")
}
}
}
}

View File

@ -0,0 +1,78 @@
package com.example.myapplication.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.components.common.CardOption
import com.example.myapplication.components.common.CardOptionType
import com.example.myapplication.components.common.TovarCard
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Item
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Favorites(navController: NavController, userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
val coroutineScope = rememberCoroutineScope()
val items = remember { mutableStateListOf<Item>() }
LaunchedEffect(Unit) {
if(userViewModel.getUserId() == 0) {
navController.navigate("authorization")
}
withContext(Dispatchers.IO) {
val data = userViewModel.getUserItemsFavoriteById(userViewModel.getUserId())
items.clear()
data.items.forEach {
items.add(it)
}
}
}
Column {
Text("Избранное", fontSize = 35.sp, modifier = Modifier.padding(top = 20.dp, bottom = 20.dp))
FlowRow(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top
) {
items.forEach {
TovarCard(
img = it.img,
text = it.name,
price = "${it.price}$",
componentClick = { navController.navigate("tovar/${it.itemId}") },
options = arrayOf(
CardOption(CardOptionType.Minus) {
coroutineScope.launch {
userViewModel.deleteFavoriteItem(userViewModel.getUserId(), it.itemId!!)
items.remove(it)
}
}
)
)
}
}
}
}

View File

@ -0,0 +1,142 @@
package com.example.myapplication.components
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.compose.itemKey
import com.example.myapplication.components.common.CardOption
import com.example.myapplication.components.common.CardOptionType
import com.example.myapplication.components.common.Input
import com.example.myapplication.components.common.TovarCard
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Item
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import com.example.myapplication.viewModels.ItemViewModel
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Main(
navController: NavController,
catalogId: Int,
itemViewModel: ItemViewModel = viewModel(factory = ItemViewModel.factory),
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val tovars = when(catalogId) {
0 -> itemViewModel.getAll().collectAsLazyPagingItems()
else -> itemViewModel.getByCategory(catalogId).collectAsLazyPagingItems()
}
LaunchedEffect(Unit) {
if (userViewModel.getUserId() == 0) {
navController.navigate("authorization")
}
}
Column {
Button(
onClick = {
navController.navigate("add-tovar")
},
shape = RectangleShape,
modifier = Modifier
.padding(10.dp)
.height(40.dp)
) {
Text("Добавить товар")
}
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxWidth(),
) {
items(
count = tovars.itemCount,
key = tovars.itemKey { item -> item.itemId!! }
) {index ->
val it = tovars[index]!!
TovarCard(
img = it.img,
text = it.name,
price = "${it.price}$",
componentClick = { navController.navigate("tovar/${it.itemId}") },
options = arrayOf(
CardOption(CardOptionType.AddFavorites) {
coroutineScope.launch {
kotlin.runCatching {
userViewModel.addItemFavorite(UserItemFavorite(userViewModel.getUserId(), it.itemId!!))
}
.onSuccess {
val toast = Toast.makeText(context, "Добавлено", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
}
.onFailure {
val toast = Toast.makeText(context, "Уже есть в избранном", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
}
}
},
CardOption(CardOptionType.AddCart) {
coroutineScope.launch {
kotlin.runCatching {
userViewModel.addItemCart(UserItemCart(userViewModel.getUserId(), it.itemId!!))
}
.onSuccess {
val toast = Toast.makeText(context, "Добавлено", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
}
.onFailure {
val toast = Toast.makeText(context, "Уже есть в корзине", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
}
}
},
)
)
}
}
}
}

View File

@ -0,0 +1,78 @@
package com.example.myapplication.components
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.components.common.Input
import com.example.myapplication.database.entities.User
import com.example.myapplication.viewModels.UserViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@Composable
fun Registration(
navController: NavController,
userViewModel: UserViewModel = viewModel(factory = UserViewModel.factory)) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val mailState = remember { mutableStateOf("") }
val loginState = remember { mutableStateOf("") }
val passState = remember { mutableStateOf("") }
val repeatPassState = remember { mutableStateOf("") }
Column(modifier = Modifier.padding(start=30.dp, end=30.dp, top=100.dp)) {
Input("Email", mailState)
Input("Логин", loginState)
Input("Пароль", passState)
Input("Повторите Пароль", repeatPassState)
Button(
onClick = {
coroutineScope.launch {
if(passState.value != repeatPassState.value) {
val toast = Toast.makeText(context, "Пароли не совпадают", Toast.LENGTH_SHORT)
toast.show()
delay(500)
toast.cancel()
return@launch
}
userViewModel.insert(User(null, mailState.value, loginState.value, passState.value))
navController.navigate("authorization")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp)) {
Text("Регистрация", fontSize = 20.sp)
}
Button(
onClick = {navController.navigate("authorization")},
modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, Color.Black),
colors= ButtonDefaults.buttonColors(
containerColor= Color.White,
contentColor = Color.Gray
)) {
Text("Авторизация", fontSize = 20.sp, color= Color.Black)
}
}
}

View File

@ -0,0 +1,50 @@
package com.example.myapplication.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.R
import com.example.myapplication.database.AppDb
import com.example.myapplication.database.entities.Item
import com.example.myapplication.viewModels.ItemViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext
@Composable
fun Tovar(id: Int, itemViewModel: ItemViewModel = viewModel(factory = ItemViewModel.factory)) {
val (item, setItem) = remember { mutableStateOf<Item?>(null) }
LaunchedEffect(Unit) {
itemViewModel.getById(id).collect{
setItem(it)
}
}
Column {
item?.let{
Text(it.name, fontSize = 35.sp, modifier = Modifier.padding(top=20.dp, bottom=20.dp))
Image(
bitmap= it.img.asImageBitmap(),
contentDescription = null,
modifier = Modifier.size(250.dp)
)
Text("${item.price}$", fontSize = 20.sp)
}
}
}

View File

@ -0,0 +1,7 @@
package com.example.myapplication.components.common
class CardOption(
val type : CardOptionType,
val onClick: () -> Unit = {}
) {
}

View File

@ -0,0 +1,8 @@
package com.example.myapplication.components.common
enum class CardOptionType {
AddFavorites,
AddCart,
Edit,
Minus;
}

View File

@ -0,0 +1,30 @@
package com.example.myapplication.components.common
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.R
@Composable
fun CatalogItem(text: String, onClick: () -> Unit) {
Button(
onClick = {onClick()},
modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, Color.Black),
shape= RectangleShape,
colors= ButtonDefaults.buttonColors(
containerColor= Color(0xFFCACACA),
contentColor = Color.Gray
)
) {
Text(text, fontSize = 25.sp, color= Color.Black)
}
}

View File

@ -0,0 +1,69 @@
package com.example.myapplication.components.common
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.myapplication.database.entities.Category
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DropDown(items: List<Category>, selected: MutableState<Category?>) {
val expanded = remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier
.padding(top = 7.dp),
expanded = expanded.value,
onExpandedChange = {
expanded.value = !expanded.value
}
) {
TextField(
value = when(selected.value) {
null -> "Значение не выбрано"
else -> selected.value!!.name
},
onValueChange = {},
readOnly = true,
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded.value)
},
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
)
DropdownMenu(
expanded = expanded.value,
onDismissRequest = { expanded.value = false },
modifier = Modifier
.background(Color.White)
.exposedDropdownSize()
) {
items.forEach {
DropdownMenuItem(
text = {
Text(text = it.name)
},
onClick = {
selected.value = it
expanded.value = false
}
)
}
}
}
}

View File

@ -0,0 +1,39 @@
package com.example.myapplication.components.common
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Input(label: String, text: MutableState<String>, height: Dp = 50.dp, modifier: Modifier = Modifier) {
Column(modifier=modifier) {
Text(label)
TextField(
value = text.value,
onValueChange = {newText -> text.value = newText},
colors= TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
errorBorderColor = Color.Transparent
),
modifier = Modifier
.border(1.dp, Color.Black, RoundedCornerShape(10.dp))
.fillMaxWidth()
.height(height),
)
}
}

View File

@ -0,0 +1,96 @@
package com.example.myapplication.components.common
import android.graphics.Bitmap
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.R
private val optionToIcon = mapOf<CardOptionType, Int>(
CardOptionType.AddCart to R.drawable.add_cart_icon,
CardOptionType.AddFavorites to R.drawable.add_favorites_icon,
CardOptionType.Edit to R.drawable.edit_icon,
CardOptionType.Minus to R.drawable.minus_icon,
)
@Composable
fun TovarCard(
img: Bitmap,
text: String,
price: String,
componentClick : () -> Unit,
options: Array<CardOption>) {
Column(
modifier = Modifier
.size(185.dp, 240.dp)
.padding(bottom = 10.dp)
) {
Row {
Button(
onClick = componentClick,
colors = ButtonDefaults.buttonColors(
containerColor= Color.Transparent,
contentColor = Color.Gray
),
contentPadding = PaddingValues(0.dp),
shape = RectangleShape
) {
Image(
bitmap = img.asImageBitmap(),
contentDescription = null,
modifier = Modifier.size(150.dp)
)
}
Column {
options.forEach {option ->
Button(
onClick = option.onClick,
colors = ButtonDefaults.buttonColors(
containerColor= Color.Transparent,
contentColor = Color.Gray
),
contentPadding = PaddingValues(0.dp),
modifier = Modifier.size(20.dp)
) {
Image(
painter = painterResource(id = optionToIcon.getValue(option.type)),
contentDescription = null,
modifier = Modifier.size(20.dp)
)
}
}
}
}
Text(text)
Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.BottomStart) {
Text(
price,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
)
}
}
}

View File

@ -0,0 +1,32 @@
package com.example.myapplication.database
import android.content.Context
import com.example.myapplication.database.repository.CategoryRepository
import com.example.myapplication.database.repository.ItemRepository
import com.example.myapplication.database.repository.OfflineCategoryRepository
import com.example.myapplication.database.repository.OfflineItemRepository
import com.example.myapplication.database.repository.OfflineUserRepository
import com.example.myapplication.database.repository.UserRepository
interface AppContainer {
val userRepository: UserRepository
val itemRepository: ItemRepository
val categoryRepository: CategoryRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDb.getInstance(context).userDao())
}
override val itemRepository: ItemRepository by lazy {
OfflineItemRepository(AppDb.getInstance(context).itemDao())
}
override val categoryRepository: CategoryRepository by lazy {
OfflineCategoryRepository(AppDb.getInstance(context).categoryDao())
}
companion object {
const val TIMEOUT = 5000L
}
}

View File

@ -0,0 +1,95 @@
package com.example.myapplication.database
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.myapplication.R
import com.example.myapplication.database.dao.CategoryDao
import com.example.myapplication.database.dao.ItemDao
import com.example.myapplication.database.dao.RemoteKeysDao
import com.example.myapplication.database.dao.UserDao
import com.example.myapplication.database.entities.Category
import com.example.myapplication.database.entities.Converters
import com.example.myapplication.database.entities.Item
import com.example.myapplication.database.entities.RemoteKeys
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Database(entities = [Item::class, Category::class, User::class, UserItemCart::class, UserItemFavorite::class, RemoteKeys::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDb : RoomDatabase() {
abstract fun itemDao(): ItemDao
abstract fun categoryDao(): CategoryDao
abstract fun userDao(): UserDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {
private const val DB_NAME: String = "myApp.db"
@Volatile
private var INSTANCE: AppDb? = null
private suspend fun populateDatabase(context:Context) {
INSTANCE?.let { database ->
// Categorys
val categoryDao = database.categoryDao()
categoryDao.insert(Category(1, "Смартфоны"))
categoryDao.insert(Category(2, "Компьютеры"))
categoryDao.insert(Category(3, "Телевизоры"))
categoryDao.insert(Category(4, "Для кухни"))
categoryDao.insert(Category(5, "Для дома"))
// Items
val itemDao = database.itemDao()
val smart1: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart1)
val smart2: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart2)
val smart3: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart3)
val smart4: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.smart4)
itemDao.insert(Item(1, "DEXP G450 One 8 GB red", 400.0, smart1,1))
itemDao.insert(Item(2, "INOI A22 Lite 8 GB black", 500.0, smart2,1))
itemDao.insert(Item(3, "DEXP A455 16 GB blue", 250.0, smart3,1))
itemDao.insert(Item(4, "BQ 5031G Fun 8 GB green", 300.0, smart4,1))
val userDao = database.userDao()
userDao.insert(User(1, "Иванов И.И", "ivanov", "1234"))
database.userDao().addItemCart(UserItemCart(1, 1))
database.userDao().addItemCart(UserItemCart(1, 2))
database.userDao().addItemFavorite(UserItemFavorite(1, 3))
database.userDao().addItemFavorite(UserItemFavorite(1, 4))
}
}
fun getInstance(appContext: Context): AppDb {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
appContext,
AppDb::class.java,
DB_NAME
)
/* .addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
populateDatabase(appContext)
}
}
})*/
.build()
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,16 @@
package com.example.myapplication.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.example.myapplication.database.entities.Category
import kotlinx.coroutines.flow.Flow
@Dao
interface CategoryDao {
@Query("select * from categories")
fun getAll(): List<Category>
@Insert
suspend fun insert(category: Category)
}

View File

@ -0,0 +1,30 @@
package com.example.myapplication.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.example.myapplication.database.entities.Item
import kotlinx.coroutines.flow.Flow
@Dao
interface ItemDao {
@Query("select * from items")
fun getAll(): PagingSource<Int, Item>
@Query("select * from items where items.itemId = :id")
fun getById(id: Int): Flow<Item>
@Query("select * from items where items.category_id = :category_id")
fun getByCategory(category_id: Int): PagingSource<Int, Item>
@Insert
suspend fun insert(item: Item)
@Insert
suspend fun insertAll(items: List<Item>)
@Query("delete from items")
suspend fun deleteAll()
}

View File

@ -0,0 +1,20 @@
package com.example.myapplication.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.example.myapplication.database.entities.RemoteKeyType
import com.example.myapplication.database.entities.RemoteKeys
@Dao
interface RemoteKeysDao {
@Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type")
suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys>)
@Query("DELETE FROM remote_keys WHERE type = :type")
suspend fun clearRemoteKeys(type: RemoteKeyType)
}

View File

@ -0,0 +1,44 @@
package com.example.myapplication.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import com.example.myapplication.database.entities.UserWithCartItems
import com.example.myapplication.database.entities.UserWithFavoriteItems
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("select * from users")
fun getAll(): List<User>
@Query("select * from users where users.userId = :id")
fun getById(id: Int): User
@Query("select * from users where users.login = :login and users.password = :password")
fun getByAuth(login: String, password: String): User?
@Query("delete from useritemcart where useritemcart.userId == :userId and useritemcart.itemId == :itemId")
suspend fun deleteCartItem(userId: Int, itemId: Int)
@Query("delete from useritemfavorite where useritemfavorite.userId == :userId and useritemfavorite.itemId == :itemId")
suspend fun deleteFavoriteItem(userId: Int, itemId: Int)
@Insert
suspend fun insert(user: User)
@Query("select * from users where users.userId = :id")
fun getUserItemsCartById(id: Int): UserWithCartItems
@Insert
suspend fun addItemCart(userItem: UserItemCart)
@Query("select * from users where users.userId = :id")
fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems
@Insert
suspend fun addItemFavorite(userItem: UserItemFavorite)
}

View File

@ -0,0 +1,25 @@
package com.example.myapplication.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "categories")
data class Category(
@PrimaryKey(autoGenerate = true)
var id: Int?,
@ColumnInfo(name="name")
val name: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Category
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@ -0,0 +1,21 @@
package com.example.myapplication.database.entities
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.TypeConverter
import java.io.ByteArrayOutputStream
class Converters {
@TypeConverter
fun fromBitmap(bitmap: Bitmap) : ByteArray {
val outputStream = ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return outputStream.toByteArray()
}
@TypeConverter
fun toBitmap(byteArray: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
}

View File

@ -0,0 +1,46 @@
package com.example.myapplication.database.entities
import android.graphics.Bitmap
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
tableName = "items",
foreignKeys = [
ForeignKey(
entity = Category::class,
parentColumns = ["id"],
childColumns = ["category_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
]
)
data class Item(
@PrimaryKey(autoGenerate = true)
var itemId: Int?,
@ColumnInfo(name="name")
val name: String,
@ColumnInfo(name="price")
val price: Double,
@ColumnInfo(name="img")
val img: Bitmap,
@ColumnInfo(name="category_id")
val categoryId: Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Item
if (itemId != other.itemId) return false
return true
}
override fun hashCode(): Int {
return itemId ?: -1
}
}

View File

@ -0,0 +1,26 @@
package com.example.myapplication.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import com.example.myapplication.database.entities.Item
enum class RemoteKeyType(private val type: String) {
ITEM(Item::class.simpleName ?: "Item");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -0,0 +1,29 @@
package com.example.myapplication.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
var userId: Int?,
@ColumnInfo(name="mail")
val name: String,
@ColumnInfo(name="login")
val login: String,
@ColumnInfo(name="password")
val password: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as User
if (userId != other.userId) return false
return true
}
override fun hashCode(): Int {
return userId ?: -1
}
}

View File

@ -0,0 +1,9 @@
package com.example.myapplication.database.entities
import androidx.room.Entity
@Entity(primaryKeys = ["userId", "itemId"])
data class UserItemCart(
val userId: Int,
val itemId: Int
)

View File

@ -0,0 +1,9 @@
package com.example.myapplication.database.entities
import androidx.room.Entity
@Entity(primaryKeys = ["userId", "itemId"])
data class UserItemFavorite (
val userId: Int,
val itemId: Int
)

View File

@ -0,0 +1,15 @@
package com.example.myapplication.database.entities
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class UserWithCartItems (
@Embedded val user : User,
@Relation(
parentColumn = "userId",
entityColumn = "itemId",
associateBy = Junction(UserItemCart::class)
)
val items : List<Item>
)

View File

@ -0,0 +1,16 @@
package com.example.myapplication.database.entities
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class UserWithFavoriteItems (
@Embedded
val user : User,
@Relation(
parentColumn = "userId",
entityColumn = "itemId",
associateBy = Junction(UserItemFavorite::class)
)
val items : List<Item>
)

View File

@ -0,0 +1,9 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.entities.Category
import kotlinx.coroutines.flow.Flow
interface CategoryRepository {
suspend fun getAll(): List<Category>
suspend fun insert(category: Category)
}

View File

@ -0,0 +1,15 @@
package com.example.myapplication.database.repository
import androidx.paging.PagingData
import androidx.room.Query
import com.example.myapplication.database.entities.Item
import kotlinx.coroutines.flow.Flow
interface ItemRepository {
fun getAll(): Flow<PagingData<Item>>
fun getById(id: Int): Flow<Item>
fun getByCategory(category_id: Int): Flow<PagingData<Item>>
suspend fun insert(item: Item)
suspend fun insertAll(items: List<Item>)
suspend fun clearItems()
}

View File

@ -0,0 +1,10 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.dao.CategoryDao
import com.example.myapplication.database.entities.Category
import kotlinx.coroutines.flow.Flow
class OfflineCategoryRepository(private val categoryDao: CategoryDao) : CategoryRepository {
override suspend fun getAll(): List<Category> = categoryDao.getAll()
override suspend fun insert(category: Category) = categoryDao.insert(category)
}

View File

@ -0,0 +1,44 @@
package com.example.myapplication.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.database.dao.ItemDao
import com.example.myapplication.database.entities.Item
import kotlinx.coroutines.flow.Flow
class OfflineItemRepository(private val itemDao: ItemDao) : ItemRepository {
override fun getAll(): Flow<PagingData<Item>> {
return Pager(
config = PagingConfig(
pageSize = 8,
prefetchDistance = 2,
enablePlaceholders = true,
initialLoadSize = 12,
maxSize = 24
),
pagingSourceFactory = {
itemDao.getAll()
}
).flow
}
override fun getById(id: Int): Flow<Item> = itemDao.getById(id)
override fun getByCategory(category_id: Int): Flow<PagingData<Item>> {
return Pager(
config = PagingConfig(
pageSize = 8,
prefetchDistance = 2,
enablePlaceholders = true,
initialLoadSize = 12,
maxSize = 24
),
pagingSourceFactory = {
itemDao.getByCategory(category_id)
}
).flow
}
override suspend fun insert(item: Item) = itemDao.insert(item)
override suspend fun insertAll(items: List<Item>) = itemDao.insertAll(items)
override suspend fun clearItems() = itemDao.deleteAll()
}

View File

@ -0,0 +1,16 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.dao.RemoteKeysDao
import com.example.myapplication.database.entities.RemoteKeyType
import com.example.myapplication.database.entities.RemoteKeys
class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository {
override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) =
remoteKeysDao.getRemoteKeys(id, type)
override suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -0,0 +1,25 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.dao.UserDao
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import com.example.myapplication.database.entities.UserWithCartItems
import com.example.myapplication.database.entities.UserWithFavoriteItems
import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override suspend fun getAll(): List<User> = userDao.getAll()
override suspend fun getById(id: Int): User = userDao.getById(id)
override suspend fun getByAuth(login: String, password: String): User? = userDao.getByAuth(login, password)
override suspend fun getUserItemsCartById(id: Int): UserWithCartItems = userDao.getUserItemsCartById(id)
override suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems = userDao.getUserItemsFavoriteById(id)
override suspend fun deleteCartItem(userId: Int, itemId: Int) = userDao.deleteCartItem(userId, itemId)
override suspend fun deleteFavoriteItem(userId: Int, itemId: Int) = userDao.deleteFavoriteItem(userId, itemId)
override suspend fun insert(user: User) = userDao.insert(user)
override suspend fun addItemCart(userItem: UserItemCart) = userDao.addItemCart(userItem)
override suspend fun addItemFavorite(userItem: UserItemFavorite) = userDao.addItemFavorite(userItem)
}

View File

@ -0,0 +1,11 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.entities.RemoteKeyType
import com.example.myapplication.database.entities.RemoteKeys
interface RemoteKeyRepository {
suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys?
suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

@ -0,0 +1,26 @@
package com.example.myapplication.database.repository
import androidx.room.Query
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import com.example.myapplication.database.entities.UserWithCartItems
import com.example.myapplication.database.entities.UserWithFavoriteItems
import kotlinx.coroutines.flow.Flow
interface UserRepository {
suspend fun getAll(): List<User>
suspend fun getById(id: Int): User
suspend fun getByAuth(login: String, password: String): User?
suspend fun getUserItemsCartById(id: Int): UserWithCartItems
suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems
suspend fun deleteCartItem(userId: Int, itemId: Int)
suspend fun deleteFavoriteItem(userId: Int, itemId: Int)
suspend fun insert(user: User)
suspend fun addItemCart(userItem: UserItemCart)
suspend fun addItemFavorite(userItem: UserItemFavorite)
}

View File

@ -0,0 +1,12 @@
package com.example.myapplication.navigation
import android.graphics.drawable.Drawable
import android.media.Image
import androidx.annotation.DrawableRes
class NavItem(
val route : String,
val label : String,
@DrawableRes val icon : Int
) {
}

View File

@ -0,0 +1,109 @@
package com.example.myapplication.navigation
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.myapplication.R
import com.example.myapplication.components.AddTovar
import com.example.myapplication.components.Authorization
import com.example.myapplication.components.Cart
import com.example.myapplication.components.Catalog
import com.example.myapplication.components.Favorites
import com.example.myapplication.components.Main
import com.example.myapplication.components.Registration
import com.example.myapplication.components.Tovar
import com.example.myapplication.ui.theme.MyApplicationTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Navbar() {
val navController = rememberNavController()
val items = listOf(
NavItem("main/0", "Главная", R.drawable.home_icon),
NavItem("catalog", "Каталог", R.drawable.catalog_icon),
NavItem("cart", "Корзина", R.drawable.cart_icon),
NavItem("favorites", "Избранное", R.drawable.favorites_icon)
)
Scaffold(
bottomBar = {
NavigationBar {
items.forEach{item ->
NavigationBarItem(
icon = {
Image(
painter = painterResource(item.icon),
contentDescription = null,
modifier = Modifier.size(35.dp)
)
},
label={Text(item.label)},
onClick = {
navController.navigate(item.route)
},
selected = false,
modifier = Modifier.fillMaxSize()
)
}
}
}
) {innerPaddings ->
NavHost(
navController = navController,
startDestination = "authorization",
modifier = Modifier.padding(innerPaddings)
) {
composable("authorization") { Authorization(navController) }
composable("registration") { Registration(navController) }
composable("catalog") { Catalog(navController) }
composable("cart") { Cart(navController) }
composable("favorites") { Favorites(navController) }
composable("add-tovar") { AddTovar(navController) }
composable(
"main/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType })
) {backStackEntry ->
backStackEntry.arguments?.let { Main(navController, it.getInt("id")) }
}
composable(
"tovar/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { Tovar(it.getInt("id")) }
}
}
}
}
@Preview(name="Navbar")
@Composable
fun PreviewNavbar() {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Navbar()
}
}
}

View File

@ -0,0 +1,11 @@
package com.example.myapplication.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)

View File

@ -0,0 +1,70 @@
package com.example.myapplication.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
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun MyApplicationTheme(
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
)
}

View File

@ -0,0 +1,34 @@
package com.example.myapplication.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
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,33 @@
package com.example.myapplication.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import com.example.myapplication.MyApp
import com.example.myapplication.api.repository.RestCategoryRepository
import com.example.myapplication.database.entities.Category
import com.example.myapplication.database.repository.CategoryRepository
import com.example.myapplication.database.repository.OfflineCategoryRepository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.api.MyServerService
class CategoryViewModel(
private val categoryRepository: CategoryRepository
) : ViewModel() {
companion object {
val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val app: MyApp = (checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) as MyApp)
return CategoryViewModel(RestCategoryRepository(MyServerService.getInstance(), app.categoryRepository)) as T
}
}
}
suspend fun getAll(): List<Category> {
return categoryRepository.getAll()
}
}

View File

@ -0,0 +1,60 @@
package com.example.myapplication.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.example.myapplication.MyApp
import com.example.myapplication.api.repository.ItemsRestRepository
import com.example.myapplication.api.repository.RestCategoryRepository
import com.example.myapplication.database.entities.Item
import com.example.myapplication.database.repository.ItemRepository
import com.example.myapplication.database.repository.OfflineItemRepository
import com.example.myapplication.database.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.api.MyServerService
class ItemViewModel(
private val itemRepository: ItemRepository
) : ViewModel() {
companion object {
val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory{
override fun <T: ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras) : T {
val app: MyApp = (checkNotNull(extras[APPLICATION_KEY]) as MyApp)
return ItemViewModel(ItemsRestRepository(
MyServerService.getInstance(),
app.db.itemDao(),
app.itemRepository,
OfflineRemoteKeyRepository(app.db.remoteKeysDao()),
RestCategoryRepository(MyServerService.getInstance(), app.categoryRepository),
app.db
)) as T
}
}
}
fun getAll() : Flow<PagingData<Item>> {
return itemRepository.getAll().cachedIn(viewModelScope)
}
fun getById(id: Int) : Flow<Item> {
return itemRepository.getById(id)
}
fun getByCategory(cId: Int): Flow<PagingData<Item>> {
return itemRepository.getByCategory(cId).cachedIn(viewModelScope)
}
suspend fun insert(item: Item) {
itemRepository.insert(item)
}
}

View File

@ -0,0 +1,78 @@
package com.example.myapplication.viewModels
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import com.example.myapplication.GlobalUser
import com.example.myapplication.MyApp
import com.example.myapplication.api.repository.UserRestRepository
import com.example.myapplication.database.entities.User
import com.example.myapplication.database.entities.UserItemCart
import com.example.myapplication.database.entities.UserItemFavorite
import com.example.myapplication.database.entities.UserWithCartItems
import com.example.myapplication.database.entities.UserWithFavoriteItems
import com.example.myapplication.database.repository.OfflineUserRepository
import com.example.myapplication.database.repository.UserRepository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.api.MyServerService
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
companion object {
val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory{
override fun <T: ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
) : T {
return UserViewModel(UserRestRepository(MyServerService.getInstance())) as T
}
}
}
fun getUserId(): Int {
return GlobalUser.getInstance().userId
}
fun setUserId(id: Int) {
GlobalUser.getInstance().userId = id
}
suspend fun getAll(): List<User> {
return userRepository.getAll()
}
suspend fun getById(id: Int): User {
return userRepository.getById(id)
}
suspend fun getByAuth(login: String, password: String): User? {
return userRepository.getByAuth(login, password)
}
suspend fun deleteCartItem(userId: Int, itemId: Int) {
userRepository.deleteCartItem(userId, itemId)
}
suspend fun deleteFavoriteItem(userId: Int, itemId: Int) {
userRepository.deleteFavoriteItem(userId, itemId)
}
suspend fun getUserItemsCartById(id: Int): UserWithCartItems {
return userRepository.getUserItemsCartById(id)
}
suspend fun getUserItemsFavoriteById(id: Int): UserWithFavoriteItems {
return userRepository.getUserItemsFavoriteById(id)
}
suspend fun insert(user: User) {
userRepository.insert(user)
}
suspend fun addItemCart(userItem: UserItemCart) {
userRepository.addItemCart(userItem)
}
suspend fun addItemFavorite(userItem: UserItemFavorite) {
userRepository.addItemFavorite(userItem)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

Some files were not shown because too many files have changed in this diff Show More