Compare commits

..

3 Commits

Author SHA1 Message Date
maxnes3
8e644e6ba0 5 лаба готова и сдана 2023-12-23 11:23:33 +04:00
maxnes3
ca101dfb80 Уже что-то 2023-12-23 08:43:17 +04:00
maxnes3
6bce075545 Начало 2023-12-14 00:48:13 +04:00
38 changed files with 801 additions and 91 deletions

View File

@ -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")
}

View File

@ -1,6 +1,8 @@
<?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.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MobileApp"
@ -12,7 +14,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MobileApp"
tools:targetApi="31">
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"

View File

@ -1,11 +1,16 @@
package com.example.mobileapp
import android.content.Context
import com.example.mobileapp.api.ServerService
import com.example.mobileapp.api.repository.RestMailRepository
import com.example.mobileapp.api.repository.RestStoryRepository
import com.example.mobileapp.api.repository.RestUserRepository
import com.example.mobileapp.database.MobileAppDataBase
import com.example.mobileapp.database.repositories.MailRepository
import com.example.mobileapp.database.repositories.OfflineMailRepository
import com.example.mobileapp.database.repositories.OfflineStoryRepository
import com.example.mobileapp.database.repositories.OfflineUserRepository
import com.example.mobileapp.database.repositories.RemoteKeysRepositoryImpl
import com.example.mobileapp.database.repositories.StoryRepository
import com.example.mobileapp.database.repositories.UserRepository
@ -13,22 +18,42 @@ interface MobileAppContainer {
val mailRepository: MailRepository
val storyRepository: StoryRepository
val userRepository: UserRepository
companion object{
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class MobileAppDataContainer(private val context: Context): MobileAppContainer {
override val mailRepository: MailRepository by lazy {
OfflineMailRepository(MobileAppDataBase.getInstance(context).mailDao())
//OfflineMailRepository(MobileAppDataBase.getInstance(context).mailDao())
RestMailRepository(ServerService.getInstance())
}
override val storyRepository: StoryRepository by lazy {
OfflineStoryRepository(MobileAppDataBase.getInstance(context).storyDao())
//OfflineStoryRepository(MobileAppDataBase.getInstance(context).storyDao())
RestStoryRepository(ServerService.getInstance(),
storyReposLocal,
userReposLocal,
MobileAppDataBase.getInstance(context),
remoteKeyRepository)
}
override val userRepository: UserRepository by lazy {
OfflineUserRepository(MobileAppDataBase.getInstance(context).userDao())
//OfflineUserRepository(MobileAppDataBase.getInstance(context).userDao())
RestUserRepository(ServerService.getInstance())
}
companion object{
const val TIMEOUT = 5000L
private val remoteKeyRepository: RemoteKeysRepositoryImpl by lazy{
RemoteKeysRepositoryImpl(MobileAppDataBase.getInstance(context).remoteKeysDao())
}
private val storyReposLocal: OfflineStoryRepository by lazy {
OfflineStoryRepository(MobileAppDataBase.getInstance(context).storyDao())
}
private val userReposLocal: OfflineUserRepository by lazy {
OfflineUserRepository(MobileAppDataBase.getInstance(context).userDao())
}
}

View File

@ -0,0 +1,129 @@
package com.example.mobileapp.api
import com.example.mobileapp.api.model.MailRemote
import com.example.mobileapp.api.model.StoryRemote
import com.example.mobileapp.api.model.UserRemote
import com.example.mobileapp.api.model.UserRemoteSignIn
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
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 ServerService {
//USER
@GET("user/get/{id}")
suspend fun getUser(
@Path("id") id: Int,
): UserRemote
@POST("user/signup")
suspend fun SignUp(
@Body user: UserRemote,
): UserRemote
@POST("user/signin")
suspend fun SignIn(
@Body user: UserRemoteSignIn
): UserRemote
@POST("user/update")
suspend fun updateUser(
@Body user: UserRemote
): UserRemote
@GET("user/getAll")
suspend fun getUsers(): List<UserRemote>
//STORY
@GET("story/get/{id}")
suspend fun getStory(
@Path("id") id: Int,
): StoryRemote
@GET("story/getAll")
suspend fun getStories(
@Query("page") page: Int,
@Query("size") size: Int,
): List<StoryRemote>
@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<StoryRemote>
//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<MailRemote>
@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 }
}
}
}
}

View File

