Compare commits

...

5 Commits

Author SHA1 Message Date
maxnes3
ba81ea4ed2 Улучшил report 2023-12-28 13:35:16 +04:00
maxnes3
a29f8803ae I am pdfile 2023-12-28 03:14:59 +04:00
maxnes3
7baad4c44e Yes, i'am the badAssss 2023-12-27 20:56:03 +04:00
maxnes3
153661a5f0 Yes, i'am the best 2023-12-27 20:10:22 +04:00
maxnes3
59e5f2b267 Осталось дизайн получше сделать 2023-12-27 17:28:40 +04:00
22 changed files with 727 additions and 211 deletions

View File

@ -6,6 +6,7 @@ import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
import com.example.mobileapp.components.NavBar
@ -31,14 +32,14 @@ class MainActivity : ComponentActivity() {
}
class GlobalUser private constructor() {
private var user: User? = null
private var user = mutableStateOf<User?>(null)
fun setUser(user: User?) {
this.user = user
this.user.value = user
}
fun getUser(): User? {
return user
return user.value
}
companion object {

View File

@ -0,0 +1,5 @@
package com.example.mobileapp.api
enum class ApiStatus {
LOADING, ERROR, DONE, NONE
}

View File

@ -1,6 +1,7 @@
package com.example.mobileapp.api
import com.example.mobileapp.api.model.MailRemote
import com.example.mobileapp.api.model.ReportRemote
import com.example.mobileapp.api.model.StoryRemote
import com.example.mobileapp.api.model.UserRemote
import com.example.mobileapp.api.model.UserRemoteSignIn
@ -105,6 +106,13 @@ interface ServerService {
@Path("id") id: Int
)
//REPORT
@GET("report/createReport/{dateFrom}/{dateTo}")
suspend fun createReport(
@Path("dateFrom") dateFrom: Long,
@Path("dateTo") dateTo: Long
): ReportRemote
//INSTANCE
companion object {
private const val BASE_URL = "https://7hz21fz1-8080.euw.devtunnels.ms/api/"

View File

@ -5,6 +5,7 @@ import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.api.model.toStory
import com.example.mobileapp.api.model.toUser
import com.example.mobileapp.database.MobileAppDataBase
@ -18,7 +19,7 @@ import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class ServiceRemoteMediator(private val service: ServerService,
class StoryRemoteMediator(private val service: ServerService,
private val storyRepository: OfflineStoryRepository,
private val userRepository: OfflineUserRepository,
private val database: MobileAppDataBase,
@ -51,8 +52,8 @@ class ServiceRemoteMediator(private val service: ServerService,
}
try {
val users = service.getUsers().map { it.toUser() }
val stories = service.getStories(page, state.config.pageSize).map { it.toStory() }
val user = GlobalUser.getInstance().getUser()
val stories = service.getUserStories(page, state.config.pageSize, user!!.id!!).map { it.toStory() }
val endOfPaginationReached = stories.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
@ -71,7 +72,7 @@ class ServiceRemoteMediator(private val service: ServerService,
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
userRepository.insertUsers(users)
userRepository.insertUser(user)
storyRepository.insertStories(stories)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)

View File

@ -8,10 +8,17 @@ import java.io.ByteArrayOutputStream
class RemoteConverters {
companion object {
private const val CHARSET_UTF8 = "UTF-8"
private const val MAX_IMAGE_SIZE = 1024 // Размер в килобайтах, который вы хотите использовать
fun fromBitmap(bitmap: Bitmap): String {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
val extendedBitmap = scaleRatioBitmap(bitmap)
// Сжимаем изображение до указанного максимального размера
val quality = calculateQuality(extendedBitmap)
extendedBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
val byteArray = outputStream.toByteArray()
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
}
@ -20,5 +27,36 @@ class RemoteConverters {
val byteArray = Base64.decode(base64String, Base64.NO_WRAP)
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
private fun calculateQuality(bitmap: Bitmap): Int {
val outputStream = ByteArrayOutputStream()
// Уменьшаем качество изображения, пока его размер не станет меньше максимального
var quality = 100
while (outputStream.size() / 1024 > MAX_IMAGE_SIZE && quality > 0) {
outputStream.reset()
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
quality -= 10
}
// Возвращаем качество, при котором размер изображения удовлетворяет ограничению
return if (quality < 0) 0 else quality
}
private fun scaleRatioBitmap(bitmap: Bitmap): Bitmap {
val maxWidth = 990
val maxHeight = 990
if (bitmap.width > maxWidth || bitmap.height > maxHeight) {
// Если размер превышает максимальный, масштабируем изображение
val ratio = Math.min(maxWidth.toFloat() / bitmap.width,
maxHeight.toFloat() / bitmap.height)
val width = (ratio * bitmap.width).toInt()
val height = (ratio * bitmap.height).toInt()
return Bitmap.createScaledBitmap(bitmap, width, height, true)
}
return bitmap
}
}
}

View File

@ -2,9 +2,12 @@ package com.example.mobileapp.api.model
import kotlinx.serialization.Serializable
/*
@Serializable
data class ReportRemote(
val dateFrom: Long,
val dateTo: Long,
val postCount: Int,
val mostPostAuthor: UserRemote,
val mostPostCount: Int,
val listPostAuthor: List<StoryRemote>
)
*/

View File

@ -1,8 +1,9 @@
package com.example.mobileapp.api.repository
import com.example.mobileapp.api.ServerService
import com.example.mobileapp.api.model.ReportRemote
import com.example.mobileapp.database.repositories.ReportRepository
class RestReportRepository(private var service: ServerService): ReportRepository {
override suspend fun createReport(dateFrom: Long, dateTo: Long): ReportRemote = service.createReport(dateFrom, dateTo)
}

View File

@ -8,19 +8,16 @@ 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.StoryRemoteMediator
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,
@ -30,7 +27,7 @@ class RestStoryRepository(private var service: ServerService,
): StoryRepository {
override fun getAllStories(): Flow<PagingData<Story>> {
val pagingSourceFactory = {
/*val pagingSourceFactory = {
dbStoryRepository.getAllStoriesPagingSource()
}
@ -40,7 +37,36 @@ class RestStoryRepository(private var service: ServerService,
pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = ServiceRemoteMediator(
remoteMediator = StoryRemoteMediator(
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>> {
val pagingSourceFactory = {
dbStoryRepository.getUserStoriesPagingSource(userId)
}
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = StoryRemoteMediator(
service,
dbStoryRepository,
dbUserRepository,
@ -50,22 +76,12 @@ class RestStoryRepository(private var service: ServerService,
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
).flow*/
}
override suspend fun getStoryById(id: Int): Story? = service.getStory(id).toStory()

View File

@ -1,5 +1,8 @@
package com.example.mobileapp.components
import android.app.DatePickerDialog
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@ -19,15 +22,21 @@ 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
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
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 com.example.mobileapp.ui.theme.ButtonColor1
import com.example.mobileapp.ui.theme.MobileAppTheme
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
val buttonHeightStandard = 72.dp
@ -163,6 +172,52 @@ fun ActiveButton(label: String, backgroundColor: Color, textColor: Color, onClic
}
}
@Composable
fun DatePicker(startValue: Long? = null, onDateSelected: (Long) -> Unit) {
val context = LocalContext.current
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
val dateFormatter = remember { SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()) }
val selectedDate = remember { mutableStateOf<Long>(0) }
startValue?.let {
selectedDate.value = startValue
}
val datePickerDialog = remember { mutableStateOf(DatePickerDialog(context)) }
Column(
modifier = Modifier
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Selected Date: ${dateFormatter.format(selectedDate.value)}")
ActiveButton(label = "Выбрать дату", backgroundColor = ButtonColor1,
textColor = Color.Black, onClickAction = {
datePickerDialog.value = DatePickerDialog(
context,
{ _, year: Int, month: Int, dayOfMonth: Int ->
val selectedDateInMillis = Calendar.getInstance().apply {
set(year, month, dayOfMonth)
}.timeInMillis
selectedDate.value = selectedDateInMillis
onDateSelected(selectedDateInMillis)
},
year,
month,
day
)
datePickerDialog.value.show()
})
}
}
@Preview(showBackground = true)
@Composable
fun PlaceholderTextFieldPreview() {

View File

@ -25,6 +25,7 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -32,6 +33,7 @@ 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
@ -246,7 +248,8 @@ fun MailListItem(item: Mail, navController: NavHostController,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(96.dp)
.padding(8.dp))
.padding(8.dp)
.clip(RoundedCornerShape(16.dp)))
Column(
modifier = Modifier.padding(8.dp)
){
@ -281,7 +284,7 @@ fun addNewListItem(navController: NavHostController, destination: String){
},
shape = RoundedCornerShape(15.dp),
colors = CardDefaults.cardColors(
containerColor = BackgroundItem2
containerColor = Color.White
),
elevation = CardDefaults.cardElevation(
defaultElevation = 8.dp
@ -304,7 +307,9 @@ fun addNewListItem(navController: NavHostController, destination: String){
text = "Добавить",
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = 32.dp))
modifier = Modifier.padding(start = 32.dp),
color = Color.Black
)
}
}
}

View File

@ -22,6 +22,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
@ -53,8 +54,9 @@ 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),
NavBarItem(route = "mail", label = "Почта", icon = R.drawable.mail),
NavBarItem(route = "report", label = "Отчёт", icon = R.drawable.report),
NavBarItem(route = "settings", label = "Настройки", icon = R.drawable.settings)
)
@OptIn(ExperimentalMaterial3Api::class)
@ -69,8 +71,10 @@ fun NavBar(navController: NavHostController) {
visible = topBarState.value,
enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }),
content = {
TopAppBar(
modifier = Modifier.fillMaxWidth(),
title = {
Text(
text = "Storyteller!",
@ -79,7 +83,8 @@ fun NavBar(navController: NavHostController) {
Font(
R.font.roboto_regular, FontWeight.Bold
)
)
),
color = Color.Black
)
}
)
@ -204,7 +209,7 @@ fun NavBar(navController: NavHostController) {
}
composable("report"){
topBarState.value = false
bottomBarState.value = false
bottomBarState.value = true
ReportScreen(navController = navController)
}
}

View File

@ -19,7 +19,7 @@ interface StoryDao {
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>>
fun getByUserId(userId: Int): PagingSource<Int, Story>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(vararg story: Story)

View File

@ -25,7 +25,18 @@ class OfflineStoryRepository(private val storyDao: StoryDao): StoryRepository {
}
override fun getStoriesByUserId(userId: Int): Flow<PagingData<Story>> {
TODO("Not yet implemented")
return Pager(
config = PagingConfig(
pageSize = 5,
prefetchDistance = 1,
enablePlaceholders = true,
initialLoadSize = 10,
maxSize = 15
),
pagingSourceFactory = {
storyDao.getByUserId(userId)
}
).flow
}
override suspend fun getStoryById(id: Int): Story? = storyDao.getById(id)
@ -41,4 +52,6 @@ class OfflineStoryRepository(private val storyDao: StoryDao): StoryRepository {
storyDao.insert(*stories.toTypedArray())
fun getAllStoriesPagingSource(): PagingSource<Int, Story> = storyDao.getAll()
fun getUserStoriesPagingSource(userId: Int): PagingSource<Int, Story> = storyDao.getByUserId(userId)
}

View File

@ -1,4 +1,7 @@
package com.example.mobileapp.database.repositories
import com.example.mobileapp.api.model.ReportRemote
interface ReportRepository {
suspend fun createReport(dateFrom: Long, dateTo: Long): ReportRemote
}

View File

@ -0,0 +1,75 @@
package com.example.mobileapp.database.viewmodels
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.mobileapp.api.ApiStatus
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
open class CustomViewModel : ViewModel() {
var apiStatus by mutableStateOf(ApiStatus.NONE)
private set
var apiError by mutableStateOf("")
private set
fun runInScope(
actionSuccess: suspend () -> Unit,
actionError: suspend () -> Unit
) {
viewModelScope.launch {
apiStatus = ApiStatus.LOADING
runCatching {
actionSuccess()
apiStatus = ApiStatus.DONE
apiError = ""
}.onFailure { e: Throwable ->
when (e) {
is IOException,
is HttpException -> {
actionError()
apiStatus = ApiStatus.ERROR
apiError = e.localizedMessage ?: e.toString()
}
else -> throw e
}
}
}
}
fun runInScope(actionSuccess: suspend () -> Unit) {
runInScope(actionSuccess, actionError = {})
}
fun clearStatus(){
apiStatus = ApiStatus.NONE
}
}
@Composable
fun LoadingScreen(color: Color) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
color = color,
strokeWidth = 6.dp
)
}
}

