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.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.mobileapp.components.NavBar import com.example.mobileapp.components.NavBar
@ -31,14 +32,14 @@ class MainActivity : ComponentActivity() {
} }
class GlobalUser private constructor() { class GlobalUser private constructor() {
private var user: User? = null private var user = mutableStateOf<User?>(null)
fun setUser(user: User?) { fun setUser(user: User?) {
this.user = user this.user.value = user
} }
fun getUser(): User? { fun getUser(): User? {
return user return user.value
} }
companion object { 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 package com.example.mobileapp.api
import com.example.mobileapp.api.model.MailRemote 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.StoryRemote
import com.example.mobileapp.api.model.UserRemote import com.example.mobileapp.api.model.UserRemote
import com.example.mobileapp.api.model.UserRemoteSignIn import com.example.mobileapp.api.model.UserRemoteSignIn
@ -105,6 +106,13 @@ interface ServerService {
@Path("id") id: Int @Path("id") id: Int
) )
//REPORT
@GET("report/createReport/{dateFrom}/{dateTo}")
suspend fun createReport(
@Path("dateFrom") dateFrom: Long,
@Path("dateTo") dateTo: Long
): ReportRemote
//INSTANCE //INSTANCE
companion object { companion object {
private const val BASE_URL = "https://7hz21fz1-8080.euw.devtunnels.ms/api/" 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.PagingState
import androidx.paging.RemoteMediator import androidx.paging.RemoteMediator
import androidx.room.withTransaction import androidx.room.withTransaction
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.api.model.toStory import com.example.mobileapp.api.model.toStory
import com.example.mobileapp.api.model.toUser import com.example.mobileapp.api.model.toUser
import com.example.mobileapp.database.MobileAppDataBase import com.example.mobileapp.database.MobileAppDataBase
@ -18,11 +19,11 @@ import retrofit2.HttpException
import java.io.IOException import java.io.IOException
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
class ServiceRemoteMediator(private val service: ServerService, class StoryRemoteMediator(private val service: ServerService,
private val storyRepository: OfflineStoryRepository, private val storyRepository: OfflineStoryRepository,
private val userRepository: OfflineUserRepository, private val userRepository: OfflineUserRepository,
private val database: MobileAppDataBase, private val database: MobileAppDataBase,
private val dbRemoteKeyRepository: RemoteKeysRepositoryImpl private val dbRemoteKeyRepository: RemoteKeysRepositoryImpl
) : RemoteMediator<Int, Story>() { ) : RemoteMediator<Int, Story>() {
override suspend fun initialize(): InitializeAction { override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH return InitializeAction.LAUNCH_INITIAL_REFRESH
@ -51,8 +52,8 @@ class ServiceRemoteMediator(private val service: ServerService,
} }
try { try {
val users = service.getUsers().map { it.toUser() } val user = GlobalUser.getInstance().getUser()
val stories = service.getStories(page, state.config.pageSize).map { it.toStory() } val stories = service.getUserStories(page, state.config.pageSize, user!!.id!!).map { it.toStory() }
val endOfPaginationReached = stories.isEmpty() val endOfPaginationReached = stories.isEmpty()
database.withTransaction { database.withTransaction {
if (loadType == LoadType.REFRESH) { if (loadType == LoadType.REFRESH) {
@ -71,7 +72,7 @@ class ServiceRemoteMediator(private val service: ServerService,
) )
} }
dbRemoteKeyRepository.createRemoteKeys(keys) dbRemoteKeyRepository.createRemoteKeys(keys)
userRepository.insertUsers(users) userRepository.insertUser(user)
storyRepository.insertStories(stories) storyRepository.insertStories(stories)
} }
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)

View File

@ -8,10 +8,17 @@ import java.io.ByteArrayOutputStream
class RemoteConverters { class RemoteConverters {
companion object { companion object {
private const val CHARSET_UTF8 = "UTF-8" private const val CHARSET_UTF8 = "UTF-8"
private const val MAX_IMAGE_SIZE = 1024 // Размер в килобайтах, который вы хотите использовать
fun fromBitmap(bitmap: Bitmap): String { fun fromBitmap(bitmap: Bitmap): String {
val outputStream = ByteArrayOutputStream() 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() val byteArray = outputStream.toByteArray()
return Base64.encodeToString(byteArray, Base64.NO_WRAP) return Base64.encodeToString(byteArray, Base64.NO_WRAP)
} }
@ -20,5 +27,36 @@ class RemoteConverters {
val byteArray = Base64.decode(base64String, Base64.NO_WRAP) val byteArray = Base64.decode(base64String, Base64.NO_WRAP)
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) 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 import kotlinx.serialization.Serializable
/*
@Serializable @Serializable
data class ReportRemote( 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 package com.example.mobileapp.api.repository
import com.example.mobileapp.api.ServerService import com.example.mobileapp.api.ServerService
import com.example.mobileapp.api.model.ReportRemote
import com.example.mobileapp.database.repositories.ReportRepository import com.example.mobileapp.database.repositories.ReportRepository
class RestReportRepository(private var service: ServerService): 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 androidx.paging.PagingState
import com.example.mobileapp.MobileAppContainer import com.example.mobileapp.MobileAppContainer
import com.example.mobileapp.api.ServerService import com.example.mobileapp.api.ServerService
import com.example.mobileapp.api.ServiceRemoteMediator import com.example.mobileapp.api.StoryRemoteMediator
import com.example.mobileapp.api.model.toMail
import com.example.mobileapp.api.model.toStory import com.example.mobileapp.api.model.toStory
import com.example.mobileapp.api.model.toStoryRemote import com.example.mobileapp.api.model.toStoryRemote
import com.example.mobileapp.database.MobileAppDataBase 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.entities.Story
import com.example.mobileapp.database.repositories.OfflineStoryRepository import com.example.mobileapp.database.repositories.OfflineStoryRepository
import com.example.mobileapp.database.repositories.OfflineUserRepository import com.example.mobileapp.database.repositories.OfflineUserRepository
import com.example.mobileapp.database.repositories.RemoteKeysRepositoryImpl import com.example.mobileapp.database.repositories.RemoteKeysRepositoryImpl
import com.example.mobileapp.database.repositories.StoryRepository import com.example.mobileapp.database.repositories.StoryRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class RestStoryRepository(private var service: ServerService, class RestStoryRepository(private var service: ServerService,
private val dbStoryRepository: OfflineStoryRepository, private val dbStoryRepository: OfflineStoryRepository,
@ -30,7 +27,7 @@ class RestStoryRepository(private var service: ServerService,
): StoryRepository { ): StoryRepository {
override fun getAllStories(): Flow<PagingData<Story>> { override fun getAllStories(): Flow<PagingData<Story>> {
val pagingSourceFactory = { /*val pagingSourceFactory = {
dbStoryRepository.getAllStoriesPagingSource() dbStoryRepository.getAllStoriesPagingSource()
} }
@ -40,7 +37,36 @@ class RestStoryRepository(private var service: ServerService,
pageSize = MobileAppContainer.LIMIT, pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false 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, service,
dbStoryRepository, dbStoryRepository,
dbUserRepository, dbUserRepository,
@ -50,22 +76,12 @@ class RestStoryRepository(private var service: ServerService,
pagingSourceFactory = pagingSourceFactory pagingSourceFactory = pagingSourceFactory
).flow ).flow
/*return Pager( /*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( config = PagingConfig(
pageSize = MobileAppContainer.LIMIT, pageSize = MobileAppContainer.LIMIT,
enablePlaceholders = false enablePlaceholders = false
), ),
pagingSourceFactory = { StoryPagingSource(service, userId) } pagingSourceFactory = { StoryPagingSource(service, userId) }
).flow ).flow*/
} }
override suspend fun getStoryById(id: Int): Story? = service.getStory(id).toStory() override suspend fun getStoryById(id: Int): Story? = service.getStory(id).toStory()

View File

@ -1,5 +1,8 @@
package com.example.mobileapp.components 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.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -19,15 +22,21 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector 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.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.mobileapp.ui.theme.ButtonColor1
import com.example.mobileapp.ui.theme.MobileAppTheme import com.example.mobileapp.ui.theme.MobileAppTheme
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
val buttonHeightStandard = 72.dp 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) @Preview(showBackground = true)
@Composable @Composable
fun PlaceholderTextFieldPreview() { fun PlaceholderTextFieldPreview() {

View File

@ -25,6 +25,7 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -32,6 +33,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
@ -246,7 +248,8 @@ fun MailListItem(item: Mail, navController: NavHostController,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.size(96.dp) .size(96.dp)
.padding(8.dp)) .padding(8.dp)
.clip(RoundedCornerShape(16.dp)))
Column( Column(
modifier = Modifier.padding(8.dp) modifier = Modifier.padding(8.dp)
){ ){
@ -281,7 +284,7 @@ fun addNewListItem(navController: NavHostController, destination: String){
}, },
shape = RoundedCornerShape(15.dp), shape = RoundedCornerShape(15.dp),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = BackgroundItem2 containerColor = Color.White
), ),
elevation = CardDefaults.cardElevation( elevation = CardDefaults.cardElevation(
defaultElevation = 8.dp defaultElevation = 8.dp
@ -304,7 +307,9 @@ fun addNewListItem(navController: NavHostController, destination: String){
text = "Добавить", text = "Добавить",
fontSize = 28.sp, fontSize = 28.sp,
fontWeight = FontWeight.Bold, 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.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -53,8 +54,9 @@ import com.example.mobileapp.screens.StoryViewScreen
val navBarItems = listOf( val navBarItems = listOf(
NavBarItem(route = "main", label = "Главная", icon = R.drawable.home), NavBarItem(route = "main", label = "Главная", icon = R.drawable.home),
NavBarItem(route = "story", label = "Создание", icon = R.drawable.edit), NavBarItem(route = "story", label = "Создание", icon = R.drawable.edit),
NavBarItem(route = "mail", label = "Уведомления", icon = R.drawable.mail), NavBarItem(route = "mail", label = "Почта", icon = R.drawable.mail),
NavBarItem(route = "settings", label = "Настройки", icon = R.drawable.settings), NavBarItem(route = "report", label = "Отчёт", icon = R.drawable.report),
NavBarItem(route = "settings", label = "Настройки", icon = R.drawable.settings)
) )
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -69,8 +71,10 @@ fun NavBar(navController: NavHostController) {
visible = topBarState.value, visible = topBarState.value,
enter = slideInVertically(initialOffsetY = { it }), enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }), exit = slideOutVertically(targetOffsetY = { it }),
content = { content = {
TopAppBar( TopAppBar(
modifier = Modifier.fillMaxWidth(),
title = { title = {
Text( Text(
text = "Storyteller!", text = "Storyteller!",
@ -79,7 +83,8 @@ fun NavBar(navController: NavHostController) {
Font( Font(
R.font.roboto_regular, FontWeight.Bold R.font.roboto_regular, FontWeight.Bold
) )
) ),
color = Color.Black
) )
} }
) )
@ -204,7 +209,7 @@ fun NavBar(navController: NavHostController) {
} }
composable("report"){ composable("report"){
topBarState.value = false topBarState.value = false
bottomBarState.value = false bottomBarState.value = true
ReportScreen(navController = navController) ReportScreen(navController = navController)
} }
} }

View File

@ -19,7 +19,7 @@ interface StoryDao {
suspend fun getById(id: Int): Story? suspend fun getById(id: Int): Story?
@Query("select * from stories where stories.user_id = :userId order by stories.id desc") @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) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(vararg story: Story) 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>> { 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) 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()) storyDao.insert(*stories.toTypedArray())
fun getAllStoriesPagingSource(): PagingSource<Int, Story> = storyDao.getAll() 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 package com.example.mobileapp.database.repositories
import com.example.mobileapp.api.model.ReportRemote
interface ReportRepository { 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 package com.example.mobileapp.database.viewmodels
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.mobileapp.api.model.ReportRemote
import com.example.mobileapp.database.repositories.ReportRepository import com.example.mobileapp.database.repositories.ReportRepository
import kotlinx.coroutines.launch
class ReportViewModel(private val reportRepository: ReportRepository): ViewModel() { 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.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class UserViewModel(private val userRepository: UserRepository): ViewModel() { class UserViewModel(private val userRepository: UserRepository): CustomViewModel() {
//val getAllUsers = userRepository.getAllUsers() //val getAllUsers = userRepository.getAllUsers()
suspend fun getUser(id: Int): User? = userRepository.getUser(id) suspend fun getUser(id: Int): User? = userRepository.getUser(id)
@ -28,8 +28,12 @@ class UserViewModel(private val userRepository: UserRepository): ViewModel() {
if (user.password.isEmpty()){ if (user.password.isEmpty()){
return@launch return@launch
} }
userRepository.updateUser(user) runInScope(
GlobalUser.getInstance().setUser(user) actionSuccess = {
userRepository.updateUser(user)
GlobalUser.getInstance().setUser(user)
}
)
} }
fun deleteUser(user: User) = viewModelScope.launch { fun deleteUser(user: User) = viewModelScope.launch {
@ -44,18 +48,38 @@ class UserViewModel(private val userRepository: UserRepository): ViewModel() {
if(user.email.isEmpty() || !isValidEmail(user.email)){ if(user.email.isEmpty() || !isValidEmail(user.email)){
return@launch return@launch
} }
userRepository.insertUser(user) runInScope(
actionSuccess = {
userRepository.insertUser(user)
GlobalUser.getInstance().setUser(userRepository.getUserByLogin(
UserRemoteSignIn(user.login, user.password)))
}
)
/*userRepository.insertUser(user)
GlobalUser.getInstance().setUser(userRepository.getUserByLogin( GlobalUser.getInstance().setUser(userRepository.getUserByLogin(
UserRemoteSignIn(user.login, user.password))) UserRemoteSignIn(user.login, user.password)))*/
} }
fun authUser(user: User) = viewModelScope.launch { fun authUser(user: User) = viewModelScope.launch {
val globalUser = userRepository.getUserByLogin(UserRemoteSignIn(user.login, user.password)) 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 { globalUser?.let {
if (user.password.isNotEmpty() && user.password == globalUser.password){ if (user.password.isNotEmpty() && user.password == globalUser.password){
GlobalUser.getInstance().setUser(globalUser) GlobalUser.getInstance().setUser(globalUser)
} }
} }*/
} }
private fun isValidEmail(email: String): Boolean { private fun isValidEmail(email: String): Boolean {

View File

@ -1,5 +1,6 @@
package com.example.mobileapp.screens package com.example.mobileapp.screens
import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -21,13 +22,16 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.R import com.example.mobileapp.R
import com.example.mobileapp.api.ApiStatus
import com.example.mobileapp.components.ActiveButton import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.NavigationButton import com.example.mobileapp.components.NavigationButton
import com.example.mobileapp.components.PasswordInputField import com.example.mobileapp.components.PasswordInputField
import com.example.mobileapp.components.PlaceholderInputField import com.example.mobileapp.components.PlaceholderInputField
import com.example.mobileapp.database.MobileAppDataBase import com.example.mobileapp.database.MobileAppDataBase
import com.example.mobileapp.database.entities.User 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.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.UserViewModel import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor1 import com.example.mobileapp.ui.theme.ButtonColor1
@ -40,45 +44,63 @@ fun Authorization(navController: NavHostController,
userViewModel: UserViewModel = viewModel( userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory 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 login = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") } val password = remember { mutableStateOf("") }
Column( if(userViewModel.apiStatus != ApiStatus.LOADING) {
modifier = Modifier Column(
.fillMaxSize()
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Bottom
) {
Image(
painter = painterResource(id = R.drawable.login),
contentDescription = "login",
contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.size(448.dp) .fillMaxSize()
.padding(8.dp) .padding(bottom = 8.dp),
.align(Alignment.CenterHorizontally)) verticalArrangement = Arrangement.Bottom
PlaceholderInputField(label = "Логин", isSingleLine = true, onTextChanged = {newlogin -> ) {
login.value = newlogin Image(
}) painter = painterResource(id = R.drawable.login),
PasswordInputField(label = "Пароль", onPasswordChanged = {newpassword -> contentDescription = "login",
password.value = newpassword contentScale = ContentScale.Crop,
}) modifier = Modifier
ActiveButton(label = "Вход", backgroundColor = ButtonColor2, .size(448.dp)
textColor = Color.White, onClickAction = { .padding(8.dp)
if (login.value.isNotEmpty() && password.value.isNotEmpty()) { .align(Alignment.CenterHorizontally)
userViewModel.authUser( )
User( PlaceholderInputField(
login = login.value, label = "Логин",
password = password.value, isSingleLine = true,
email = String() 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 = "Регистрация",
NavigationButton(navController = navController, destination = "registration", label = "Регистрация", backgroundColor = ButtonColor1, textColor = Color.Black
backgroundColor = ButtonColor1, textColor = Color.Black) )
}
} }
} }

View File

@ -6,16 +6,22 @@ import android.graphics.ImageDecoder
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape 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.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -25,8 +31,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color 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.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -36,12 +45,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.example.mobileapp.GlobalUser import com.example.mobileapp.GlobalUser
import com.example.mobileapp.R import com.example.mobileapp.R
import com.example.mobileapp.api.ApiStatus
import com.example.mobileapp.components.ActiveButton import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.NavigationButton import com.example.mobileapp.components.NavigationButton
import com.example.mobileapp.components.PlaceholderInputField import com.example.mobileapp.components.PlaceholderInputField
import com.example.mobileapp.database.entities.Mail import com.example.mobileapp.database.entities.Mail
import com.example.mobileapp.database.entities.Story import com.example.mobileapp.database.entities.Story
import com.example.mobileapp.database.entities.User 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.MailViewModel
import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider import com.example.mobileapp.database.viewmodels.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.StoryViewModel import com.example.mobileapp.database.viewmodels.StoryViewModel
@ -90,17 +101,32 @@ fun EditStoryScreen(navController: NavHostController, storyId: Int? = null,
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(bottom = 8.dp), .padding(bottom = 8.dp)
verticalArrangement = Arrangement.Bottom .verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Bottom,
) { ) {
Image( Box(
bitmap = cover.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.size(384.dp) .size(512.dp),
.padding(8.dp) contentAlignment = Alignment.Center
.align(Alignment.CenterHorizontally)) ) {
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
.padding(8.dp)
.size(384.dp)
.clip(RoundedCornerShape(16.dp)))
}
ActiveButton(label = "Выбрать обложку", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = { ActiveButton(label = "Выбрать обложку", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = {
launcher.launch("image/*") launcher.launch("image/*")
}) })
@ -184,6 +210,16 @@ fun EditUserScreen(navController: NavHostController,
factory = MobileAppViewModelProvider.Factory factory = MobileAppViewModelProvider.Factory
)) { )) {
val context = LocalContext.current 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) } var userId = remember { mutableStateOf(0) }
val photo = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.photoplaceholder)) } val photo = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.photoplaceholder)) }
@ -218,55 +254,76 @@ fun EditUserScreen(navController: NavHostController,
} }
} }
Column( if(userViewModel.apiStatus != ApiStatus.LOADING) {
modifier = Modifier Column(
.fillMaxSize()
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Bottom
) {
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.padding(8.dp) .fillMaxSize()
.clip(CircleShape) .padding(bottom = 8.dp)
.size(384.dp) .verticalScroll(rememberScrollState()),
.border( verticalArrangement = Arrangement.Bottom
width = 2.dp, ) {
color = MaterialTheme.colorScheme.onPrimary, 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) })
) )
.align(Alignment.CenterHorizontally)) Image(
ActiveButton(label = "Выбрать фото", backgroundColor = ButtonColor1, textColor = Color.Black, onClickAction = { bitmap = photo.value.asImageBitmap(),
launcher.launch("image/*") contentDescription = "editplaceholder",
}) contentScale = ContentScale.Crop,
PlaceholderInputField(label = "Никнейм", isSingleLine = true, modifier = Modifier
startValue = login.value, onTextChanged = { newLogin -> .padding(8.dp)
login.value = newLogin .clip(CircleShape)
}) .size(384.dp)
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 = "Выбрать фото",
ActiveButton(label = "Назад", backgroundColor = ButtonColor2, textColor = Color.White, backgroundColor = ButtonColor1,
onClickAction = { textColor = Color.Black,
navController.navigate("settings") 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
)
)
})
ActiveButton(label = "Назад", backgroundColor = ButtonColor2, textColor = Color.White,
onClickAction = {
navController.navigate("settings")
})
}
} }
} }