@ -0,0 +1,108 @@
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.api.model.toUser
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.OfflineUserRepository
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 userRepository: OfflineUserRepository,
private val database: MobileAppDataBase,
private val dbRemoteKeyRepository: RemoteKeysRepositoryImpl
) : RemoteMediator<Int, Story>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Story>
): 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 users = service.getUsers().map { it.toUser() }
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()
userRepository.clearUsers()
}
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)
userRepository.insertUsers(users)
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<Int, Story>): 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<Int, Story>): 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<Int, Story>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { storyUid ->
dbRemoteKeyRepository.getAllRemoteKeys(storyUid, RemoteKeyType.STORY)
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -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) },
)

View File

@ -0,0 +1,9 @@
package com.example.mobileapp.api.model
import kotlinx.serialization.Serializable
@Serializable
data class UserRemoteSignIn(
val login: String = "",
val password: String = "",
)

View File

@ -0,0 +1,64 @@
package com.example.mobileapp.api.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.mobileapp.MobileAppContainer
import com.example.mobileapp.api.ServerService
import com.example.mobileapp.api.model.toMail
import com.example.mobileapp.api.model.toMailRemote
import com.example.mobileapp.database.entities.Mail
import com.example.mobileapp.database.repositories.MailRepository
import kotlinx.coroutines.flow.Flow
class RestMailRepository(private var service: ServerService): MailRepository {
override fun getAllMails(): Flow<PagingData<Mail>> {
return Pager(
config = PagingConfig(
pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = { MailPagingSource(service) }
).flow
}
override suspend fun getMail(id: Int): Mail? = service.getMail(id).toMail()
override suspend fun insertMail(mail: Mail) {
service.createMail(mail.toMailRemote())
}
override suspend fun updateMail(mail: Mail) {
TODO("Not yet implemented")
}
override suspend fun deleteMail(mail: Mail) {
TODO("Not yet implemented")
}
}
class MailPagingSource(private val service: ServerService) : PagingSource<Int, Mail>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Mail> {
try {
val nextPageNumber = params.key ?: 1
val pageSize = params.loadSize
val response = service.getMails(nextPageNumber, pageSize)
val mails = response.map { it.toMail() } // Преобразование MailRemote в Mail
return LoadResult.Page(
data = mails,
prevKey = if (nextPageNumber == 1) null else nextPageNumber - 1,
nextKey = if (mails.isEmpty()) null else nextPageNumber + 1
)
} catch (exception: Exception) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, Mail>): Int? {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,118 @@
package com.example.mobileapp.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.mobileapp.MobileAppContainer
import com.example.mobileapp.api.ServerService
import com.example.mobileapp.api.ServiceRemoteMediator
import com.example.mobileapp.api.model.toMail
import com.example.mobileapp.api.model.toStory
import com.example.mobileapp.api.model.toStoryRemote
import com.example.mobileapp.database.MobileAppDataBase
import com.example.mobileapp.database.entities.Mail
import com.example.mobileapp.database.entities.Story
import com.example.mobileapp.database.repositories.OfflineStoryRepository
import com.example.mobileapp.database.repositories.OfflineUserRepository
import com.example.mobileapp.database.repositories.RemoteKeysRepositoryImpl
import com.example.mobileapp.database.repositories.StoryRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class RestStoryRepository(private var service: ServerService,
private val dbStoryRepository: OfflineStoryRepository,
private val dbUserRepository: OfflineUserRepository,
private val database: MobileAppDataBase,
private val dbRemoteKeyRepository: RemoteKeysRepositoryImpl
): StoryRepository {
override fun getAllStories(): Flow<PagingData<Story>> {
val pagingSourceFactory = {
dbStoryRepository.getAllStoriesPagingSource()
}
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = ServiceRemoteMediator(
service,
dbStoryRepository,
dbUserRepository,
database,
dbRemoteKeyRepository,
),
pagingSourceFactory = pagingSourceFactory
).flow
/*return Pager(
config = PagingConfig(
pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = { StoryPagingSource(service) }
).flow*/
}
override fun getStoriesByUserId(userId: Int): Flow<PagingData<Story>> {
return Pager(
config = PagingConfig(
pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = { StoryPagingSource(service, userId) }
).flow
}
override suspend fun getStoryById(id: Int): Story? = service.getStory(id).toStory()
override suspend fun insertStory(story: Story) {
service.createStory(story.toStoryRemote())
}
override suspend fun updateStory(story: Story) {
story.id?.let {
service.updateStory(it, story.toStoryRemote())
}
}
override suspend fun deleteStory(story: Story) {
try {
story.id?.let { this.service.deleteStory(it) }
dbStoryRepository.deleteStory(story)
}catch (ex: Exception){}
}
}
class StoryPagingSource(private val service: ServerService,
private val userId: Int? = null) : PagingSource<Int, Story>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Story> {
try {
val nextPageNumber = params.key ?: 1
val pageSize = params.loadSize
val response = if (userId != null) {
service.getUserStories(nextPageNumber, pageSize, userId)
} else {
service.getStories(nextPageNumber, pageSize)
}
val stories = response.map { it.toStory() }
return LoadResult.Page(
data = stories,
prevKey = if (nextPageNumber == 1) null else nextPageNumber - 1,
nextKey = if (stories.isEmpty()) null else nextPageNumber + 1
)
} catch (exception: Exception) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, Story>): Int? {
TODO("Not yet implemented")
}
}

View File

@ -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<List<User>> {
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")
}
}