View File

@ -1,7 +1,22 @@
package com.example.mobileapp.database.viewmodels
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.mobileapp.api.model.ReportRemote
import com.example.mobileapp.database.repositories.ReportRepository
import kotlinx.coroutines.launch
class ReportViewModel(private val reportRepository: ReportRepository): ViewModel() {
private var _report = mutableStateOf<ReportRemote?>(null)
val report: MutableState<ReportRemote?> get() = _report
fun createReport(dateFrom: Long, dateTo: Long) = viewModelScope.launch {
_report.value = reportRepository.createReport(dateFrom, dateTo)
}
fun clearReport(){
_report.value = null
}
}

View File

@ -11,7 +11,7 @@ import com.example.mobileapp.database.repositories.UserRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class UserViewModel(private val userRepository: UserRepository): ViewModel() {
class UserViewModel(private val userRepository: UserRepository): CustomViewModel() {
//val getAllUsers = userRepository.getAllUsers()
suspend fun getUser(id: Int): User? = userRepository.getUser(id)
@ -28,9 +28,13 @@ class UserViewModel(private val userRepository: UserRepository): ViewModel() {
if (user.password.isEmpty()){
return@launch
}
runInScope(
actionSuccess = {
userRepository.updateUser(user)
GlobalUser.getInstance().setUser(user)
}
)
}
fun deleteUser(user: User) = viewModelScope.launch {
userRepository.deleteUser(user)
@ -44,18 +48,38 @@ class UserViewModel(private val userRepository: UserRepository): ViewModel() {
if(user.email.isEmpty() || !isValidEmail(user.email)){
return@launch
}
runInScope(
actionSuccess = {
userRepository.insertUser(user)
GlobalUser.getInstance().setUser(userRepository.getUserByLogin(
UserRemoteSignIn(user.login, user.password)))
}
)
/*userRepository.insertUser(user)
GlobalUser.getInstance().setUser(userRepository.getUserByLogin(
UserRemoteSignIn(user.login, user.password)))*/
}
fun authUser(user: User) = viewModelScope.launch {
runInScope(
actionSuccess = {
val globalUser = userRepository.getUserByLogin(UserRemoteSignIn(user.login, user.password))
globalUser?.let {
if (user.password.isNotEmpty() && user.password == globalUser.password){
GlobalUser.getInstance().setUser(globalUser)
}
}
},
actionError = {
GlobalUser.getInstance().setUser(null)
}
)
/*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 {

View File

@ -1,5 +1,6 @@
package com.example.mobileapp.screens
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -21,13 +22,16 @@ 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.api.ApiStatus
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.LoadingScreen
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor1
@ -40,11 +44,23 @@ fun Authorization(navController: NavHostController,
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
//val users = userViewModel.getAllUsers.collectAsState(emptyList()).value
val context = LocalContext.current
when(userViewModel.apiStatus){
ApiStatus.DONE -> {
navController.navigate("main")
userViewModel.clearStatus()
}
ApiStatus.LOADING -> LoadingScreen(ButtonColor2)
ApiStatus.ERROR -> Toast.makeText(context, "Не верные данные или пользователя не существует: "
+ userViewModel.apiError, Toast.LENGTH_SHORT).show()
else -> {}
}
val login = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
if(userViewModel.apiStatus != ApiStatus.LOADING) {
Column(
modifier = Modifier
.fillMaxSize()
@ -58,8 +74,12 @@ fun Authorization(navController: NavHostController,
modifier = Modifier
.size(448.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
PlaceholderInputField(label = "Логин", isSingleLine = true, onTextChanged = {newlogin ->
.align(Alignment.CenterHorizontally)
)
PlaceholderInputField(
label = "Логин",
isSingleLine = true,
onTextChanged = { newlogin ->
login.value = newlogin
})
PasswordInputField(label = "Пароль", onPasswordChanged = { newpassword ->
@ -75,10 +95,12 @@ fun Authorization(navController: NavHostController,
email = String()
)
)
navController.navigate("main")
}
})
NavigationButton(navController = navController, destination = "registration", label = "Регистрация",
backgroundColor = ButtonColor1, textColor = Color.Black)
NavigationButton(
navController = navController, destination = "registration", label = "Регистрация",
backgroundColor = ButtonColor1, textColor = Color.Black
)
}
}
}

View File

@ -6,16 +6,22 @@ import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -25,8 +31,11 @@ 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.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
@ -36,12 +45,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.R
import com.example.mobileapp.api.ApiStatus
import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.NavigationButton
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.LoadingScreen
import com.example.mobileapp.database.viewmodels.MailViewModel
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.StoryViewModel
@ -90,17 +101,32 @@ fun EditStoryScreen(navController: NavHostController, storyId: Int? = null,
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Bottom
.padding(bottom = 8.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Bottom,
) {
Box(
modifier = Modifier
.size(512.dp),
contentAlignment = Alignment.Center
) {
Image(
bitmap = cover.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentDescription = "Background Image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.blur(12.dp),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }))
Image(
bitmap = cover.value.asImageBitmap(),
contentDescription = "cover",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(384.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
.size(384.dp)
.clip(RoundedCornerShape(16.dp)))
}
ActiveButton(label = "Выбрать обложку", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
launcher.launch("image/*")
})
@ -184,6 +210,16 @@ fun EditUserScreen(navController: NavHostController,
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
when(userViewModel.apiStatus){
ApiStatus.DONE -> {
navController.navigate("settings")
userViewModel.clearStatus()
}
ApiStatus.LOADING -> LoadingScreen(ButtonColor2)
ApiStatus.ERROR -> Toast.makeText(context, "Не удалось обновить данные пользователя: "
+ userViewModel.apiError, Toast.LENGTH_SHORT).show()
else -> {}
}
var userId = remember { mutableStateOf(0) }
val photo = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.photoplaceholder)) }
@ -218,12 +254,28 @@ fun EditUserScreen(navController: NavHostController,
}
}
if(userViewModel.apiStatus != ApiStatus.LOADING) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 8.dp),
.padding(bottom = 8.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Bottom
) {
Box(
modifier = Modifier
.size(512.dp),
contentAlignment = Alignment.Center
) {
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "Background Image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.blur(12.dp),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
)
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "editplaceholder",
@ -232,12 +284,13 @@ fun EditUserScreen(navController: NavHostController,
.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 = {
}
ActiveButton(
label = "Выбрать фото",
backgroundColor = ButtonColor1,
textColor = Color.Black,
onClickAction = {
launcher.launch("image/*")
})
PlaceholderInputField(label = "Никнейм", isSingleLine = true,
@ -252,7 +305,11 @@ fun EditUserScreen(navController: NavHostController,
startValue = email.value, onTextChanged = { newEmail ->
email.value = newEmail
})
ActiveButton(label = "Сохранить", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
ActiveButton(
label = "Сохранить",
backgroundColor = ButtonColor1,
textColor = Color.Black,
onClickAction = {
userViewModel.updateUser(
User(
id = userId.value,
@ -262,7 +319,6 @@ fun EditUserScreen(navController: NavHostController,
photo = photo.value
)
)
navController.navigate("settings")
})
ActiveButton(label = "Назад", backgroundColor = ButtonColor2, textColor = Color.White,
onClickAction = {
@ -270,3 +326,4 @@ fun EditUserScreen(navController: NavHostController,
})
}
}
}

View File

@ -1,5 +1,6 @@
package com.example.mobileapp.screens
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -13,16 +14,20 @@ 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.GlobalUser
import com.example.mobileapp.R
import com.example.mobileapp.api.ApiStatus
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.LoadingScreen
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor1
@ -33,11 +38,23 @@ fun Registration(navController: NavHostController,
userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory
)) {
val context = LocalContext.current
when(userViewModel.apiStatus){
ApiStatus.DONE -> {
navController.navigate("main")
userViewModel.clearStatus()
}
ApiStatus.LOADING -> LoadingScreen(ButtonColor2)
ApiStatus.ERROR -> Toast.makeText(context, "Не удалось создать пользователя: " + userViewModel.apiError, Toast.LENGTH_SHORT).show()
else -> {}
}
val login = remember { mutableStateOf("") }
val email = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
val repeatepassword = remember { mutableStateOf("") }
if(userViewModel.apiStatus != ApiStatus.LOADING) {
Column(
modifier = Modifier
.fillMaxSize()
@ -51,11 +68,18 @@ fun Registration(navController: NavHostController,
modifier = Modifier
.size(320.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
PlaceholderInputField(label = "Логин", isSingleLine = true, onTextChanged = {newlogin ->
.align(Alignment.CenterHorizontally)
)
PlaceholderInputField(
label = "Логин",
isSingleLine = true,
onTextChanged = { newlogin ->
login.value = newlogin
})
PlaceholderInputField(label = "Email", isSingleLine = true, onTextChanged = {newemail ->
PlaceholderInputField(
label = "Email",
isSingleLine = true,
onTextChanged = { newemail ->
email.value = newemail
})
PasswordInputField(label = "Пароль", onPasswordChanged = { newpassword ->
@ -75,9 +99,11 @@ fun Registration(navController: NavHostController,
)
)
}
navController.navigate("main")
})
NavigationButton(navController = navController, destination = "authorization",
label = "Назад", backgroundColor = ButtonColor1, textColor = Color.Black)
NavigationButton(
navController = navController, destination = "authorization",
label = "Назад", backgroundColor = ButtonColor1, textColor = Color.Black
)
}
}
}

View File

@ -1,21 +1,146 @@
package com.example.mobileapp.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
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.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import com.example.mobileapp.api.model.toStory
import com.example.mobileapp.api.model.toUser
import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.DatePicker
import com.example.mobileapp.components.MailListItem
import com.example.mobileapp.components.StoryListItem
import com.example.mobileapp.components.addNewListItem
import com.example.mobileapp.components.isListOf
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.ReportViewModel
import com.example.mobileapp.ui.theme.ButtonColor2
import java.text.SimpleDateFormat
import java.util.Date
@Composable
fun ReportScreen(navController: NavController,
fun ReportScreen(navController: NavHostController,
reportViewModel: ReportViewModel = viewModel(factory = MobileAppViewModelProvider.Factory)){
val dateFormat = SimpleDateFormat("dd.MM.yyyy")
val dateFrom = remember { mutableStateOf(Date().time) }
val dateTo = remember { mutableStateOf(Date().time) }
Column(
modifier = Modifier
.fillMaxWidth(),
verticalArrangement = Arrangement.Center
) {
Column(
modifier = Modifier
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Отчёт по публикациям:",
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(top = 16.dp, bottom = 16.dp, start = 8.dp, end = 8.dp))
}
if(reportViewModel.report.value == null) {
DatePicker(startValue = dateFrom.value, onDateSelected = { newDateFrom ->
dateFrom.value = newDateFrom
})
DatePicker(startValue = dateTo.value, onDateSelected = { newDateTo ->
dateTo.value = newDateTo
})
ActiveButton(label = "Сформировать", backgroundColor = ButtonColor2,
textColor = Color.White, onClickAction = {
reportViewModel.createReport(dateFrom.value, dateTo.value)
})
}
else{
Text(text = "Дата с ${dateFormat.format(reportViewModel.report.value?.dateFrom?.let { Date(it) })}",
fontSize = 20.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Text(text = "Дата по ${dateFormat.format(reportViewModel.report.value?.dateTo?.let { Date(it) })}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Text(text = "Кол-в публикаций за период: ${reportViewModel.report.value?.postCount}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Row(
verticalAlignment = Alignment.Top
){
Image(bitmap = reportViewModel.report.value?.mostPostAuthor?.toUser()?.photo!!.asImageBitmap(),
contentDescription = "message",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(96.dp)
.padding(8.dp)
.clip(RoundedCornerShape(16.dp)))
Column(
modifier = Modifier.padding(8.dp)
){
Text(
text = "${reportViewModel.report.value?.mostPostAuthor?.toUser()?.login}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold)
Text(text = "Кол-во публикаций у пользователя:${reportViewModel.report.value?.mostPostCount}")
}
}
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
){
val list = reportViewModel.report.value?.listPostAuthor!!.map {
it.toStory()
}
items(list){ item ->
StoryListItem(item = item, navController = navController, isReadOnly = true)
}
}
/*Text(text = "Наибольшее число публикаций у: ${reportViewModel.report.value?.mostPostAuthor}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))
Text(text = "Кол-во публикаций у пользователя:${reportViewModel.report.value?.mostPostCount}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp))*/
}
}
}

View File

@ -5,23 +5,26 @@ 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.Box
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.foundation.shape.RoundedCornerShape
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.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
@ -54,14 +57,6 @@ fun StoryViewScreen(navController: NavHostController, storyId: Int,
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)
@ -80,14 +75,28 @@ fun StoryViewScreen(navController: NavHostController, storyId: Int,
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Center
) {
Box(
modifier = Modifier
.size(512.dp),
contentAlignment = Alignment.Center
) {
Image(
bitmap = cover.value.asImageBitmap(),
contentDescription = "Background Image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.blur(12.dp),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }))
Image(
bitmap = cover.value.asImageBitmap(),
contentDescription = "cover",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.padding(8.dp)
.align(Alignment.CenterHorizontally))
.size(384.dp)
.clip(RoundedCornerShape(16.dp)))
}
Text(text = "Название: ${title.value}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
@ -144,6 +153,19 @@ fun MailViewScreen(navController: NavHostController, mailId: Int,
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Center
) {
Box(
modifier = Modifier
.size(512.dp),
contentAlignment = Alignment.Center
) {
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "Background Image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(512.dp)
.blur(12.dp),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }))
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "editplaceholder",
@ -151,12 +173,8 @@ fun MailViewScreen(navController: NavHostController, mailId: Int,
modifier = Modifier
.padding(8.dp)
.clip(CircleShape)
.size(384.dp)
.border(
width = 2.dp,
color = MaterialTheme.colorScheme.onPrimary,
)
.align(Alignment.CenterHorizontally))
.size(384.dp))
}
Text(text = "Автор: ${userName.value}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,