View File

@ -1,5 +1,6 @@
package com.example.mobileapp.screens package com.example.mobileapp.screens
import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -13,16 +14,20 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.example.mobileapp.GlobalUser
import com.example.mobileapp.R import com.example.mobileapp.R
import com.example.mobileapp.api.ApiStatus
import com.example.mobileapp.components.ActiveButton import com.example.mobileapp.components.ActiveButton
import com.example.mobileapp.components.NavigationButton import com.example.mobileapp.components.NavigationButton
import com.example.mobileapp.components.PasswordInputField import com.example.mobileapp.components.PasswordInputField
import com.example.mobileapp.components.PlaceholderInputField import com.example.mobileapp.components.PlaceholderInputField
import com.example.mobileapp.database.entities.User 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.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.UserViewModel import com.example.mobileapp.database.viewmodels.UserViewModel
import com.example.mobileapp.ui.theme.ButtonColor1 import com.example.mobileapp.ui.theme.ButtonColor1
@ -33,51 +38,72 @@ fun Registration(navController: NavHostController,
userViewModel: UserViewModel = viewModel( userViewModel: UserViewModel = viewModel(
factory = MobileAppViewModelProvider.Factory 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 login = remember { mutableStateOf("") }
val email = remember { mutableStateOf("") } val email = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") } val password = remember { mutableStateOf("") }
val repeatepassword = remember { mutableStateOf("") } val repeatepassword = remember { mutableStateOf("") }
Column( if(userViewModel.apiStatus != ApiStatus.LOADING) {
modifier = Modifier Column(
.fillMaxSize()
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.Bottom
) {
Image(
painter = painterResource(id = R.drawable.registration),
contentDescription = "registration",
contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.size(320.dp) .fillMaxSize()
.padding(8.dp) .padding(bottom = 8.dp),
.align(Alignment.CenterHorizontally)) verticalArrangement = Arrangement.Bottom
PlaceholderInputField(label = "Логин", isSingleLine = true, onTextChanged = {newlogin -> ) {
login.value = newlogin Image(
}) painter = painterResource(id = R.drawable.registration),
PlaceholderInputField(label = "Email", isSingleLine = true, onTextChanged = {newemail -> contentDescription = "registration",
email.value = newemail contentScale = ContentScale.Crop,
}) modifier = Modifier
PasswordInputField(label = "Пароль", onPasswordChanged = {newpassword -> .size(320.dp)
password.value = newpassword .padding(8.dp)
}) .align(Alignment.CenterHorizontally)
PasswordInputField(label = "Пароль ещё раз", onPasswordChanged = {newpassword -> )
repeatepassword.value = newpassword PlaceholderInputField(
}) label = "Логин",
ActiveButton(label = "Зарегистрироваться", backgroundColor = ButtonColor2, isSingleLine = true,
textColor = Color.White, onClickAction = { onTextChanged = { newlogin ->
if (password.value == repeatepassword.value){ login.value = newlogin
userViewModel.regUser( })
User( PlaceholderInputField(
login = login.value, label = "Email",
password = password.value, isSingleLine = true,
email = email.value 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",
NavigationButton(navController = navController, destination = "authorization", label = "Назад", backgroundColor = ButtonColor1, textColor = Color.Black
label = "Назад", backgroundColor = ButtonColor1, textColor = Color.Black) )
}
} }
} }

View File

@ -1,21 +1,146 @@
package com.example.mobileapp.screens 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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth 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.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.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.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController 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.MobileAppViewModelProvider
import com.example.mobileapp.database.viewmodels.ReportViewModel import com.example.mobileapp.database.viewmodels.ReportViewModel
import com.example.mobileapp.ui.theme.ButtonColor2
import java.text.SimpleDateFormat
import java.util.Date
@Composable @Composable
fun ReportScreen(navController: NavController, fun ReportScreen(navController: NavHostController,
reportViewModel: ReportViewModel = viewModel(factory = MobileAppViewModelProvider.Factory)){ 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( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .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.Image
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color 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.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -54,14 +57,6 @@ fun StoryViewScreen(navController: NavHostController, storyId: Int,
val description = remember { mutableStateOf("") } val description = remember { mutableStateOf("") }
val postdate = remember { mutableStateOf<Long>(0) } 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) { LaunchedEffect(Unit) {
storyId?.let { storyId?.let {
val story = storyViewModel.getStoryById(storyId) val story = storyViewModel.getStoryById(storyId)
@ -80,14 +75,28 @@ fun StoryViewScreen(navController: NavHostController, storyId: Int,
.padding(bottom = 8.dp), .padding(bottom = 8.dp),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Image( Box(
bitmap = cover.value.asImageBitmap(),
contentDescription = "cover",
contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.size(512.dp) .size(512.dp),
.padding(8.dp) contentAlignment = Alignment.Center
.align(Alignment.CenterHorizontally)) ) {
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
.padding(8.dp)
.size(384.dp)
.clip(RoundedCornerShape(16.dp)))
}
Text(text = "Название: ${title.value}", Text(text = "Название: ${title.value}",
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@ -144,19 +153,28 @@ fun MailViewScreen(navController: NavHostController, mailId: Int,
.padding(bottom = 8.dp), .padding(bottom = 8.dp),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Image( Box(
bitmap = photo.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.padding(8.dp) .size(512.dp),
.clip(CircleShape) contentAlignment = Alignment.Center
.size(384.dp) ) {
.border( Image(
width = 2.dp, bitmap = photo.value.asImageBitmap(),
color = MaterialTheme.colorScheme.onPrimary, contentDescription = "Background Image",
) contentScale = ContentScale.Crop,
.align(Alignment.CenterHorizontally)) modifier = Modifier
.size(512.dp)
.blur(12.dp),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }))
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = "editplaceholder",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(8.dp)
.clip(CircleShape)
.size(384.dp))
}
Text(text = "Автор: ${userName.value}", Text(text = "Автор: ${userName.value}",
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,