Compare commits

...

22 Commits

Author SHA1 Message Date
maxnes3
2fa34f5a82 Все отчёты 2023-12-25 23:34:25 +04:00
maxnes3
2e9c68705f Загрузил отчёт 2023-12-25 21:51:12 +04:00
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
maxnes3
fb7e7a1a43 Сдал 2023-12-09 11:21:16 +04:00
maxnes3
18d1ad1392 Осталось поправить мелочи 2023-12-09 10:42:23 +04:00
maxnes3
530972ba22 Готово, почти. Не факт что сдам 2023-12-09 04:43:14 +04:00
maxnes3
06f8248c75 Осталась пагинация с учёткой 2023-12-08 21:27:12 +04:00
maxnes3
2c1100863b CRUD почти есть, также работает регистрация и вход 2023-12-08 18:04:05 +04:00
maxnes3
69175e82f0 Подготовка к 4 лабе(дополнил) 2023-11-25 21:44:25 +04:00
maxnes3
7076d707ea Подготовка к 4 лабе 2023-11-25 21:43:54 +04:00
maxnes3
da844b15f7 CRUD работает 2023-11-25 03:02:32 +04:00
maxnes3
f4fa24f50a БД работает 2023-11-24 21:39:43 +04:00
maxnes3
1d1a0e9be7 ВСЁ на ГИТ 2023-11-24 20:22:29 +04:00
maxnes3
1e3fcaa50f Не работает 2023-11-24 19:45:20 +04:00
maxnes3
31b936fe0e ещё немного осталось 2023-11-16 04:17:27 +04:00
maxnes3
1a3a0d4cd7 Я почти гений 2023-11-16 03:06:38 +04:00
maxnes3
6fc39a2396 Я хорош 2023-11-16 02:01:29 +04:00
maxnes3
2567fc24a1 Подготовка к переходу 2023-11-15 23:59:54 +04:00
maxnes3
b45183ebcc Кое-что добавил 2023-11-15 22:21:16 +04:00
maxnes3
1cfd40ae3e Начал 3 лабу 2023-11-15 18:23:02 +04:00
66 changed files with 2447 additions and 369 deletions

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>

View File

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

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Программирование мобильных устройств
## ПИбд-32 || Бондаренко М.С.
## Отчёты загружены в папку reports/

View File

@ -1,6 +1,8 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
kotlin("plugin.serialization") version "1.4.21"
}
android {
@ -30,17 +32,17 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
kotlinCompilerExtensionVersion = "1.4.5"
}
packaging {
resources {
@ -67,4 +69,25 @@ 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"
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")
//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,8 +1,11 @@
<?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"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -11,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

@ -3,28 +3,13 @@ package com.example.mobileapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.mobileapp.entities.Mail
import com.example.mobileapp.entities.MailSingleton
import com.example.mobileapp.entities.Story
import com.example.mobileapp.entities.StorySingleton
import com.example.mobileapp.screens.Authorization
import com.example.mobileapp.screens.EditMailScreen
import com.example.mobileapp.screens.EditStoryScreen
import com.example.mobileapp.screens.ListDataScreen
import com.example.mobileapp.screens.ListMailScreen
import com.example.mobileapp.screens.MainScreen
import com.example.mobileapp.screens.Registration
import com.example.mobileapp.screens.SettingsScreen
import com.example.mobileapp.components.NavBar
import com.example.mobileapp.database.entities.User
import com.example.mobileapp.ui.theme.MobileAppTheme
class MainActivity : ComponentActivity() {
@ -38,57 +23,31 @@ class MainActivity : ComponentActivity() {
color = MaterialTheme.colorScheme.background
) {
val navController = rememberNavController()
val mailSingleton = MailSingleton()
mailSingleton.addMail(Mail(0, 0, "Дзюнзи Ито", "Выложил новый"))
mailSingleton.addMail(Mail(1, 1, "Стивен Кинг", "Меня отменили в Твиттере"))
mailSingleton.addMail(Mail(0, 0, "Дзюнзи Ито", "Выложил новый"))
mailSingleton.addMail(Mail(1, 1, "Стивен Кинг", "Меня отменили в Твиттере"))
mailSingleton.addMail(Mail(0, 0, "Дзюнзи Ито", "Выложил новый"))
mailSingleton.addMail(Mail(1, 1, "Стивен Кинг", "Меня отменили в Твиттере"))
mailSingleton.addMail(Mail(0, 0, "Дзюнзи Ито", "Выложил новый"))
mailSingleton.addMail(Mail(1, 1, "Стивен Кинг", "Меня отменили в Твиттере"))
val storySingleton = StorySingleton()
storySingleton.addStory(Story(0, "Чужак", "Знаменитая книга стивена кинга", R.drawable.king))
storySingleton.addStory(Story(1, "Переулок", "История ужасов от Дзюнзи Ито", R.drawable.dzun))
storySingleton.addStory(Story(2, "Чужак", "Знаменитая книга стивена кинга", R.drawable.king))
storySingleton.addStory(Story(3, "Переулок", "История ужасов от Дзюнзи Ито", R.drawable.dzun))
AppNavigation(navController = navController)
NavBar(navController = navController)
}
}
}
}
}
@Composable
fun AppNavigation(navController: NavHostController){
NavHost(
navController = navController, startDestination = "authorization"
) {
composable("authorization"){
Authorization(navController = navController)
}
composable("registration"){
Registration(navController = navController)
}
composable("main"){
MainScreen(navController = navController)
}
composable("listdata"){
ListDataScreen(navController = navController)
}
composable("mail"){
ListMailScreen(navController = navController)
}
composable("settings"){
SettingsScreen(navController = navController)
}
composable("editstory"){
EditStoryScreen(navController = navController)
}
composable("editmail"){
EditMailScreen(navController = navController)
class GlobalUser private constructor() {
private var user: User? = null
fun setUser(user: User?) {
this.user = user
}
fun getUser(): User? {
return user
}
companion object {
private var instance: GlobalUser? = null
fun getInstance(): GlobalUser {
return instance ?: synchronized(this) {
instance ?: GlobalUser().also { instance = it }
}
}
}
}

View File

@ -0,0 +1,12 @@
package com.example.mobileapp
import android.app.Application
class MobileApp: Application() {
lateinit var container: MobileAppContainer
override fun onCreate() {
super.onCreate()
container = MobileAppDataContainer(this)
}
}

View File

@ -0,0 +1,59 @@
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
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())
RestMailRepository(ServerService.getInstance())
}
override val storyRepository: StoryRepository by lazy {
//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())
RestUserRepository(ServerService.getInstance())
}
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

@ -27,18 +27,23 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.example.mobileapp.ui.theme.MobileAppTheme
val buttonHeightStandard = 72.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlaceholderInputField(label: String){
fun PlaceholderInputField(label: String, startValue: String? = null, isSingleLine: Boolean, onTextChanged: (String) -> Unit){
var text = remember { mutableStateOf("") }
startValue?.let{
text.value = startValue
}
OutlinedTextField(
value = text.value,
onValueChange = {
text.value = it
onTextChanged(it)
},
placeholder = {
Text(label)
@ -46,18 +51,24 @@ fun PlaceholderInputField(label: String){
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, start = 16.dp, bottom = 8.dp, end = 16.dp),
shape = RoundedCornerShape(10.dp))
shape = RoundedCornerShape(10.dp),
singleLine = isSingleLine
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PasswordInputField(label: String){
fun PasswordInputField(label: String, startValue: String? = null, onPasswordChanged: (String) -> Unit){
var text = remember { mutableStateOf("") }
startValue?.let{
text.value = startValue
}
OutlinedTextField(
value = text.value,
onValueChange = {
text.value = it
onPasswordChanged(it)
},
placeholder = {
Text(label)
@ -71,13 +82,14 @@ fun PasswordInputField(label: String){
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchInputField(){
fun SearchInputField(onTextChanged: (String) -> Unit){
var text = remember { mutableStateOf("") }
OutlinedTextField(
value = text.value,
onValueChange = {
text.value = it
onTextChanged(it)
},
leadingIcon = {
Icon(
@ -92,7 +104,8 @@ fun SearchInputField(){
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, start = 16.dp, bottom = 8.dp, end = 16.dp),
shape = RoundedCornerShape(10.dp)
shape = RoundedCornerShape(10.dp),
singleLine = true
)
}
@ -102,7 +115,7 @@ fun IconButton(iconLeft: ImageVector, label: String, backgroundColor: Color, tex
onClick = onClickAction,
modifier = Modifier
.fillMaxWidth()
.requiredHeight(64.dp)
.requiredHeight(buttonHeightStandard)
.padding(top = 8.dp, start = 16.dp, bottom = 8.dp, end = 16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = backgroundColor
@ -134,7 +147,7 @@ fun ActiveButton(label: String, backgroundColor: Color, textColor: Color, onClic
onClick = onClickAction,
modifier = Modifier
.fillMaxWidth()
.requiredHeight(64.dp)
.requiredHeight(buttonHeightStandard)
.padding(top = 8.dp, start = 16.dp, bottom = 8.dp, end = 16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = backgroundColor
@ -155,7 +168,7 @@ fun ActiveButton(label: String, backgroundColor: Color, textColor: Color, onClic
fun PlaceholderTextFieldPreview() {
MobileAppTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
PlaceholderInputField("Email")
PlaceholderInputField("Email", null,true, { })
}
}
}

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
@ -14,60 +16,92 @@ import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
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.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
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.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.res.painterResource
import androidx.compose.ui.text.font.FontWeight
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.NavHostController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemKey
import com.example.mobileapp.R
import com.example.mobileapp.entities.Mail
import com.example.mobileapp.entities.MailSingleton
import com.example.mobileapp.entities.Story
import com.example.mobileapp.entities.StorySingleton
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.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.StoryViewModel
import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.BackgroundItem2
import com.example.mobileapp.ui.theme.ButtonColor1
import com.example.mobileapp.ui.theme.ButtonColor2
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Date
val dateFormat = SimpleDateFormat("dd.MM.yyyy")
@Composable
fun DataListScroll(navController: NavHostController){
val storySingleton = StorySingleton()
val isFirstTime = remember { mutableStateOf(true) }
fun <T : Any> DataListScroll(navController: NavHostController, dataList: List<T>){
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
){
item {
addNewListItem(navController, "editstory")
when {
dataList.isListOf<Story>() -> addNewListItem(navController, "editstory")
dataList.isListOf<Mail>() -> addNewListItem(navController, "editmail")
}
}
items(storySingleton.getStoryList()){ item ->
DataListItem(item = item, navController = navController)
items(dataList){ item ->
when(item){
is Story -> StoryListItem(item = item, navController = navController)
is Mail -> MailListItem(item = item, navController = navController)
}
}
}
}
inline fun <reified T> List<*>.isListOf(): Boolean {
return isNotEmpty() && all { it is T }
}
@Composable
fun DataListItem(item: Story, navController: NavHostController){
fun StoryListItem(item: Story, navController: NavHostController,
isReadOnly: Boolean? = false,
storyViewModel: StoryViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val isExpanded = remember {
mutableStateOf(false)
}
val showDialog = remember {
mutableStateOf(false)
}
Card(
modifier = Modifier
.fillMaxWidth()
@ -89,7 +123,7 @@ fun DataListItem(item: Story, navController: NavHostController){
Row(
verticalAlignment = Alignment.Top
){
Image(painter = painterResource(id = item.cover),
Image(bitmap = item.cover.asImageBitmap(),
contentDescription = item.description,
contentScale = ContentScale.Crop,
modifier = Modifier
@ -99,7 +133,7 @@ fun DataListItem(item: Story, navController: NavHostController){
modifier = Modifier.padding(8.dp)
){
Text(
text = item.title,
text = "${item.title} | ${dateFormat.format(Date(item.postdate!!))}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
@ -110,16 +144,39 @@ fun DataListItem(item: Story, navController: NavHostController){
visible = isExpanded.value,
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
){
DataListItemButton("Изменить", ButtonColor2, Color.White, onClickAction = { navController.navigate("editstory") })
DataListItemButton("Удалить", Color.Red, Color.White, onClickAction = { })
if (isReadOnly!!){
DataListItemButton(label = "Подробнее", backgroundColor = ButtonColor2,
textColor = Color.White, onClickAction = {
navController.navigate("viewstory/${item.id}")
})
}
else{
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
){
DataListItemButton("Изменить", ButtonColor2, Color.White, onClickAction = {
navController.navigate("editstory/${item.id}")
})
DataListItemButton("Удалить", Color.Red, Color.White, onClickAction = {
showDialog.value = !showDialog.value
})
}
}
}
}
}
if(showDialog.value) {
DialogWindow(label = "Подтверждение",
message = "Вы уверены что хотите удалить запись?", onConfirmAction = {
storyViewModel.deleteStory(item)
showDialog.value = !showDialog.value
navController.navigate("story")
}, onDismissAction = {
showDialog.value = !showDialog.value
})
}
}
@Composable
@ -141,29 +198,28 @@ fun DataListItemButton(label: String, backgroundColor: Color, textColor: Color,
}
@Composable
fun MailListScroll(navController: NavHostController){
val mailSingleton = MailSingleton()
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
){
item {
addNewListItem(navController, "editmail")
}
items(mailSingleton.getMailList()){ item ->
MailListItem(item = item)
}
}
}
@Composable
fun MailListItem(item: Mail){
fun MailListItem(item: Mail, navController: NavHostController,
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
val isExpanded = remember {
mutableStateOf(false)
}
val userPhoto = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.post)) }
val userName = remember { mutableStateOf("UserName") }
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
.fillMaxWidth()
@ -185,7 +241,7 @@ fun MailListItem(item: Mail){
Row(
verticalAlignment = Alignment.Top
){
Image(painter = painterResource(id = R.drawable.post),
Image(bitmap = userPhoto.value.asImageBitmap(),
contentDescription = "message",
contentScale = ContentScale.Crop,
modifier = Modifier
@ -195,7 +251,7 @@ fun MailListItem(item: Mail){
modifier = Modifier.padding(8.dp)
){
Text(
text = item.username,
text = "${userName.value} | ${dateFormat.format(Date(item.postdate!!))}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold)
Text(text = item.message)
@ -205,22 +261,10 @@ fun MailListItem(item: Mail){
visible = isExpanded.value,
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = { /* Действие при нажатии кнопки */ },
modifier = Modifier
.requiredHeight(64.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = ButtonColor2
)
) {
Text(
text = "Подробнее",
color = Color.White,
fontSize = 18.sp,
)
}
DataListItemButton(label = "Подробнее", backgroundColor = ButtonColor2,
textColor = Color.White, onClickAction = {
navController.navigate("viewmail/${item.id}")
})
}
}
}
@ -265,3 +309,41 @@ fun addNewListItem(navController: NavHostController, destination: String){
}
}
}
@Composable
fun DialogWindow(label: String, message: String, onConfirmAction: () -> Unit, onDismissAction: () -> Unit){
AlertDialog(onDismissRequest = onDismissAction,
title = {
Text(text = label,
fontSize = 20.sp,
fontWeight = FontWeight.Bold)
},
text = {
Text(text = message)
},
confirmButton = {
Button(
onClick = onConfirmAction,
colors = ButtonDefaults.buttonColors(
containerColor = ButtonColor2
),
shape = RoundedCornerShape(5.dp)
) {
Text(text = "Подтвердить",
color = Color.White)
}
},
dismissButton = {
Button(
onClick = onDismissAction,
colors = ButtonDefaults.buttonColors(
containerColor = ButtonColor1
),
shape = RoundedCornerShape(5.dp)
) {
Text(text = "Отмена",
color = Color.Black)
}
}
)
}

View File

@ -0,0 +1,11 @@
package com.example.mobileapp.components
import androidx.annotation.DrawableRes
class NavBarItem(
val route: String,
val label: String,
@DrawableRes
val icon: Int
) {
}

View File

@ -1,9 +1,11 @@
package com.example.mobileapp.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
@ -12,16 +14,21 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
@ -30,40 +37,174 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.example.mobileapp.R
import com.example.mobileapp.screens.Authorization
import com.example.mobileapp.screens.EditMailScreen
import com.example.mobileapp.screens.EditStoryScreen
import com.example.mobileapp.screens.EditUserScreen
import com.example.mobileapp.screens.ListMailScreen
import com.example.mobileapp.screens.ListStoryScreen
import com.example.mobileapp.screens.MailViewScreen
import com.example.mobileapp.screens.MainScreen
import com.example.mobileapp.screens.Registration
import com.example.mobileapp.screens.SettingsScreen
import com.example.mobileapp.screens.StoryViewScreen
val navBarItems = listOf(
NavBarItem(route = "main", label = "Главная", icon = R.drawable.home),
NavBarItem(route = "story", label = "Создание", icon = R.drawable.edit),
NavBarItem(route = "mail", label = "Уведомления", icon = R.drawable.mail),
NavBarItem(route = "settings", label = "Настройки", icon = R.drawable.settings),
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavBar(navController: NavHostController, itemColorFilter: Color) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
NavItem(navController = navController, imageId = R.drawable.home,
description = "homeButton", destination = "main", itemColorFilter = itemColorFilter)
NavItem(navController = navController, imageId = R.drawable.edit,
description = "editButton", destination = "listdata", itemColorFilter = itemColorFilter)
NavItem(navController = navController, imageId = R.drawable.mail,
description = "mailButton", destination = "mail", itemColorFilter = itemColorFilter)
NavItem(navController = navController, imageId = R.drawable.settings,
description = "settingsButton", destination = "settings", itemColorFilter = itemColorFilter)
fun NavBar(navController: NavHostController) {
val topBarState = rememberSaveable { (mutableStateOf(false)) }
val bottomBarState = rememberSaveable { (mutableStateOf(false)) }
Scaffold(
topBar = {
AnimatedVisibility(
visible = topBarState.value,
enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }),
content = {
TopAppBar(
title = {
Text(
text = "Storyteller!",
textAlign = TextAlign.Center,
fontFamily = FontFamily(
Font(
R.font.roboto_regular, FontWeight.Bold
)
)
)
}
)
}
)
},
bottomBar = {
AnimatedVisibility(
visible = bottomBarState.value,
enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }),
content = {
NavigationBar {
navBarItems.forEach{item ->
NavigationBarItem(
icon = {
Image(
painter = painterResource(item.icon),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.padding(bottom = 6.dp)
)
},
label = {
Text(
text = item.label
)
},
onClick = {
navController.navigate(item.route)
},
selected = false,
modifier = Modifier.fillMaxSize()
)
}
}
}
)
},
modifier = Modifier.background(Color.White)
) {innerPaddings ->
NavHost(
navController = navController,
startDestination = "authorization",
modifier = Modifier.padding(innerPaddings)
) {
composable("authorization"){
topBarState.value = true
bottomBarState.value = false
Authorization(navController = navController)
}
composable("registration"){
topBarState.value = true
bottomBarState.value = false
Registration(navController = navController)
}
composable("main"){
topBarState.value = false
bottomBarState.value = true
MainScreen(navController = navController)
}
composable("story"){
topBarState.value = false
bottomBarState.value = true
ListStoryScreen(navController = navController)
}
composable("mail"){
topBarState.value = false
bottomBarState.value = true
ListMailScreen(navController = navController)
}
composable("settings"){
topBarState.value = true
bottomBarState.value = true
SettingsScreen(navController = navController)
}
composable("editstory"){ // Без аргумента
topBarState.value = false
bottomBarState.value = false
EditStoryScreen(navController = navController)
}
composable(
"editstory/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType }) //С аргументом
) { backStackEntry ->
backStackEntry.arguments?.let {
topBarState.value = false
bottomBarState.value = false
EditStoryScreen(navController = navController, storyId = it.getInt("id"))
}
}
composable("editmail"){ // Без аргумента
topBarState.value = false
bottomBarState.value = false
EditMailScreen(navController = navController)
}
composable("edituser"){
topBarState.value = false
bottomBarState.value = false
EditUserScreen(navController = navController)
}
composable(
"viewstory/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType }) //С аргументом
) { backStackEntry ->
backStackEntry.arguments?.let {
topBarState.value = false
bottomBarState.value = false
StoryViewScreen(navController = navController, storyId = it.getInt("id"))
}
}
composable(
"viewmail/{id}",
arguments = listOf(navArgument("id") { type = NavType.IntType }) //С аргументом
) { backStackEntry ->
backStackEntry.arguments?.let {
topBarState.value = false
bottomBarState.value = false
MailViewScreen(navController = navController, mailId = it.getInt("id"))
}
}
}
}
}
@Composable
fun NavItem(navController: NavHostController, imageId: Int,
description: String, destination: String, itemColorFilter: Color){
Image(painter = painterResource(id = imageId),
contentDescription = description,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(64.dp)
.clickable {
navController.navigate(destination)
},
colorFilter = ColorFilter.tint(itemColorFilter))
}
@Composable
fun NavigationButton(navController: NavHostController,
destination: String, label: String,
@ -74,7 +215,7 @@ fun NavigationButton(navController: NavHostController,
},
modifier = Modifier
.fillMaxWidth()
.requiredHeight(64.dp)
.requiredHeight(72.dp)
.padding(top = 8.dp, start = 16.dp, bottom = 8.dp, end = 16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = backgroundColor

View File

@ -0,0 +1,83 @@
package com.example.mobileapp.database
import android.content.Context
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.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, 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"
@Volatile
private var INSTANCE: MobileAppDataBase? = null
suspend fun initialDataBase(appContext: Context){
/*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"))
val storyDao = database.storyDao()
storyDao.insert(Story(title = "Переулок", description = "История ужасов от Дзюнзи Ито",
cover = BitmapFactory.decodeResource(appContext.resources, R.drawable.dzun), userId = 1))
storyDao.insert(Story(title = "Чужак", description = "Знаменитая книга стивена кинга",
cover = BitmapFactory.decodeResource(appContext.resources, R.drawable.king), userId = 2))
val mailDao = database.mailDao()
for (i in 0..50){
if (i % 2 == 0){
mailDao.insert(Mail(message = "Выложил новые страницы", userId = 1))
}
else{
mailDao.insert(Mail(message = "Меня отменили в Твиттере", userId = 2))
}
}
}*/
}
fun getInstance(appContext: Context): MobileAppDataBase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
appContext,
MobileAppDataBase::class.java,
DB_NAME
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
initialDataBase(appContext)
}
}
})
.fallbackToDestructiveMigration()
.build()
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,30 @@
package com.example.mobileapp.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.example.mobileapp.database.entities.Mail
import com.example.mobileapp.database.entities.Story
import kotlinx.coroutines.flow.Flow
@Dao
interface MailDao {
@Query("select * from mails order by id desc")
fun getAll(): PagingSource<Int, Mail>
@Query("select * from mails where mails.id = :id")
suspend fun getById(id: Int): Mail?
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(mail: Mail)
@Update
suspend fun update(mail: Mail)
@Delete
suspend fun delete(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

@ -0,0 +1,35 @@
package com.example.mobileapp.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.example.mobileapp.database.entities.Story
import kotlinx.coroutines.flow.Flow
@Dao
interface StoryDao {
@Query("select * from stories order by id desc")
fun getAll(): PagingSource<Int, Story>
@Query("select * from stories where stories.id = :id")
suspend fun getById(id: Int): Story?
@Query("select * from stories where stories.user_id = :userId order by stories.id desc")
fun getByUserId(userId: Int): Flow<List<Story>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
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

@ -0,0 +1,34 @@
package com.example.mobileapp.database.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.example.mobileapp.database.entities.User
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("select * from users")
fun getAll(): Flow<List<User>>
@Query("select * from users where users.id = :id")
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(vararg user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("delete from stories")
suspend fun deleteAll()
}

View File

@ -0,0 +1,20 @@
package com.example.mobileapp.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,34 @@
package com.example.mobileapp.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.util.Date
@Entity(
tableName = "mails",
foreignKeys = [
ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["user_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
]
)
data class Mail(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,
@ColumnInfo(name = "message")
val message: String,
@ColumnInfo(name = "postdate")
val postdate: Long? = Date().time,
@ColumnInfo(name="user_id")
val userId: Int
){
override fun hashCode(): Int {
return id ?: -1
}
}

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

@ -0,0 +1,39 @@
package com.example.mobileapp.database.entities
import android.graphics.Bitmap
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.util.Date
@Entity(
tableName = "stories",
foreignKeys = [
ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["user_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
]
)
data class Story(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "description")
val description: String,
@ColumnInfo(name = "cover")
val cover: Bitmap,
@ColumnInfo(name = "postdate")
val postdate: Long? = Date().time,
@ColumnInfo(name="user_id")
val userId: Int
){
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@ -0,0 +1,24 @@
package com.example.mobileapp.database.entities
import android.graphics.Bitmap
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,
@ColumnInfo(name = "login")
val login: String,
@ColumnInfo(name = "password")
val password: String,
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "photo")
val photo: Bitmap? = null
){
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@ -0,0 +1,17 @@
package com.example.mobileapp.database.repositories
import androidx.paging.PagingData
import com.example.mobileapp.database.entities.Mail
import kotlinx.coroutines.flow.Flow
interface MailRepository {
fun getAllMails(): Flow<PagingData<Mail>>
suspend fun getMail(id: Int): Mail?
suspend fun insertMail(mail: Mail)
suspend fun updateMail(mail: Mail)
suspend fun deleteMail(mail: Mail)
}

View File

@ -0,0 +1,33 @@
package com.example.mobileapp.database.repositories
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.mobileapp.database.dao.MailDao
import com.example.mobileapp.database.entities.Mail
import kotlinx.coroutines.flow.Flow
class OfflineMailRepository(private val mailDao: MailDao): MailRepository {
override fun getAllMails(): Flow<PagingData<Mail>> {
return Pager(
config = PagingConfig(
pageSize = 8,
prefetchDistance = 2,
enablePlaceholders = true,
initialLoadSize = 12,
maxSize = 24
),
pagingSourceFactory = {
mailDao.getAll()
}
).flow
}
override suspend fun getMail(id: Int): Mail? = mailDao.getById(id)
override suspend fun insertMail(mail: Mail) = mailDao.insert(mail)
override suspend fun updateMail(mail: Mail) = mailDao.update(mail)
override suspend fun deleteMail(mail: Mail) = mailDao.delete(mail)
}

View File

@ -0,0 +1,44 @@
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
class OfflineStoryRepository(private val storyDao: StoryDao): StoryRepository {
override fun getAllStories(): Flow<PagingData<Story>> {
return Pager(
config = PagingConfig(
pageSize = 5,
prefetchDistance = 1,
enablePlaceholders = true,
initialLoadSize = 10,
maxSize = 15
),
pagingSourceFactory = {
storyDao.getAll()
}
).flow
}
override fun getStoriesByUserId(userId: Int): Flow<PagingData<Story>> {
TODO("Not yet implemented")
}
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

@ -0,0 +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 suspend fun getUser(id: Int): User? = userDao.getById(id)
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

@ -0,0 +1,20 @@
package com.example.mobileapp.database.repositories
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.mobileapp.database.entities.Story
import kotlinx.coroutines.flow.Flow
interface StoryRepository {
fun getAllStories(): Flow<PagingData<Story>>
fun getStoriesByUserId(userId: Int): Flow<PagingData<Story>>
suspend fun getStoryById(id: Int): Story?
suspend fun insertStory(story: Story)
suspend fun updateStory(story: Story)
suspend fun deleteStory(story: Story)
}

View File

@ -0,0 +1,19 @@
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>>
suspend fun getUser(id: Int): User?
suspend fun getUserByLogin(user: UserRemoteSignIn): User?
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)
}

View File

@ -0,0 +1,28 @@
package com.example.mobileapp.database.viewmodels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.example.mobileapp.database.entities.Mail
import com.example.mobileapp.database.repositories.MailRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class MailViewModel(private val mailRepository: MailRepository): ViewModel() {
val getAllMails: Flow<PagingData<Mail>> = mailRepository.getAllMails().cachedIn(viewModelScope)
suspend fun getMail(id: Int): Mail? = mailRepository.getMail(id)
fun insertMail(mail: Mail) = viewModelScope.launch {
mailRepository.insertMail(mail)
}
fun updateMail(mail: Mail) = viewModelScope.launch {
mailRepository.updateMail(mail)
}
fun deleteMail(mail: Mail) = viewModelScope.launch {
mailRepository.deleteMail(mail)
}
}

View File

@ -0,0 +1,24 @@
package com.example.mobileapp.database.viewmodels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.mobileapp.MobileApp
object MobileAppViewModelProvider {
val Factory = viewModelFactory {
initializer {
MailViewModel(app().container.mailRepository)
}
initializer {
StoryViewModel(app().container.storyRepository)
}
initializer {
UserViewModel(app().container.userRepository)
}
}
}
fun CreationExtras.app(): MobileApp =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MobileApp)

View File

@ -0,0 +1,35 @@
package com.example.mobileapp.database.viewmodels
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)
suspend fun getStoryById(id: Int): Story? = storyRepository.getStoryById(id)
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)
}
fun updateStory(story: Story) = viewModelScope.launch {
storyRepository.updateStory(story)
}
fun deleteStory(story: Story) = viewModelScope.launch {
storyRepository.deleteStory(story)
}
}

View File

@ -0,0 +1,64 @@
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()
suspend fun getUser(id: Int): User? = userRepository.getUser(id)
fun updateUser(user: User) = viewModelScope.launch {
if (user.login.isEmpty()){
return@launch
}
if (user.email.isEmpty() || !isValidEmail(user.email)){
return@launch
}
if (user.password.isEmpty()){
return@launch
}
userRepository.updateUser(user)
GlobalUser.getInstance().setUser(user)
}
fun deleteUser(user: User) = viewModelScope.launch {
userRepository.deleteUser(user)
}
fun regUser(user: User) = viewModelScope.launch {
if(user.password.isEmpty()){
return@launch
}
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(UserRemoteSignIn(user.login, user.password))
globalUser?.let {
if (user.password.isNotEmpty() && user.password == globalUser.password){
GlobalUser.getInstance().setUser(globalUser)
}
}
}
private fun isValidEmail(email: String): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
}

View File

@ -1,8 +0,0 @@
package com.example.mobileapp.entities
data class Mail(
val id: Int,
val userId: Int,
val username: String,
val message: String
)

View File

@ -1,15 +0,0 @@
package com.example.mobileapp.entities
class MailSingleton {
companion object {
val mailList: MutableList<Mail> = mutableListOf()
}
fun addMail(mail: Mail) {
mailList.add(mail)
}
fun getMailList(): List<Mail> {
return mailList.toList()
}
}

View File

@ -1,8 +0,0 @@
package com.example.mobileapp.entities
data class Story(
val id: Int,
val title: String,
val description: String,
val cover: Int
)

View File

@ -1,15 +0,0 @@
package com.example.mobileapp.entities
class StorySingleton {
companion object {
val storyList: MutableList<Story> = mutableListOf()
}
fun addStory(story: Story) {
storyList.add(story)
}
fun getStoryList(): List<Story> {
return storyList.toList()
}
}

View File

@ -1,7 +0,0 @@
package com.example.mobileapp.entities
data class User(
val email: String,
val login: String,
val password: String
)

View File

@ -7,22 +7,44 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.mobileapp.R
import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.NavigationButton
import com.example.mobileapp.components.PasswordInputField
import com.example.mobileapp.components.PlaceholderInputField
import com.example.mobileapp.database.MobileAppDataBase
import com.example.mobileapp.database.entities.User
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor1
import com.example.mobileapp.ui.theme.ButtonColor2
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun Authorization(navController: NavHostController){
fun Authorization(navController: NavHostController,
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
//val users = userViewModel.getAllUsers.collectAsState(emptyList()).value
val login = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
@ -34,13 +56,28 @@ fun Authorization(navController: NavHostController){
contentDescription = "login",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.size(448.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
PlaceholderInputField(label = "Логин")
PasswordInputField(label = "Пароль")
NavigationButton(navController = navController, destination = "main", label = "Вход",
backgroundColor = ButtonColor2, textColor = Color.White)
PlaceholderInputField(label = "Логин", isSingleLine = true, onTextChanged = {newlogin ->
login.value = newlogin
})
PasswordInputField(label = "Пароль", onPasswordChanged = {newpassword ->
password.value = newpassword
})
ActiveButton(label = "Вход", backgroundColor = ButtonColor2,
textColor = Color.White, onClickAction = {
if (login.value.isNotEmpty() && password.value.isNotEmpty()) {
userViewModel.authUser(
User(
login = login.value,
password = password.value,
email = String()
)
)
navController.navigate("main")
}
})
NavigationButton(navController = navController, destination = "registration", label = "Регистрация",
backgroundColor = ButtonColor1, textColor = Color.Black)
}

View File

@ -1,29 +1,92 @@
package com.example.mobileapp.screens
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 androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.R
import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.NavigationButton
import com.example.mobileapp.components.PasswordInputField
import com.example.mobileapp.components.PlaceholderInputField
import com.example.mobileapp.database.entities.Mail
import com.example.mobileapp.database.entities.Story
import com.example.mobileapp.database.entities.User
import com.example.mobileapp.database.viewmodels.MailViewModel
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.StoryViewModel
import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor1
import com.example.mobileapp.ui.theme.ButtonColor2
@Composable
fun EditStoryScreen(navController: NavHostController) {
fun EditStoryScreen(navController: NavHostController, storyId: Int? = null,
storyViewModel: StoryViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
val cover = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.editplaceholder)) }
val title = remember { mutableStateOf("") }
val description = remember { mutableStateOf("") }
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) {
cover.value = MediaStore.Images
.Media.getBitmap(context.contentResolver, imageData.value)
} else {
val source = ImageDecoder
.createSource(context.contentResolver, imageData.value!!)
cover.value = ImageDecoder.decodeBitmap(source)
}
}
LaunchedEffect(Unit) {
storyId?.let {
val story = storyViewModel.getStoryById(storyId)
if (story != null) {
cover.value = story.cover
title.value = story.title
description.value = story.description
}
}
}
Column(
modifier = Modifier
.fillMaxSize()
@ -31,23 +94,59 @@ fun EditStoryScreen(navController: NavHostController) {
verticalArrangement = Arrangement.Bottom
) {
Image(
painter = painterResource(id = R.drawable.editplaceholder),
bitmap = cover.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.size(384.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
PlaceholderInputField(label = "Название")
ActiveButton(label = "Выбрать обложку", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {})
PlaceholderInputField(label = "Описание")
NavigationButton(navController = navController, destination = "listdata", label = "Назад",
ActiveButton(label = "Выбрать обложку", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
launcher.launch("image/*")
})
PlaceholderInputField(label = "Название", isSingleLine = true,
startValue = title.value, onTextChanged = { newName ->
title.value = newName
})
PlaceholderInputField(label = "Описание", isSingleLine = true,
startValue = description.value, onTextChanged = { newDescription ->
description.value = newDescription
})
ActiveButton(label = "Сохранить", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
storyId?.let {
storyViewModel.updateStory(
Story(
id = storyId,
cover = cover.value,
title = title.value,
description = description.value,
userId = GlobalUser.getInstance().getUser()?.id!!
)
)
} ?: run {
storyViewModel.insertStory(
Story(
cover = cover.value,
title = title.value,
description = description.value,
userId = GlobalUser.getInstance().getUser()?.id!!
)
)
}
navController.navigate("story")
})
NavigationButton(navController = navController, destination = "story", label = "Назад",
backgroundColor = ButtonColor2, textColor = Color.White)
}
}
@Composable
fun EditMailScreen(navController: NavHostController) {
fun EditMailScreen(navController: NavHostController,
mailViewModel: MailViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val message = remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
@ -62,9 +161,112 @@ fun EditMailScreen(navController: NavHostController) {
.size(512.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
PlaceholderInputField(label = "Заголовок")
PlaceholderInputField(label = "Текс поста")
PlaceholderInputField(label = "Текс поста", isSingleLine = false, onTextChanged = { newmessage ->
message.value = newmessage
})
ActiveButton(label = "Сохранить", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
mailViewModel.insertMail(
Mail(
message = message.value,
userId = GlobalUser.getInstance().getUser()?.id!!
)
)
navController.navigate("mail")
})
NavigationButton(navController = navController, destination = "mail", label = "Назад",
backgroundColor = ButtonColor2, textColor = Color.White)
}
}
@Composable
fun EditUserScreen(navController: NavHostController,
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
var userId = remember { mutableStateOf(0) }
val photo = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.photoplaceholder)) }
val login = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
val email = remember { mutableStateOf("") }
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) {
photo.value = MediaStore.Images
.Media.getBitmap(context.contentResolver, imageData.value)
} else {
val source = ImageDecoder
.createSource(context.contentResolver, imageData.value!!)
photo.value = ImageDecoder.decodeBitmap(source)
}
}
LaunchedEffect(Unit) {
GlobalUser.getInstance().getUser()?.let { user ->
if (user!!.photo != null)
photo.value = user!!.photo!!
userId.value = user!!.id!!
login.value = user!!.login
password.value = user!!.password
email.value = user!!.email
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Bottom
) {
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(8.dp)
.clip(CircleShape)
.size(384.dp)
.border(
width = 2.dp,
color = MaterialTheme.colorScheme.onPrimary,
)
.align(Alignment.CenterHorizontally))
ActiveButton(label = "Выбрать фото", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
launcher.launch("image/*")
})
PlaceholderInputField(label = "Никнейм", isSingleLine = true,
startValue = login.value, onTextChanged = { newLogin ->
login.value = newLogin
})
PlaceholderInputField(label = "Пароль", isSingleLine = true,
startValue = password.value, onTextChanged = { newPassword ->
password.value = newPassword
})
PlaceholderInputField(label = "Почта", isSingleLine = true,
startValue = email.value, onTextChanged = { newEmail ->
email.value = newEmail
})
ActiveButton(label = "Сохранить", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
userViewModel.updateUser(
User(
id = userId.value,
login = login.value,
password = password.value,
email = email.value,
photo = photo.value
)
)
navController.navigate("settings")
})
ActiveButton(label = "Назад", backgroundColor = ButtonColor2, textColor = Color.White,
onClickAction = {
navController.navigate("settings")
})
}
}

View File

@ -1,40 +0,0 @@
package com.example.mobileapp.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import com.example.mobileapp.components.DataListScroll
import com.example.mobileapp.components.NavBar
import com.example.mobileapp.ui.theme.BackgroundItem1
import com.example.mobileapp.ui.theme.BackgroundItem2
@Composable
fun ListDataScreen(navController: NavHostController){
Column(
modifier = Modifier
.fillMaxSize()
.background(BackgroundItem1)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.9f)
){
DataListScroll(navController)
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
NavBar(navController = navController, itemColorFilter = BackgroundItem2)
}
}
}

View File

@ -1,40 +1,62 @@
package com.example.mobileapp.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
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.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.mobileapp.components.MailListScroll
import com.example.mobileapp.components.NavBar
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.example.mobileapp.components.DataListScroll
import com.example.mobileapp.components.MailListItem
import com.example.mobileapp.components.StoryListItem
import com.example.mobileapp.components.addNewListItem
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.viewmodels.MailViewModel
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.ui.theme.BackgroundItem1
import com.example.mobileapp.ui.theme.BackgroundItem2
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext
@Composable
fun ListMailScreen(navController: NavHostController){
fun ListMailScreen(navController: NavHostController,
mailViewModel: MailViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val mails = mailViewModel.getAllMails.collectAsLazyPagingItems()
Column(
modifier = Modifier
.fillMaxSize()
.background(BackgroundItem1)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.9f)
){
MailListScroll(navController)
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center
LazyVerticalGrid(
columns = GridCells.Fixed(1)
) {
NavBar(navController = navController, itemColorFilter = BackgroundItem2)
item {
addNewListItem(navController, "editmail")
}
items(
count = mails.itemCount,
key = mails.itemKey { item -> item.id!! }
) { index: Int ->
val mail: Mail? = mails[index]
if (mail != null) {
MailListItem(item = mail, navController = navController)
}
}
}
}
}

View File

@ -0,0 +1,62 @@
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
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.components.DataListScroll
import com.example.mobileapp.components.StoryListItem
import com.example.mobileapp.components.addNewListItem
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.collectAsLazyPagingItems()
Column(
modifier = Modifier
.fillMaxSize()
.background(BackgroundItem1)
) {
LazyVerticalGrid(
columns = GridCells.Fixed(1)
) {
item {
addNewListItem(navController, "editstory")
}
items(
count = stories.itemCount,
key = stories.itemKey { item -> item.id!! }
) { index: Int ->
val story: Story? = stories[index]
if (story != null) {
StoryListItem(item = story, navController = navController)
}
}
}
}
}

View File

@ -8,8 +8,12 @@ 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.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -18,45 +22,69 @@ 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 androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.example.mobileapp.R
import com.example.mobileapp.components.NavBar
import com.example.mobileapp.components.SearchInputField
import com.example.mobileapp.components.StoryListItem
import com.example.mobileapp.components.addNewListItem
import com.example.mobileapp.database.entities.Story
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.StoryViewModel
@Composable
fun MainScreen(navController: NavHostController) {
fun MainScreen(navController: NavHostController,
storyViewModel: StoryViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val stories = storyViewModel.getAllStories.collectAsLazyPagingItems()
val search = remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
) {
SearchInputField()
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.89f),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.main),
contentDescription = "main",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.padding(8.dp)
)
Text(
text = "Здесь будут посты авторов",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
SearchInputField(onTextChanged = {newsearch ->
search.value = newsearch
})
if (stories.itemCount > 0){
LazyVerticalGrid(
columns = GridCells.Fixed(1)
) {
items(
count = stories.itemCount,
key = stories.itemKey { item -> item.id!! }
) { index: Int ->
val story: Story? = stories[index]
if (story != null && (search.value.isEmpty() || story.title.contains(search.value, ignoreCase = true))) {
StoryListItem(item = story, navController = navController, isReadOnly = true)
}
}
}
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
NavBar(navController = navController, itemColorFilter = Color.Black)
else {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.main),
contentDescription = "main",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.padding(8.dp)
)
Text(
text = "Здесь будут посты авторов",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
}
}
}

View File

@ -7,22 +7,37 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.mobileapp.R
import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.NavigationButton
import com.example.mobileapp.components.PasswordInputField
import com.example.mobileapp.components.PlaceholderInputField
import com.example.mobileapp.database.entities.User
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor1
import com.example.mobileapp.ui.theme.ButtonColor2
@Composable
fun Registration(navController: NavHostController){
fun Registration(navController: NavHostController,
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val login = remember { mutableStateOf("") }
val email = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
val repeatepassword = remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
@ -34,15 +49,34 @@ fun Registration(navController: NavHostController){
contentDescription = "registration",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(384.dp)
.size(320.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
PlaceholderInputField(label = "Логин")
PlaceholderInputField(label = "Email")
PasswordInputField(label = "Пароль")
PasswordInputField(label = "Пароль ещё раз")
NavigationButton(navController = navController, destination = "main",
label = "Зарегистрироваться", backgroundColor = ButtonColor2, textColor = Color.White)
PlaceholderInputField(label = "Логин", isSingleLine = true, onTextChanged = {newlogin ->
login.value = newlogin
})
PlaceholderInputField(label = "Email", isSingleLine = true, onTextChanged = {newemail ->
email.value = newemail
})
PasswordInputField(label = "Пароль", onPasswordChanged = {newpassword ->
password.value = newpassword
})
PasswordInputField(label = "Пароль ещё раз", onPasswordChanged = {newpassword ->
repeatepassword.value = newpassword
})
ActiveButton(label = "Зарегистрироваться", backgroundColor = ButtonColor2,
textColor = Color.White, onClickAction = {
if (password.value == repeatepassword.value){
userViewModel.regUser(
User(
login = login.value,
password = password.value,
email = email.value
)
)
}
navController.navigate("main")
})
NavigationButton(navController = navController, destination = "authorization",
label = "Назад", backgroundColor = ButtonColor1, textColor = Color.Black)
}

View File

@ -27,6 +27,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.R
import com.example.mobileapp.components.IconButton
import com.example.mobileapp.components.NavBar
@ -37,39 +38,37 @@ import com.example.mobileapp.ui.theme.ButtonColor2
fun SettingsScreen(navController: NavHostController){
Column(
modifier = Modifier
.fillMaxSize()
.fillMaxSize(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
Image(
painter = painterResource(id = R.drawable.settingsplaceholder),
contentDescription = "settings",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.9f),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
){
Image(
painter = painterResource(id = R.drawable.settingsplaceholder),
contentDescription = "settings",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(384.dp)
.padding(8.dp))
IconButton(iconLeft = Icons.Default.AccountCircle, label = "Учётная запись",
backgroundColor = ButtonColor2, textColor = Color.White, onClickAction = { })
IconButton(iconLeft = Icons.Default.Face, label = "Внешний вид",
backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = { })
IconButton(iconLeft = Icons.Default.Share, label = "Контакты",
backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = { })
IconButton(iconLeft = Icons.Default.Info, label = "О приложении",
backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = { })
IconButton(iconLeft = Icons.Default.ExitToApp, label = "Выйти",
backgroundColor = Color.Red, textColor = Color.White, onClickAction = { })
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
NavBar(navController = navController, itemColorFilter = Color.Black)
}
.size(320.dp)
.padding(8.dp))
IconButton(iconLeft = Icons.Default.AccountCircle, label = "Учётная запись",
backgroundColor = ButtonColor2, textColor = Color.White, onClickAction = {
navController.navigate("edituser")
})
IconButton(iconLeft = Icons.Default.Face, label = "Внешний вид",
backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
})
IconButton(iconLeft = Icons.Default.Share, label = "Контакты",
backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
})
IconButton(iconLeft = Icons.Default.Info, label = "О приложении",
backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
})
IconButton(iconLeft = Icons.Default.ExitToApp, label = "Выйти",
backgroundColor = Color.Red, textColor = Color.White, onClickAction = {
GlobalUser.getInstance().setUser(null)
navController.navigate("authorization")
})
}
}

View File

@ -0,0 +1,178 @@
package com.example.mobileapp.screens
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.mobileapp.R
import com.example.mobileapp.components.NavigationButton
import com.example.mobileapp.database.viewmodels.MailViewModel
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.StoryViewModel
import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor2
import java.text.SimpleDateFormat
import java.util.Date
val dateFormat = SimpleDateFormat("dd.MM.yyyy")
@Composable
fun StoryViewScreen(navController: NavHostController, storyId: Int,
storyViewModel: StoryViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
val cover = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.editplaceholder)) }
val title = remember { mutableStateOf("") }
val description = remember { mutableStateOf("") }
val postdate = remember { mutableStateOf<Long>(0) }
/*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(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Center
) {
Image(
bitmap = cover.value.asImageBitmap(),
contentDescription = "cover",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
Text(text = "Название: ${title.value}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Text(text = "Дата публикации: ${dateFormat.format(Date(postdate.value))}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Text(text = "Описание: ${description.value}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
NavigationButton(navController = navController, destination = "main", label = "Назад",
backgroundColor = ButtonColor2, textColor = Color.White)
}
}
@Composable
fun MailViewScreen(navController: NavHostController, mailId: Int,
mailViewModel: MailViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
),
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
val userName = remember { mutableStateOf("") }
val photo = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.photoplaceholder)) }
val message = remember { mutableStateOf("") }
val postdate = remember { mutableStateOf<Long>(0) }
LaunchedEffect(Unit){
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
}
userName.value = user.email
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Center
) {
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(8.dp)
.clip(CircleShape)
.size(384.dp)
.border(
width = 2.dp,
color = MaterialTheme.colorScheme.onPrimary,
)
.align(Alignment.CenterHorizontally))
Text(text = "Автор: ${userName.value}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Text(text = "Дата публикации: ${dateFormat.format(Date(postdate.value))}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Text(text = "Текст: ${message.value}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
NavigationButton(navController = navController, destination = "mail", label = "Назад",
backgroundColor = ButtonColor2, textColor = Color.White)
}
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>

View File

@ -2,4 +2,5 @@
plugins {
id("com.android.application") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.