From ca101dfb80e293afff4a24501fe43fdea5b98cdf Mon Sep 17 00:00:00 2001 From: maxnes3 <112558334+maxnes3@users.noreply.github.com> Date: Sat, 23 Dec 2023 08:43:17 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B6=D0=B5=20=D1=87=D1=82=D0=BE-=D1=82?= =?UTF-8?q?=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 10 ++ app/src/main/AndroidManifest.xml | 2 + .../example/mobileapp/MobileAppContainer.kt | 30 ++++- .../example/mobileapp/api/ServerService.kt | 126 ++++++++++++++++++ .../mobileapp/api/ServiceRemoteMediator.kt | 102 ++++++++++++++ .../example/mobileapp/api/model/MailRemote.kt | 27 ++++ .../mobileapp/api/model/RemoteConverters.kt | 24 ++++ .../mobileapp/api/model/StoryRemote.kt | 35 +++++ .../example/mobileapp/api/model/UserRemote.kt | 29 ++++ .../mobileapp/api/model/UserRemoteSignIn.kt | 9 ++ .../api/repository/RestUserRepository.kt | 34 +++++ .../mobileapp/components/ListContent.kt | 16 +-- .../mobileapp/database/MobileAppDataBase.kt | 11 +- .../mobileapp/database/dao/RemoteKeysDao.kt | 18 +++ .../mobileapp/database/dao/StoryDao.kt | 9 +- .../example/mobileapp/database/dao/UserDao.kt | 2 +- .../mobileapp/database/entities/Converters.kt | 2 - .../mobileapp/database/entities/RemoteKeys.kt | 23 ++++ .../mobileapp/database/entities/Story.kt | 1 - .../repositories/OfflineStoryRepository.kt | 22 ++- .../repositories/OfflineUserRepository.kt | 5 +- .../repositories/RemoteKeyRepository.kt | 10 ++ .../repositories/RemoteKeysRepositoryImpl.kt | 16 +++ .../database/repositories/StoryRepository.kt | 2 +- .../database/repositories/UserRepository.kt | 5 +- .../database/viewmodels/StoryViewModel.kt | 8 +- .../database/viewmodels/UserViewModel.kt | 29 ++-- .../mobileapp/screens/Authorization.kt | 2 +- .../example/mobileapp/screens/EditScreens.kt | 10 +- .../mobileapp/screens/ListStoryScreen.kt | 34 ++++- .../example/mobileapp/screens/ViewScreens.kt | 25 +++- 31 files changed, 605 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/com/example/mobileapp/api/ServerService.kt create mode 100644 app/src/main/java/com/example/mobileapp/api/ServiceRemoteMediator.kt create mode 100644 app/src/main/java/com/example/mobileapp/api/model/MailRemote.kt create mode 100644 app/src/main/java/com/example/mobileapp/api/model/RemoteConverters.kt create mode 100644 app/src/main/java/com/example/mobileapp/api/model/StoryRemote.kt create mode 100644 app/src/main/java/com/example/mobileapp/api/model/UserRemote.kt create mode 100644 app/src/main/java/com/example/mobileapp/api/model/UserRemoteSignIn.kt create mode 100644 app/src/main/java/com/example/mobileapp/api/repository/RestUserRepository.kt create mode 100644 app/src/main/java/com/example/mobileapp/database/dao/RemoteKeysDao.kt create mode 100644 app/src/main/java/com/example/mobileapp/database/entities/RemoteKeys.kt create mode 100644 app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeyRepository.kt create mode 100644 app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeysRepositoryImpl.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 734980b..06d2276 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp") + kotlin("plugin.serialization") version "1.4.21" } android { @@ -68,6 +69,7 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") + implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0-alpha03") // Room val room_version = "2.5.2" @@ -80,4 +82,12 @@ dependencies { //Paging implementation ("androidx.paging:paging-compose:3.2.1") implementation ("androidx.paging:paging-runtime:3.2.1") + + // 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") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a349b4c..277daa4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + + + @POST("story/create") + suspend fun createStory( + @Body service: StoryRemote, + ): StoryRemote + + @PUT("story/update/{id}") + suspend fun updateStory( + @Path("id") id: Int, + @Body service: StoryRemote + ): StoryRemote + + @DELETE("story/delete/{id}") + suspend fun deleteStory( + @Path("id") id: Int + ) + + @GET("story/getByUser") + suspend fun getUserStories( + @Query("page") page: Int, + @Query("size") size: Int, + @Query("userId") userId: Int, + ): List + + //MAIL + @GET("mail/get/{id}") + suspend fun getMail( + @Path("id") id: Int, + ): MailRemote + + @GET("mail/getAll") + suspend fun getMails( + @Query("page") page: Int, + @Query("size") size: Int, + ): List + + @POST("mail/create") + suspend fun createMail( + @Body service: MailRemote, + ): MailRemote + + @PUT("mail/update/{id}") + suspend fun updateMail( + @Path("id") id: Int, + @Body service: MailRemote + ): MailRemote + + @DELETE("mail/delete/{id}") + suspend fun deleteMail( + @Path("id") id: Int + ) + + //INSTANCE + companion object { + private const val BASE_URL = "https://7hz21fz1-8080.euw.devtunnels.ms/api/" + + @Volatile + private var INSTANCE: ServerService? = null + + fun getInstance(): ServerService { + return INSTANCE ?: synchronized(this) { + val client = OkHttpClient.Builder() + .build() + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + .create(ServerService::class.java) + .also { INSTANCE = it } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/api/ServiceRemoteMediator.kt b/app/src/main/java/com/example/mobileapp/api/ServiceRemoteMediator.kt new file mode 100644 index 0000000..b64f206 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/api/ServiceRemoteMediator.kt @@ -0,0 +1,102 @@ +package com.example.mobileapp.api + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.mobileapp.api.model.toStory +import com.example.mobileapp.database.MobileAppDataBase +import com.example.mobileapp.database.entities.RemoteKeyType +import com.example.mobileapp.database.entities.RemoteKeys +import com.example.mobileapp.database.entities.Story +import com.example.mobileapp.database.repositories.OfflineStoryRepository +import com.example.mobileapp.database.repositories.RemoteKeysRepositoryImpl +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class ServiceRemoteMediator(private val service: ServerService, + private val storyRepository: OfflineStoryRepository, + private val database: MobileAppDataBase, + private val dbRemoteKeyRepository: RemoteKeysRepositoryImpl +) : RemoteMediator() { + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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 stories = service.getStories(page, state.config.pageSize).map { it.toStory() } + val endOfPaginationReached = stories.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.STORY) + storyRepository.clearStories() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = stories.map { + RemoteKeys( + entityId = it.id!!, + type = RemoteKeyType.STORY, + prevKey = prevKey, + nextKey = nextKey + ) + } + dbRemoteKeyRepository.createRemoteKeys(keys) + storyRepository.insertStories(stories) + } + 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): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { story -> + story.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.STORY) } + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { story -> + story.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.STORY) } + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.id?.let { storyUid -> + dbRemoteKeyRepository.getAllRemoteKeys(storyUid, RemoteKeyType.STORY) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/api/model/MailRemote.kt b/app/src/main/java/com/example/mobileapp/api/model/MailRemote.kt new file mode 100644 index 0000000..db67f67 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/api/model/MailRemote.kt @@ -0,0 +1,27 @@ +package com.example.mobileapp.api.model + +import com.example.mobileapp.database.entities.Mail +import kotlinx.serialization.Serializable +import java.util.Date + +@Serializable +data class MailRemote( + val id: Int? = null, + val message: String, + val postdate: Long? = Date().time, + val userId: Int +) + +fun MailRemote.toMail(): Mail = Mail( + id, + message, + postdate, + userId +) + +fun Mail.toMailRemote():MailRemote = MailRemote( + id, + message, + postdate, + userId +) diff --git a/app/src/main/java/com/example/mobileapp/api/model/RemoteConverters.kt b/app/src/main/java/com/example/mobileapp/api/model/RemoteConverters.kt new file mode 100644 index 0000000..016ad12 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/api/model/RemoteConverters.kt @@ -0,0 +1,24 @@ +package com.example.mobileapp.api.model + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 +import java.io.ByteArrayOutputStream + +class RemoteConverters { + companion object { + private const val CHARSET_UTF8 = "UTF-8" + + fun fromBitmap(bitmap: Bitmap): String { + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + val byteArray = outputStream.toByteArray() + return Base64.encodeToString(byteArray, Base64.NO_WRAP) + } + + fun toBitmap(base64String: String): Bitmap { + val byteArray = Base64.decode(base64String, Base64.NO_WRAP) + return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/api/model/StoryRemote.kt b/app/src/main/java/com/example/mobileapp/api/model/StoryRemote.kt new file mode 100644 index 0000000..65da11d --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/api/model/StoryRemote.kt @@ -0,0 +1,35 @@ +package com.example.mobileapp.api.model + +import android.graphics.Bitmap +import com.example.mobileapp.database.entities.Story +import kotlinx.serialization.Serializable +import java.util.Date + +@Serializable +data class StoryRemote( + val id: Int? = null, + val title: String, + val description: String, + val cover: String, + val postdate: Long? = Date().time, + val userId: Int +) + +fun StoryRemote.toStory(): Story = Story( + id, + title, + description, + RemoteConverters.toBitmap(cover), + postdate, + userId +) + +fun Story.toStoryRemote(): StoryRemote = StoryRemote( + id, + title, + description, + RemoteConverters.fromBitmap(cover), + postdate, + userId +) + diff --git a/app/src/main/java/com/example/mobileapp/api/model/UserRemote.kt b/app/src/main/java/com/example/mobileapp/api/model/UserRemote.kt new file mode 100644 index 0000000..9a7dc07 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/api/model/UserRemote.kt @@ -0,0 +1,29 @@ +package com.example.mobileapp.api.model + +import com.example.mobileapp.database.entities.User +import kotlinx.serialization.Serializable + +@Serializable +data class UserRemote( + val id: Int? = 0, + val login: String, + val password: String = "", + val email: String = "", + val photo: String? = null +) + +fun UserRemote.toUser(): User = User( + id, + login, + password, + email, + photo?.let { RemoteConverters.toBitmap(it) }, +) + +fun User.toUserRemote():UserRemote = UserRemote( + id, + login, + password, + email, + photo?.let { RemoteConverters.fromBitmap(it) }, +) diff --git a/app/src/main/java/com/example/mobileapp/api/model/UserRemoteSignIn.kt b/app/src/main/java/com/example/mobileapp/api/model/UserRemoteSignIn.kt new file mode 100644 index 0000000..3bcb055 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/api/model/UserRemoteSignIn.kt @@ -0,0 +1,9 @@ +package com.example.mobileapp.api.model + +import kotlinx.serialization.Serializable + +@Serializable +data class UserRemoteSignIn( + val login: String = "", + val password: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/api/repository/RestUserRepository.kt b/app/src/main/java/com/example/mobileapp/api/repository/RestUserRepository.kt new file mode 100644 index 0000000..7be01f3 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/api/repository/RestUserRepository.kt @@ -0,0 +1,34 @@ +package com.example.mobileapp.api.repository + +import com.example.mobileapp.api.ServerService +import com.example.mobileapp.api.model.UserRemoteSignIn +import com.example.mobileapp.api.model.toUser +import com.example.mobileapp.api.model.toUserRemote +import com.example.mobileapp.database.entities.User +import com.example.mobileapp.database.repositories.UserRepository +import kotlinx.coroutines.flow.Flow + +class RestUserRepository(private var service: ServerService): UserRepository { + override fun getAllUsers(): Flow> { + TODO("Not yet implemented") + } + + override suspend fun getUser(id: Int): User = service.getUser(id).toUser() + + + override suspend fun getUserByLogin(user: UserRemoteSignIn): User { + return service.SignIn(user).toUser() + } + + override suspend fun insertUser(user: User) { + service.SignUp(user.toUserRemote()) + } + + override suspend fun updateUser(user: User) { + service.updateUser(user.toUserRemote()) + } + + override suspend fun deleteUser(user: User) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/components/ListContent.kt b/app/src/main/java/com/example/mobileapp/components/ListContent.kt index a2f9e31..0b8d692 100644 --- a/app/src/main/java/com/example/mobileapp/components/ListContent.kt +++ b/app/src/main/java/com/example/mobileapp/components/ListContent.kt @@ -172,6 +172,7 @@ fun StoryListItem(item: Story, navController: NavHostController, message = "Вы уверены что хотите удалить запись?", onConfirmAction = { storyViewModel.deleteStory(item) showDialog.value = !showDialog.value + navController.navigate("story") }, onDismissAction = { showDialog.value = !showDialog.value }) @@ -209,16 +210,15 @@ fun MailListItem(item: Mail, navController: NavHostController, val userPhoto = remember { mutableStateOf(BitmapFactory.decodeResource(context.resources, R.drawable.post)) } val userName = remember { mutableStateOf("") } - LaunchedEffect(Unit){ - userViewModel.getUser(item.userId).collect { - if (it != null) { - userName.value = it.email - if (it.photo != null){ - userPhoto.value = it.photo - } + /*LaunchedEffect(Unit){ + val user = userViewModel.getUser(item.userId) + if (user != null) { + userName.value = user.email + if (user.photo != null){ + userPhoto.value = user.photo } } - } + }*/ Card( modifier = Modifier diff --git a/app/src/main/java/com/example/mobileapp/database/MobileAppDataBase.kt b/app/src/main/java/com/example/mobileapp/database/MobileAppDataBase.kt index 1085071..90e6ae2 100644 --- a/app/src/main/java/com/example/mobileapp/database/MobileAppDataBase.kt +++ b/app/src/main/java/com/example/mobileapp/database/MobileAppDataBase.kt @@ -9,22 +9,25 @@ import androidx.room.TypeConverters import androidx.sqlite.db.SupportSQLiteDatabase import com.example.mobileapp.R import com.example.mobileapp.database.dao.MailDao +import com.example.mobileapp.database.dao.RemoteKeysDao import com.example.mobileapp.database.dao.StoryDao import com.example.mobileapp.database.dao.UserDao import com.example.mobileapp.database.entities.Converters import com.example.mobileapp.database.entities.Mail +import com.example.mobileapp.database.entities.RemoteKeys import com.example.mobileapp.database.entities.Story import com.example.mobileapp.database.entities.User import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -@Database(entities = [User::class, Story::class, Mail::class], version = 1, exportSchema = false) +@Database(entities = [User::class, Story::class, Mail::class, RemoteKeys::class], version = 10, exportSchema = false) @TypeConverters(Converters::class) abstract class MobileAppDataBase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun storyDao(): StoryDao abstract fun mailDao(): MailDao + abstract fun remoteKeysDao(): RemoteKeysDao companion object{ private const val DB_NAME: String = "my-db" @@ -33,7 +36,7 @@ abstract class MobileAppDataBase : RoomDatabase() { private var INSTANCE: MobileAppDataBase? = null suspend fun initialDataBase(appContext: Context){ - INSTANCE?.let { database -> + /*INSTANCE?.let { database -> val userDao = database.userDao() userDao.insert(User(id = 1, login = "Дзюнзи Ито", password = "1234", email = "ito@gmail.com")) userDao.insert(User(id = 2, login = "Стивен Кинг", password = "4321", email = "king@gmail.com")) @@ -53,9 +56,7 @@ abstract class MobileAppDataBase : RoomDatabase() { mailDao.insert(Mail(message = "Меня отменили в Твиттере", userId = 2)) } } - /*mailDao.insert(Mail(message = "Выложил новые страницы", userId = 1)) - mailDao.insert(Mail(message = "Меня отменили в Твиттере", userId = 2))*/ - } + }*/ } fun getInstance(appContext: Context): MobileAppDataBase { diff --git a/app/src/main/java/com/example/mobileapp/database/dao/RemoteKeysDao.kt b/app/src/main/java/com/example/mobileapp/database/dao/RemoteKeysDao.kt new file mode 100644 index 0000000..d4d64ff --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/database/dao/RemoteKeysDao.kt @@ -0,0 +1,18 @@ +package com.example.mobileapp.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.example.mobileapp.database.entities.RemoteKeyType +import com.example.mobileapp.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) + @Query("DELETE FROM remote_keys WHERE type = :type") + suspend fun clearRemoteKeys(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/database/dao/StoryDao.kt b/app/src/main/java/com/example/mobileapp/database/dao/StoryDao.kt index 4407387..6261823 100644 --- a/app/src/main/java/com/example/mobileapp/database/dao/StoryDao.kt +++ b/app/src/main/java/com/example/mobileapp/database/dao/StoryDao.kt @@ -16,17 +16,20 @@ interface StoryDao { fun getAll(): PagingSource @Query("select * from stories where stories.id = :id") - fun getById(id: Int): Flow + suspend fun getById(id: Int): Story? @Query("select * from stories where stories.user_id = :userId order by stories.id desc") - fun getByUserId(userId: Int): PagingSource + fun getByUserId(userId: Int): Flow> @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insert(story: Story) + suspend fun insert(vararg story: Story) @Update suspend fun update(story: Story) @Delete suspend fun delete(story: Story) + + @Query("delete from stories") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/database/dao/UserDao.kt b/app/src/main/java/com/example/mobileapp/database/dao/UserDao.kt index c2221aa..b76a50a 100644 --- a/app/src/main/java/com/example/mobileapp/database/dao/UserDao.kt +++ b/app/src/main/java/com/example/mobileapp/database/dao/UserDao.kt @@ -15,7 +15,7 @@ interface UserDao { fun getAll(): Flow> @Query("select * from users where users.id = :id") - fun getById(id: Int): Flow + suspend fun getById(id: Int): User? @Query("select * from users where users.login = :login") suspend fun getByLogin(login: String): User? diff --git a/app/src/main/java/com/example/mobileapp/database/entities/Converters.kt b/app/src/main/java/com/example/mobileapp/database/entities/Converters.kt index 90ea52b..a95d5cd 100644 --- a/app/src/main/java/com/example/mobileapp/database/entities/Converters.kt +++ b/app/src/main/java/com/example/mobileapp/database/entities/Converters.kt @@ -4,8 +4,6 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import androidx.room.TypeConverter import java.io.ByteArrayOutputStream -import java.text.SimpleDateFormat -import java.util.Date class Converters { @TypeConverter diff --git a/app/src/main/java/com/example/mobileapp/database/entities/RemoteKeys.kt b/app/src/main/java/com/example/mobileapp/database/entities/RemoteKeys.kt new file mode 100644 index 0000000..ef19b4c --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/database/entities/RemoteKeys.kt @@ -0,0 +1,23 @@ +package com.example.mobileapp.database.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import androidx.room.TypeConverters + +enum class RemoteKeyType(private val type: String) { + STORY(Story::class.simpleName ?: "Story"); + @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? +) \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/database/entities/Story.kt b/app/src/main/java/com/example/mobileapp/database/entities/Story.kt index e63e256..538eef6 100644 --- a/app/src/main/java/com/example/mobileapp/database/entities/Story.kt +++ b/app/src/main/java/com/example/mobileapp/database/entities/Story.kt @@ -5,7 +5,6 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey -import java.util.Calendar import java.util.Date @Entity( diff --git a/app/src/main/java/com/example/mobileapp/database/repositories/OfflineStoryRepository.kt b/app/src/main/java/com/example/mobileapp/database/repositories/OfflineStoryRepository.kt index b68a86a..6c01007 100644 --- a/app/src/main/java/com/example/mobileapp/database/repositories/OfflineStoryRepository.kt +++ b/app/src/main/java/com/example/mobileapp/database/repositories/OfflineStoryRepository.kt @@ -3,6 +3,7 @@ package com.example.mobileapp.database.repositories import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import androidx.paging.PagingSource import com.example.mobileapp.database.dao.StoryDao import com.example.mobileapp.database.entities.Story import kotlinx.coroutines.flow.Flow @@ -24,25 +25,20 @@ class OfflineStoryRepository(private val storyDao: StoryDao): StoryRepository { } override fun getStoriesByUserId(userId: Int): Flow> { - return Pager( - config = PagingConfig( - pageSize = 5, - prefetchDistance = 1, - enablePlaceholders = true, - initialLoadSize = 10, - maxSize = 15 - ), - pagingSourceFactory = { - storyDao.getByUserId(userId) - } - ).flow + TODO("Not yet implemented") } - override fun getStoryById(id: Int): Flow = storyDao.getById(id) + override suspend fun getStoryById(id: Int): Story? = storyDao.getById(id) override suspend fun insertStory(story: Story) = storyDao.insert(story) override suspend fun updateStory(story: Story) = storyDao.update(story) override suspend fun deleteStory(story: Story) = storyDao.delete(story) + + suspend fun clearStories() = storyDao.deleteAll() + suspend fun insertStories(stories: List) = + storyDao.insert(*stories.toTypedArray()) + + fun getAllStoriesPagingSource(): PagingSource = storyDao.getAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/database/repositories/OfflineUserRepository.kt b/app/src/main/java/com/example/mobileapp/database/repositories/OfflineUserRepository.kt index add8f4d..23fcf76 100644 --- a/app/src/main/java/com/example/mobileapp/database/repositories/OfflineUserRepository.kt +++ b/app/src/main/java/com/example/mobileapp/database/repositories/OfflineUserRepository.kt @@ -1,5 +1,6 @@ package com.example.mobileapp.database.repositories +import com.example.mobileapp.api.model.UserRemoteSignIn import com.example.mobileapp.database.dao.UserDao import com.example.mobileapp.database.entities.User import kotlinx.coroutines.flow.Flow @@ -7,9 +8,9 @@ import kotlinx.coroutines.flow.Flow class OfflineUserRepository(private val userDao: UserDao): UserRepository { override fun getAllUsers(): Flow> = userDao.getAll() - override fun getUser(id: Int): Flow = userDao.getById(id) + override suspend fun getUser(id: Int): User? = userDao.getById(id) - override suspend fun getUserByLogin(login: String): User? = userDao.getByLogin(login) + override suspend fun getUserByLogin(user: UserRemoteSignIn): User? = userDao.getByLogin(user.login) override suspend fun insertUser(user: User) = userDao.insert(user) diff --git a/app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeyRepository.kt b/app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeyRepository.kt new file mode 100644 index 0000000..3536e32 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeyRepository.kt @@ -0,0 +1,10 @@ +package com.example.mobileapp.database.repositories + +import com.example.mobileapp.database.entities.RemoteKeyType +import com.example.mobileapp.database.entities.RemoteKeys + +interface RemoteKeyRepository { + suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys? + suspend fun createRemoteKeys(remoteKeys: List) + suspend fun deleteRemoteKey(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeysRepositoryImpl.kt b/app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeysRepositoryImpl.kt new file mode 100644 index 0000000..1f1ba81 --- /dev/null +++ b/app/src/main/java/com/example/mobileapp/database/repositories/RemoteKeysRepositoryImpl.kt @@ -0,0 +1,16 @@ +package com.example.mobileapp.database.repositories + +import com.example.mobileapp.database.dao.RemoteKeysDao +import com.example.mobileapp.database.entities.RemoteKeyType +import com.example.mobileapp.database.entities.RemoteKeys + +class RemoteKeysRepositoryImpl(private val remoteKeysDao: RemoteKeysDao): RemoteKeyRepository { + override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) = + remoteKeysDao.getRemoteKeys(id, type) + + override suspend fun createRemoteKeys(remoteKeys: List) = + remoteKeysDao.insertAll(remoteKeys) + + override suspend fun deleteRemoteKey(type: RemoteKeyType) = + remoteKeysDao.clearRemoteKeys(type) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/database/repositories/StoryRepository.kt b/app/src/main/java/com/example/mobileapp/database/repositories/StoryRepository.kt index cec8433..c274772 100644 --- a/app/src/main/java/com/example/mobileapp/database/repositories/StoryRepository.kt +++ b/app/src/main/java/com/example/mobileapp/database/repositories/StoryRepository.kt @@ -10,7 +10,7 @@ interface StoryRepository { fun getStoriesByUserId(userId: Int): Flow> - fun getStoryById(id: Int): Flow + suspend fun getStoryById(id: Int): Story? suspend fun insertStory(story: Story) diff --git a/app/src/main/java/com/example/mobileapp/database/repositories/UserRepository.kt b/app/src/main/java/com/example/mobileapp/database/repositories/UserRepository.kt index 1c75ef0..1aabaac 100644 --- a/app/src/main/java/com/example/mobileapp/database/repositories/UserRepository.kt +++ b/app/src/main/java/com/example/mobileapp/database/repositories/UserRepository.kt @@ -1,14 +1,15 @@ package com.example.mobileapp.database.repositories +import com.example.mobileapp.api.model.UserRemoteSignIn import com.example.mobileapp.database.entities.User import kotlinx.coroutines.flow.Flow interface UserRepository { fun getAllUsers(): Flow> - fun getUser(id: Int): Flow + suspend fun getUser(id: Int): User? - suspend fun getUserByLogin(login: String): User? + suspend fun getUserByLogin(user: UserRemoteSignIn): User? suspend fun insertUser(user: User) diff --git a/app/src/main/java/com/example/mobileapp/database/viewmodels/StoryViewModel.kt b/app/src/main/java/com/example/mobileapp/database/viewmodels/StoryViewModel.kt index 9896fdb..0241322 100644 --- a/app/src/main/java/com/example/mobileapp/database/viewmodels/StoryViewModel.kt +++ b/app/src/main/java/com/example/mobileapp/database/viewmodels/StoryViewModel.kt @@ -4,18 +4,22 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn +import com.example.mobileapp.GlobalUser import com.example.mobileapp.database.entities.Story import com.example.mobileapp.database.repositories.StoryRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch class StoryViewModel(private val storyRepository: StoryRepository): ViewModel() { val getAllStories: Flow> = storyRepository.getAllStories().cachedIn(viewModelScope) - fun getStoryById(id: Int): Flow = storyRepository.getStoryById(id) + suspend fun getStoryById(id: Int): Story? = storyRepository.getStoryById(id) - fun getStoriesByUserId(userId: Int): Flow> = storyRepository.getStoriesByUserId(userId).cachedIn(viewModelScope) + val getStoriesByUserId: Flow> = GlobalUser.getInstance().getUser()?.id?.let { + storyRepository.getStoriesByUserId(it).cachedIn(viewModelScope) + } ?: flowOf(PagingData.empty()) fun insertStory(story: Story) = viewModelScope.launch { storyRepository.insertStory(story) diff --git a/app/src/main/java/com/example/mobileapp/database/viewmodels/UserViewModel.kt b/app/src/main/java/com/example/mobileapp/database/viewmodels/UserViewModel.kt index bfa2156..aaaa61a 100644 --- a/app/src/main/java/com/example/mobileapp/database/viewmodels/UserViewModel.kt +++ b/app/src/main/java/com/example/mobileapp/database/viewmodels/UserViewModel.kt @@ -3,15 +3,18 @@ package com.example.mobileapp.database.viewmodels import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.mobileapp.GlobalUser +import com.example.mobileapp.api.model.UserRemoteSignIn +import com.example.mobileapp.api.model.toUserRemote import com.example.mobileapp.database.entities.User +import com.example.mobileapp.database.repositories.OfflineUserRepository import com.example.mobileapp.database.repositories.UserRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch class UserViewModel(private val userRepository: UserRepository): ViewModel() { - val getAllUsers = userRepository.getAllUsers() + //val getAllUsers = userRepository.getAllUsers() - fun getUser(id: Int): Flow = userRepository.getUser(id) + suspend fun getUser(id: Int): User? = userRepository.getUser(id) fun updateUser(user: User) = viewModelScope.launch { if (user.login.isEmpty()){ @@ -34,24 +37,20 @@ class UserViewModel(private val userRepository: UserRepository): ViewModel() { } fun regUser(user: User) = viewModelScope.launch { - val globalUser = userRepository.getUserByLogin(user.login) - globalUser?.let { + if(user.password.isEmpty()){ return@launch - } ?: run { - if(user.password.isEmpty()){ - return@launch - } - - if(user.email.isEmpty() || !isValidEmail(user.email)){ - return@launch - } - userRepository.insertUser(user) - GlobalUser.getInstance().setUser(userRepository.getUserByLogin(user.login)) } + + if(user.email.isEmpty() || !isValidEmail(user.email)){ + return@launch + } + userRepository.insertUser(user) + GlobalUser.getInstance().setUser(userRepository.getUserByLogin( + UserRemoteSignIn(user.login, user.password))) } fun authUser(user: User) = viewModelScope.launch { - val globalUser = userRepository.getUserByLogin(user.login) + val globalUser = userRepository.getUserByLogin(UserRemoteSignIn(user.login, user.password)) globalUser?.let { if (user.password.isNotEmpty() && user.password == globalUser.password){ GlobalUser.getInstance().setUser(globalUser) diff --git a/app/src/main/java/com/example/mobileapp/screens/Authorization.kt b/app/src/main/java/com/example/mobileapp/screens/Authorization.kt index 6174137..8d3b67c 100644 --- a/app/src/main/java/com/example/mobileapp/screens/Authorization.kt +++ b/app/src/main/java/com/example/mobileapp/screens/Authorization.kt @@ -40,7 +40,7 @@ fun Authorization(navController: NavHostController, userViewModel: UserViewModel = viewModel( factory = MobileAppViewModelProvider.Factory )) { - val users = userViewModel.getAllUsers.collectAsState(emptyList()).value + //val users = userViewModel.getAllUsers.collectAsState(emptyList()).value val login = remember { mutableStateOf("") } val password = remember { mutableStateOf("") } diff --git a/app/src/main/java/com/example/mobileapp/screens/EditScreens.kt b/app/src/main/java/com/example/mobileapp/screens/EditScreens.kt index 7244ba5..6b0d981 100644 --- a/app/src/main/java/com/example/mobileapp/screens/EditScreens.kt +++ b/app/src/main/java/com/example/mobileapp/screens/EditScreens.kt @@ -78,7 +78,13 @@ fun EditStoryScreen(navController: NavHostController, storyId: Int? = null, LaunchedEffect(Unit) { storyId?.let { - storyViewModel.getStoryById(storyId).collect { + val story = storyViewModel.getStoryById(storyId) + if (story != null) { + cover.value = story.cover + title.value = story.title + description.value = story.description + } + /*storyViewModel.getStoryById(storyId).collect { if (it != null) { cover.value = it.cover } @@ -88,7 +94,7 @@ fun EditStoryScreen(navController: NavHostController, storyId: Int? = null, if (it != null) { description.value = it.description } - } + }*/ } } diff --git a/app/src/main/java/com/example/mobileapp/screens/ListStoryScreen.kt b/app/src/main/java/com/example/mobileapp/screens/ListStoryScreen.kt index 1676738..0a0b452 100644 --- a/app/src/main/java/com/example/mobileapp/screens/ListStoryScreen.kt +++ b/app/src/main/java/com/example/mobileapp/screens/ListStoryScreen.kt @@ -3,10 +3,17 @@ package com.example.mobileapp.screens 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.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController @@ -20,20 +27,42 @@ import com.example.mobileapp.database.entities.Story import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider import com.example.mobileapp.database.viewmodels.StoryViewModel import com.example.mobileapp.ui.theme.BackgroundItem1 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Composable fun ListStoryScreen(navController: NavHostController, storyViewModel: StoryViewModel = viewModel( factory = MobileAppViewModelProvider.Factory )) { - val stories = storyViewModel.getStoriesByUserId(GlobalUser.getInstance().getUser()?.id!!).collectAsLazyPagingItems() - //val stories = storyViewModel.getAllStories.collectAsLazyPagingItems() + val stories = storyViewModel.getStoriesByUserId.collectAsLazyPagingItems() + /*val stories = remember { mutableStateListOf() } + LaunchedEffect(Unit){ + withContext(Dispatchers.IO) { + storyViewModel.getStoriesByUserId(GlobalUser.getInstance().getUser()?.id!!).collect { + stories.clear() + stories.addAll(it) + } + } + }*/ Column( modifier = Modifier .fillMaxSize() .background(BackgroundItem1) ) { + /*LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + ){ + item { + addNewListItem(navController, "editstory") + } + itemsIndexed(stories){ _, item -> + StoryListItem(item = item, navController = navController) + } + }*/ LazyVerticalGrid( columns = GridCells.Fixed(1) ) { @@ -50,6 +79,5 @@ fun ListStoryScreen(navController: NavHostController, } } } - //DataListScroll(navController, stories) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobileapp/screens/ViewScreens.kt b/app/src/main/java/com/example/mobileapp/screens/ViewScreens.kt index 40009d8..9fbb7fc 100644 --- a/app/src/main/java/com/example/mobileapp/screens/ViewScreens.kt +++ b/app/src/main/java/com/example/mobileapp/screens/ViewScreens.kt @@ -54,12 +54,24 @@ fun StoryViewScreen(navController: NavHostController, storyId: Int, val description = remember { mutableStateOf("") } val postdate = remember { mutableStateOf(0) } - val story by storyViewModel.getStoryById(storyId).collectAsState(null) + /*val story by storyViewModel.getStoryById(storyId).collectAsState(null) story?.let { cover.value = it.cover title.value = it.title description.value = it.description postdate.value = it.postdate!! + }*/ + + LaunchedEffect(Unit) { + storyId?.let { + val story = storyViewModel.getStoryById(storyId) + if (story != null) { + cover.value = story.cover + title.value = story.title + description.value = story.description + postdate.value = story.postdate!! + } + } } Column( @@ -116,13 +128,12 @@ fun MailViewScreen(navController: NavHostController, mailId: Int, if (it != null) { message.value = it.message postdate.value = it.postdate!! - userViewModel.getUser(it.userId).collect {user -> - if (user != null) { - if(user.photo != null) { - photo.value = user.photo - } - userName.value = user.email + val user = userViewModel.getUser(it.userId) + if (user != null) { + if(user.photo != null) { + photo.value = user.photo } + userName.value = user.email } } }