View File

@ -1,5 +1,7 @@
package com.example.mobileapp.components
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
@ -170,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
})
@ -199,16 +202,20 @@ fun MailListItem(item: Mail, navController: NavHostController,
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
val isExpanded = remember {
mutableStateOf(false)
}
val userName = remember { mutableStateOf("") }
val userPhoto = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.post)) }
val userName = remember { mutableStateOf("UserName") }
LaunchedEffect(Unit){
userViewModel.getUser(item.userId).collect {
if (it != null) {
userName.value = it.email
val user = userViewModel.getUser(item.userId)
if (user != null) {
userName.value = user.email
if (user.photo != null){
userPhoto.value = user.photo
}
}
}
@ -234,7 +241,7 @@ fun MailListItem(item: Mail, navController: NavHostController,
Row(
verticalAlignment = Alignment.Top
){
Image(painter = painterResource(id = R.drawable.post),
Image(bitmap = userPhoto.value.asImageBitmap(),
contentDescription = "message",
contentScale = ContentScale.Crop,
modifier = Modifier

View File

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

View File

@ -17,7 +17,7 @@ interface MailDao {
fun getAll(): PagingSource<Int, Mail>
@Query("select * from mails where mails.id = :id")
fun getById(id: Int): Flow<Mail?>
suspend fun getById(id: Int): Mail?
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(mail: Mail)

View File

@ -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<RemoteKeys>)
@Query("DELETE FROM remote_keys WHERE type = :type")
suspend fun clearRemoteKeys(type: RemoteKeyType)
}

View File

@ -16,17 +16,20 @@ interface StoryDao {
fun getAll(): PagingSource<Int, Story>
@Query("select * from stories where stories.id = :id")
fun getById(id: Int): Flow<Story?>
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<Int, Story>
fun getByUserId(userId: Int): Flow<List<Story>>
@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()
}

View File

@ -15,17 +15,20 @@ interface UserDao {
fun getAll(): Flow<List<User>>
@Query("select * from users where users.id = :id")
fun getById(id: Int): Flow<User?>
suspend fun getById(id: Int): User?
@Query("select * from users where users.login = :login")
suspend fun getByLogin(login: String): User?
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(user: User)
suspend fun insert(vararg user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("delete from stories")
suspend fun deleteAll()
}

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
interface MailRepository {
fun getAllMails(): Flow<PagingData<Mail>>
fun getMail(id: Int): Flow<Mail?>
suspend fun getMail(id: Int): Mail?
suspend fun insertMail(mail: Mail)

View File

@ -23,7 +23,7 @@ class OfflineMailRepository(private val mailDao: MailDao): MailRepository {
).flow
}
override fun getMail(id: Int): Flow<Mail?> = mailDao.getById(id)
override suspend fun getMail(id: Int): Mail? = mailDao.getById(id)
override suspend fun insertMail(mail: Mail) = mailDao.insert(mail)

View File

@ -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<PagingData<Story>> {
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<Story?> = 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<Story>) =
storyDao.insert(*stories.toTypedArray())
fun getAllStoriesPagingSource(): PagingSource<Int, Story> = storyDao.getAll()
}

View File

@ -1,19 +1,25 @@
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.Story
import com.example.mobileapp.database.entities.User
import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao): UserRepository {
override fun getAllUsers(): Flow<List<User>> = userDao.getAll()
override fun getUser(id: Int): Flow<User?> = 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)
override suspend fun updateUser(user: User) = userDao.update(user)
override suspend fun deleteUser(user: User) = userDao.delete(user)
suspend fun clearUsers() = userDao.deleteAll()
suspend fun insertUsers(users: List<User>) =
userDao.insert(*users.toTypedArray())
}

View File

@ -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<RemoteKeys>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

@ -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<RemoteKeys>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -10,7 +10,7 @@ interface StoryRepository {
fun getStoriesByUserId(userId: Int): Flow<PagingData<Story>>
fun getStoryById(id: Int): Flow<Story?>
suspend fun getStoryById(id: Int): Story?
suspend fun insertStory(story: Story)

View File

@ -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<List<User>>
fun getUser(id: Int): Flow<User?>
suspend fun getUser(id: Int): User?
suspend fun getUserByLogin(login: String): User?
suspend fun getUserByLogin(user: UserRemoteSignIn): User?
suspend fun insertUser(user: User)

View File

@ -12,7 +12,7 @@ import kotlinx.coroutines.launch
class MailViewModel(private val mailRepository: MailRepository): ViewModel() {
val getAllMails: Flow<PagingData<Mail>> = mailRepository.getAllMails().cachedIn(viewModelScope)
fun getMail(id: Int): Flow<Mail?> = mailRepository.getMail(id)
suspend fun getMail(id: Int): Mail? = mailRepository.getMail(id)
fun insertMail(mail: Mail) = viewModelScope.launch {
mailRepository.insertMail(mail)

View File

@ -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<PagingData<Story>> = storyRepository.getAllStories().cachedIn(viewModelScope)
fun getStoryById(id: Int): Flow<Story?> = storyRepository.getStoryById(id)
suspend fun getStoryById(id: Int): Story? = storyRepository.getStoryById(id)
fun getStoriesByUserId(userId: Int): Flow<PagingData<Story>> = storyRepository.getStoriesByUserId(userId).cachedIn(viewModelScope)
val getStoriesByUserId: Flow<PagingData<Story>> = GlobalUser.getInstance().getUser()?.id?.let {
storyRepository.getStoriesByUserId(it).cachedIn(viewModelScope)
} ?: flowOf(PagingData.empty())
fun insertStory(story: Story) = viewModelScope.launch {
storyRepository.insertStory(story)

View File

@ -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<User?> = userRepository.getUser(id)
suspend fun getUser(id: Int): User? = userRepository.getUser(id)
fun updateUser(user: User) = viewModelScope.launch {
if (user.login.isEmpty()){
@ -34,10 +37,6 @@ class UserViewModel(private val userRepository: UserRepository): ViewModel() {
}
fun regUser(user: User) = viewModelScope.launch {
val globalUser = userRepository.getUserByLogin(user.login)
globalUser?.let {
return@launch
} ?: run {
if(user.password.isEmpty()){
return@launch
}
@ -46,12 +45,12 @@ class UserViewModel(private val userRepository: UserRepository): ViewModel() {
return@launch
}
userRepository.insertUser(user)
GlobalUser.getInstance().setUser(userRepository.getUserByLogin(user.login))
}
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)

View File

@ -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("") }

View File

@ -78,16 +78,11 @@ fun EditStoryScreen(navController: NavHostController, storyId: Int? = null,
LaunchedEffect(Unit) {
storyId?.let {
storyViewModel.getStoryById(storyId).collect {
if (it != null) {
cover.value = it.cover
}
if (it != null) {
title.value = it.title
}
if (it != null) {
description.value = it.description
}
val story = storyViewModel.getStoryById(storyId)
if (story != null) {
cover.value = story.cover
title.value = story.title
description.value = story.description
}
}
}

View File

@ -58,6 +58,5 @@ fun ListMailScreen(navController: NavHostController,
}
}
}
//DataListScroll(navController, mails)
}
}

View File

@ -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,14 +27,15 @@ 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()
Column(
modifier = Modifier
@ -50,6 +58,5 @@ fun ListStoryScreen(navController: NavHostController,
}
}
}
//DataListScroll(navController, stories)
}
}

View File

@ -54,12 +54,24 @@ fun StoryViewScreen(navController: NavHostController, storyId: Int,
val description = remember { mutableStateOf("") }
val postdate = remember { mutableStateOf<Long>(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(
@ -112,11 +124,12 @@ fun MailViewScreen(navController: NavHostController, mailId: Int,
val postdate = remember { mutableStateOf<Long>(0) }
LaunchedEffect(Unit){
mailViewModel.getMail(mailId).collect{
if (it != null) {
message.value = it.message
postdate.value = it.postdate!!
userViewModel.getUser(it.userId).collect {user ->
val mail = mailViewModel.getMail(mailId)
if (mail != null) {
message.value = mail.message
postdate.value = mail.postdate!!
}
val user = mail?.let { userViewModel.getUser(it.userId) }
if (user != null) {
if(user.photo != null) {
photo.value = user.photo
@ -124,9 +137,6 @@ fun MailViewScreen(navController: NavHostController, mailId: Int,
userName.value = user.email
}
}
}
}
}
Column(
modifier = Modifier