email sender
This commit is contained in:
parent
39297abc6c
commit
02138f3f2f
|
|
@ -22,6 +22,5 @@ kotlin.code.style=official
|
||||||
# resources declared in the library itself and none from the library's dependencies,
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
|
||||||
android.nonFinalResIds=true
|
android.nonFinalResIds=true
|
||||||
org.gradle.unsafe.configuration-cache=true
|
org.gradle.unsafe.configuration-cache=true
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ android {
|
||||||
applicationId = "llc.arma.vgate"
|
applicationId = "llc.arma.vgate"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 2
|
versionCode = 3
|
||||||
versionName = "1.0.1"
|
versionName = "1.0.2"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,12 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import llc.arma.vgate.app.framework.SendRequestWorker
|
import llc.arma.vgate.app.framework.SendRequestWorker
|
||||||
import llc.arma.vgate.app.ui.screens.main.MainScreen
|
import llc.arma.vgate.app.ui.screens.main.MainScreen
|
||||||
import llc.arma.vgate.app.ui.theme.BleTheme
|
import llc.arma.vgate.app.ui.theme.BleTheme
|
||||||
|
import llc.arma.vgate.domain.usecase.GetReceiverEmailFlow
|
||||||
import llc.arma.vgate.domain.usecase.GetWaitingReportsFlow
|
import llc.arma.vgate.domain.usecase.GetWaitingReportsFlow
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -45,6 +47,7 @@ import javax.inject.Inject
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
@Inject lateinit var getWaitingReportsFlow: GetWaitingReportsFlow
|
@Inject lateinit var getWaitingReportsFlow: GetWaitingReportsFlow
|
||||||
|
@Inject lateinit var getReceiverEmailFlow: GetReceiverEmailFlow
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -63,11 +66,13 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
val workManager = WorkManager.getInstance(this)
|
val workManager = WorkManager.getInstance(this)
|
||||||
|
|
||||||
getWaitingReportsFlow().onEach {
|
merge(getWaitingReportsFlow.invoke(), getReceiverEmailFlow.invoke()).onEach {
|
||||||
|
|
||||||
|
println(it)
|
||||||
|
|
||||||
workManager.enqueueUniqueWork(
|
workManager.enqueueUniqueWork(
|
||||||
uniqueWorkName = "Upload",
|
uniqueWorkName = "Upload",
|
||||||
existingWorkPolicy = ExistingWorkPolicy.KEEP,
|
existingWorkPolicy = ExistingWorkPolicy.REPLACE,
|
||||||
request = uploadWorkRequest
|
request = uploadWorkRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ fun HomeScreen(
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Image(
|
Icon(
|
||||||
imageVector = Icons.Rounded.Settings,
|
imageVector = Icons.Rounded.Settings,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import llc.arma.vgate.app.ui.screens.result.ReadResultContract
|
||||||
import llc.arma.vgate.app.ui.screens.result.ReadResultScreen
|
import llc.arma.vgate.app.ui.screens.result.ReadResultScreen
|
||||||
import llc.arma.vgate.app.ui.screens.selector.BleSelectorContract
|
import llc.arma.vgate.app.ui.screens.selector.BleSelectorContract
|
||||||
import llc.arma.vgate.app.ui.screens.selector.BleSelectorScreen
|
import llc.arma.vgate.app.ui.screens.selector.BleSelectorScreen
|
||||||
|
import llc.arma.vgate.app.ui.screens.sender.EmailSenderContract
|
||||||
|
import llc.arma.vgate.app.ui.screens.sender.EmailSenderScreen
|
||||||
import llc.arma.vgate.app.ui.screens.vehicle.form.VehicleFormContract
|
import llc.arma.vgate.app.ui.screens.vehicle.form.VehicleFormContract
|
||||||
import llc.arma.vgate.app.ui.screens.vehicle.form.VehicleFormScreen
|
import llc.arma.vgate.app.ui.screens.vehicle.form.VehicleFormScreen
|
||||||
import llc.arma.vgate.app.ui.screens.vehicle.selector.VehicleSelectorContract
|
import llc.arma.vgate.app.ui.screens.vehicle.selector.VehicleSelectorContract
|
||||||
|
|
@ -54,6 +56,11 @@ data class ReadResultScreenRoute(
|
||||||
val resultId: Long
|
val resultId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EmailSenderScreenRoute(
|
||||||
|
val requestId: Long
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object VehiclesScreenRoute
|
data object VehiclesScreenRoute
|
||||||
|
|
||||||
|
|
@ -145,6 +152,17 @@ fun MainScreen() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable<EmailSenderScreenRoute> {
|
||||||
|
EmailSenderScreen(
|
||||||
|
onNavigationEvent = {
|
||||||
|
when(it){
|
||||||
|
EmailSenderContract.Effect.Navigation.Up ->
|
||||||
|
controller.navigateUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
composable<SendRequestsScreenRoute> {
|
composable<SendRequestsScreenRoute> {
|
||||||
|
|
||||||
SendRequestsScreen(
|
SendRequestsScreen(
|
||||||
|
|
@ -152,6 +170,9 @@ fun MainScreen() {
|
||||||
when(it){
|
when(it){
|
||||||
SendRequestsContract.Effect.Navigation.NavigateUp ->
|
SendRequestsContract.Effect.Navigation.NavigateUp ->
|
||||||
controller.popBackStack()
|
controller.popBackStack()
|
||||||
|
|
||||||
|
is SendRequestsContract.Effect.Navigation.RequestSend ->
|
||||||
|
controller.navigate(EmailSenderScreenRoute(it.requestId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ import llc.arma.vgate.domain.model.Vehicle
|
||||||
|
|
||||||
class SendRequestsContract {
|
class SendRequestsContract {
|
||||||
|
|
||||||
sealed class Event : ViewEvent
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
|
data class OnSend(
|
||||||
|
val requestId: Long
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
sealed class State : ViewState {
|
sealed class State : ViewState {
|
||||||
|
|
||||||
|
|
@ -24,6 +30,10 @@ class SendRequestsContract {
|
||||||
|
|
||||||
sealed class Navigation : Effect() {
|
sealed class Navigation : Effect() {
|
||||||
|
|
||||||
|
data class RequestSend(
|
||||||
|
val requestId: Long
|
||||||
|
) : Navigation()
|
||||||
|
|
||||||
data object NavigateUp : Navigation()
|
data object NavigateUp : Navigation()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
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
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -12,7 +13,7 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material.icons.rounded.Upload
|
||||||
import androidx.compose.material3.ContainedLoadingIndicator
|
import androidx.compose.material3.ContainedLoadingIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
|
@ -25,6 +26,7 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
|
@ -53,6 +55,16 @@ fun SendRequestsScreen(
|
||||||
val viewModel = hiltViewModel<SendRequestsViewModel>()
|
val viewModel = hiltViewModel<SendRequestsViewModel>()
|
||||||
val state = viewModel.viewState.value
|
val state = viewModel.viewState.value
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.effect.collect {
|
||||||
|
|
||||||
|
when(it){
|
||||||
|
is SendRequestsContract.Effect.Navigation -> onNavigationEvent(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -126,7 +138,9 @@ private fun DisplayState(
|
||||||
|
|
||||||
items(items = state.sendRequests.toList()){
|
items(items = state.sendRequests.toList()){
|
||||||
|
|
||||||
RequestItem(it.first, it.second)
|
RequestItem(it.first, it.second){
|
||||||
|
viewModel.setEvent(SendRequestsContract.Event.OnSend(it.first.id))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +153,8 @@ private val formatter: DateFormat = SimpleDateFormat.getDateTimeInstance(3, 2)
|
||||||
@Composable
|
@Composable
|
||||||
private fun RequestItem(
|
private fun RequestItem(
|
||||||
request: SendRequest,
|
request: SendRequest,
|
||||||
vehicle: Vehicle
|
vehicle: Vehicle,
|
||||||
|
onSend: () -> Unit
|
||||||
){
|
){
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
|
|
@ -148,10 +163,14 @@ private fun RequestItem(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = vehicle.name
|
text = vehicle.name
|
||||||
)
|
)
|
||||||
|
|
@ -168,6 +187,21 @@ private fun RequestItem(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(request.status == SendRequest.Status.Waiting) {
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onSend
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Upload,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,18 @@ class SendRequestsViewModel @Inject constructor(
|
||||||
override fun setInitialState() = SendRequestsContract.State.Loading
|
override fun setInitialState() = SendRequestsContract.State.Loading
|
||||||
|
|
||||||
override fun handleEvents(event: SendRequestsContract.Event) {
|
override fun handleEvents(event: SendRequestsContract.Event) {
|
||||||
|
when(event){
|
||||||
|
is SendRequestsContract.Event.OnSend -> reduce(viewState.value, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: SendRequestsContract.State,
|
||||||
|
event: SendRequestsContract.Event.OnSend
|
||||||
|
){
|
||||||
|
setEffect {
|
||||||
|
SendRequestsContract.Effect.Navigation.RequestSend(event.requestId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package llc.arma.vgate.app.ui.screens.sender
|
||||||
|
|
||||||
|
import llc.arma.vgate.app.ui.ViewEvent
|
||||||
|
import llc.arma.vgate.app.ui.ViewSideEffect
|
||||||
|
import llc.arma.vgate.app.ui.ViewState
|
||||||
|
|
||||||
|
class EmailSenderContract {
|
||||||
|
|
||||||
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
|
data object OnNavigateUp : Event()
|
||||||
|
|
||||||
|
data object OnRetry : Event()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class State : ViewState {
|
||||||
|
|
||||||
|
data object Loading : State()
|
||||||
|
|
||||||
|
data object Success : State()
|
||||||
|
|
||||||
|
data class Error(
|
||||||
|
val error: Throwable
|
||||||
|
) : State()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Effect : ViewSideEffect {
|
||||||
|
|
||||||
|
sealed class Navigation : Effect() {
|
||||||
|
|
||||||
|
data object Up : Navigation()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,377 @@
|
||||||
|
package llc.arma.vgate.app.ui.screens.sender
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
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.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||||
|
import androidx.compose.material.icons.rounded.Close
|
||||||
|
import androidx.compose.material.icons.rounded.Done
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ContainedLoadingIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun EmailSenderScreen(
|
||||||
|
onNavigationEvent: (EmailSenderContract.Effect.Navigation) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
val viewModel = hiltViewModel<EmailSenderViewModel>()
|
||||||
|
val state = viewModel.viewState.value
|
||||||
|
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.effect.collect {
|
||||||
|
when(it){
|
||||||
|
is EmailSenderContract.Effect.Navigation -> onNavigationEvent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
onNavigationEvent(EmailSenderContract.Effect.Navigation.Up)
|
||||||
|
}
|
||||||
|
){
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
|
),
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "Отправка"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
},
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(it)
|
||||||
|
) {
|
||||||
|
when(state){
|
||||||
|
is EmailSenderContract.State.Error -> ErrorState(viewModel, state)
|
||||||
|
EmailSenderContract.State.Loading -> LoadingState()
|
||||||
|
EmailSenderContract.State.Success -> SuccessState(viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ErrorState(
|
||||||
|
viewModel: EmailSenderViewModel,
|
||||||
|
state: EmailSenderContract.State.Error
|
||||||
|
){
|
||||||
|
|
||||||
|
var showError by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
){
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.widthIn(max = 270.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.errorContainer,
|
||||||
|
modifier = Modifier.size(128.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Close,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(56.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Ошибка"
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Во время отправки данных произошла ошибка",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(EmailSenderContract.Event.OnRetry)
|
||||||
|
},
|
||||||
|
modifier = Modifier.widthIn(min = 128.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Повторить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = {
|
||||||
|
showError = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.widthIn(min = 128.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Показать ошибку"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
TextButton (
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(EmailSenderContract.Event.OnNavigateUp)
|
||||||
|
},
|
||||||
|
modifier = Modifier.widthIn(min = 128.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Отмена"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(showError){
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
showError = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(20.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.padding(20.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Ошибка отправки",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.heightIn(max = 300.dp).verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = state.error.stackTraceToString(),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
showError = false
|
||||||
|
},
|
||||||
|
modifier = Modifier.align(Alignment.End)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun LoadingState(){
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
){
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.widthIn(max = 270.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
ContainedLoadingIndicator()
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Отправка данных",
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SuccessState(
|
||||||
|
viewModel: EmailSenderViewModel
|
||||||
|
){
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
){
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.widthIn(max = 270.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = Color.Green.copy(alpha = 0.8f),
|
||||||
|
modifier = Modifier.size(128.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
tint = Color.White,
|
||||||
|
imageVector = Icons.Rounded.Done,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(56.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Успешно"
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Данные с устройства успешно отправлены",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(EmailSenderContract.Event.OnNavigateUp)
|
||||||
|
},
|
||||||
|
modifier = Modifier.widthIn(min = 128.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package llc.arma.vgate.app.ui.screens.sender
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.navigation.toRoute
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import llc.arma.vgate.app.ui.BaseViewModel
|
||||||
|
import llc.arma.vgate.app.ui.screens.main.EmailSenderScreenRoute
|
||||||
|
import llc.arma.vgate.domain.usecase.SendWaitingReports
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class EmailSenderViewModel @Inject constructor(
|
||||||
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
private val sendWaitingReports: SendWaitingReports
|
||||||
|
) : BaseViewModel<EmailSenderContract.State, EmailSenderContract.Event, EmailSenderContract.Effect>() {
|
||||||
|
|
||||||
|
private var sendJob: Job? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
send()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setInitialState() = EmailSenderContract.State.Loading
|
||||||
|
|
||||||
|
override fun handleEvents(event: EmailSenderContract.Event) {
|
||||||
|
when(event){
|
||||||
|
is EmailSenderContract.Event.OnRetry -> reduce(viewState.value, event)
|
||||||
|
is EmailSenderContract.Event.OnNavigateUp -> reduce(viewState.value, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: EmailSenderContract.State,
|
||||||
|
event: EmailSenderContract.Event.OnRetry
|
||||||
|
){
|
||||||
|
|
||||||
|
send()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: EmailSenderContract.State,
|
||||||
|
event: EmailSenderContract.Event.OnNavigateUp
|
||||||
|
){
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
EmailSenderContract.Effect.Navigation.Up
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun send() {
|
||||||
|
|
||||||
|
val params = savedStateHandle.toRoute<EmailSenderScreenRoute>()
|
||||||
|
|
||||||
|
setState {
|
||||||
|
EmailSenderContract.State.Loading
|
||||||
|
}
|
||||||
|
|
||||||
|
sendJob = viewModelScope.launch {
|
||||||
|
sendWaitingReports.invoke(params.requestId).fold(
|
||||||
|
onSuccess = {
|
||||||
|
setState {
|
||||||
|
EmailSenderContract.State.Success
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
EmailSenderContract.State.Error(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ import androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import llc.arma.common.domain.Result
|
||||||
import llc.arma.vgate.domain.repository.EmailRepository
|
import llc.arma.vgate.domain.repository.EmailRepository
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
|
@ -87,7 +88,7 @@ class EmailRepositoryImpl @Inject constructor(
|
||||||
body: String,
|
body: String,
|
||||||
fileName: String,
|
fileName: String,
|
||||||
file: File
|
file: File
|
||||||
): Boolean {
|
): Result<Unit, Throwable> {
|
||||||
|
|
||||||
return coroutineScope {
|
return coroutineScope {
|
||||||
|
|
||||||
|
|
@ -103,10 +104,10 @@ class EmailRepositoryImpl @Inject constructor(
|
||||||
body = body,
|
body = body,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
true
|
Result.success(Unit)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
err.printStackTrace()
|
err.printStackTrace()
|
||||||
false
|
Result.failure(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package llc.arma.vgate.domain.repository
|
package llc.arma.vgate.domain.repository
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import llc.arma.common.domain.Result
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface EmailRepository {
|
interface EmailRepository {
|
||||||
|
|
@ -10,7 +11,7 @@ interface EmailRepository {
|
||||||
body: String,
|
body: String,
|
||||||
fileName: String,
|
fileName: String,
|
||||||
file: File
|
file: File
|
||||||
) : Boolean
|
): Result<Unit, Throwable>
|
||||||
|
|
||||||
suspend fun saveEmail(email: String)
|
suspend fun saveEmail(email: String)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package llc.arma.vgate.domain.usecase
|
package llc.arma.vgate.domain.usecase
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.res.Resources.NotFoundException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import llc.arma.common.domain.Result
|
||||||
import llc.arma.vgate.domain.model.ReadResult
|
import llc.arma.vgate.domain.model.ReadResult
|
||||||
import llc.arma.vgate.domain.model.ReadResultPoint
|
import llc.arma.vgate.domain.model.ReadResultPoint
|
||||||
import llc.arma.vgate.domain.model.SendRequest
|
import llc.arma.vgate.domain.model.SendRequest
|
||||||
|
|
@ -45,21 +47,31 @@ class SendWaitingReports @Inject constructor(
|
||||||
|
|
||||||
return sendRequestRepository.getByStatus(SendRequest.Status.Waiting).all {
|
return sendRequestRepository.getByStatus(SendRequest.Status.Waiting).all {
|
||||||
|
|
||||||
send(it)
|
send(it).isSuccess
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(id: Long): Result<Unit, Throwable> {
|
||||||
|
|
||||||
|
val request = sendRequestRepository.getById(id) ?: return Result.failure(NotFoundException())
|
||||||
|
|
||||||
|
return send(request)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun send(
|
private suspend fun send(
|
||||||
request: SendRequest
|
request: SendRequest
|
||||||
): Boolean {
|
): Result<Unit, Throwable> {
|
||||||
|
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
|
||||||
val formatter = SimpleDateFormat.getDateInstance()
|
val formatter = SimpleDateFormat.getDateInstance()
|
||||||
|
|
||||||
suspend fun finishRequest(
|
suspend fun finishRequest(
|
||||||
status: SendRequest.Status = SendRequest.Status.Sent
|
status: SendRequest.Status = SendRequest.Status.Sent
|
||||||
){
|
) {
|
||||||
sendRequestRepository.save(
|
sendRequestRepository.save(
|
||||||
request.copy(
|
request.copy(
|
||||||
status = status
|
status = status
|
||||||
|
|
@ -67,10 +79,13 @@ class SendWaitingReports @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val statistic = getReadResultSummary(request.readResultId) ?: return run { finishRequest(); true}
|
val statistic = getReadResultSummary(request.readResultId)
|
||||||
|
?: return@withContext run { finishRequest(); Result.success(Unit) }
|
||||||
|
|
||||||
val readResult = readResultRepository.getById(request.readResultId) ?: return run { finishRequest(); true}
|
val readResult = readResultRepository.getById(request.readResultId)
|
||||||
val vehicle = vehicleRepository.getById(readResult.vehicleId) ?: return run { finishRequest(); true}
|
?: return@withContext run { finishRequest(); Result.success(Unit) }
|
||||||
|
val vehicle = vehicleRepository.getById(readResult.vehicleId)
|
||||||
|
?: return@withContext run { finishRequest(); Result.success(Unit) }
|
||||||
val ranges = vibrationRangeRepository.getByVehicleId(vehicle.id)
|
val ranges = vibrationRangeRepository.getByVehicleId(vehicle.id)
|
||||||
|
|
||||||
val workbook = XSSFWorkbook()
|
val workbook = XSSFWorkbook()
|
||||||
|
|
@ -97,13 +112,21 @@ class SendWaitingReports @Inject constructor(
|
||||||
val sendResult = emailRepository.sendFile(
|
val sendResult = emailRepository.sendFile(
|
||||||
subject = "Отчет от vGate по \"${vehicle.name}\"",
|
subject = "Отчет от vGate по \"${vehicle.name}\"",
|
||||||
fileName = "Report.xlsx",
|
fileName = "Report.xlsx",
|
||||||
body = "Статистика активности \"${vehicle.name}\" за ${formatter.format(Date(readResult.date))}" ,
|
body = "Статистика активности \"${vehicle.name}\" за ${
|
||||||
|
formatter.format(
|
||||||
|
Date(
|
||||||
|
readResult.date
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}",
|
||||||
file = reportFile
|
file = reportFile
|
||||||
)
|
)
|
||||||
|
|
||||||
if(sendResult) finishRequest(SendRequest.Status.Sent)
|
if (sendResult.isSuccess) finishRequest(SendRequest.Status.Sent)
|
||||||
|
|
||||||
return sendResult
|
return@withContext sendResult
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue