diff --git a/gradle.properties b/gradle.properties index 26bb2b2..c9648d0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,6 +22,5 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=true org.gradle.unsafe.configuration-cache=true diff --git a/vgate/build.gradle.kts b/vgate/build.gradle.kts index 20a5c59..1ae41ca 100644 --- a/vgate/build.gradle.kts +++ b/vgate/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "llc.arma.vgate" minSdk = 26 targetSdk = 35 - versionCode = 2 - versionName = "1.0.1" + versionCode = 3 + versionName = "1.0.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/vgate/src/main/java/llc/arma/vgate/app/MainActivity.kt b/vgate/src/main/java/llc/arma/vgate/app/MainActivity.kt index 0ac917f..923c0e8 100644 --- a/vgate/src/main/java/llc/arma/vgate/app/MainActivity.kt +++ b/vgate/src/main/java/llc/arma/vgate/app/MainActivity.kt @@ -33,10 +33,12 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import llc.arma.vgate.app.framework.SendRequestWorker import llc.arma.vgate.app.ui.screens.main.MainScreen import llc.arma.vgate.app.ui.theme.BleTheme +import llc.arma.vgate.domain.usecase.GetReceiverEmailFlow import llc.arma.vgate.domain.usecase.GetWaitingReportsFlow import java.time.Duration import javax.inject.Inject @@ -45,6 +47,7 @@ import javax.inject.Inject class MainActivity : ComponentActivity() { @Inject lateinit var getWaitingReportsFlow: GetWaitingReportsFlow + @Inject lateinit var getReceiverEmailFlow: GetReceiverEmailFlow @OptIn(ExperimentalPermissionsApi::class) override fun onCreate(savedInstanceState: Bundle?) { @@ -63,11 +66,13 @@ class MainActivity : ComponentActivity() { val workManager = WorkManager.getInstance(this) - getWaitingReportsFlow().onEach { + merge(getWaitingReportsFlow.invoke(), getReceiverEmailFlow.invoke()).onEach { + + println(it) workManager.enqueueUniqueWork( uniqueWorkName = "Upload", - existingWorkPolicy = ExistingWorkPolicy.KEEP, + existingWorkPolicy = ExistingWorkPolicy.REPLACE, request = uploadWorkRequest ) diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/home/HomeScreen.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/home/HomeScreen.kt index cd8def7..965fe70 100644 --- a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/home/HomeScreen.kt +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/home/HomeScreen.kt @@ -50,7 +50,7 @@ fun HomeScreen( } ) { - Image( + Icon( imageVector = Icons.Rounded.Settings, contentDescription = null ) diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/main/MainScreen.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/main/MainScreen.kt index 0d551e2..dbf6d96 100644 --- a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/main/MainScreen.kt +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/main/MainScreen.kt @@ -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.selector.BleSelectorContract 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.VehicleFormScreen import llc.arma.vgate.app.ui.screens.vehicle.selector.VehicleSelectorContract @@ -54,6 +56,11 @@ data class ReadResultScreenRoute( val resultId: Long ) +@Serializable +data class EmailSenderScreenRoute( + val requestId: Long +) + @Serializable data object VehiclesScreenRoute @@ -145,6 +152,17 @@ fun MainScreen() { } + composable { + EmailSenderScreen( + onNavigationEvent = { + when(it){ + EmailSenderContract.Effect.Navigation.Up -> + controller.navigateUp() + } + } + ) + } + composable { SendRequestsScreen( @@ -152,6 +170,9 @@ fun MainScreen() { when(it){ SendRequestsContract.Effect.Navigation.NavigateUp -> controller.popBackStack() + + is SendRequestsContract.Effect.Navigation.RequestSend -> + controller.navigate(EmailSenderScreenRoute(it.requestId)) } } ) diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsContract.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsContract.kt index 03dec01..0800e96 100644 --- a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsContract.kt +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsContract.kt @@ -8,7 +8,13 @@ import llc.arma.vgate.domain.model.Vehicle class SendRequestsContract { - sealed class Event : ViewEvent + sealed class Event : ViewEvent { + + data class OnSend( + val requestId: Long + ) : Event() + + } sealed class State : ViewState { @@ -24,6 +30,10 @@ class SendRequestsContract { sealed class Navigation : Effect() { + data class RequestSend( + val requestId: Long + ) : Navigation() + data object NavigateUp : Navigation() } diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsScreen.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsScreen.kt index 01b3ede..dff8777 100644 --- a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsScreen.kt +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth 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.material.icons.Icons 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.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -25,6 +26,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -53,6 +55,16 @@ fun SendRequestsScreen( val viewModel = hiltViewModel() val state = viewModel.viewState.value + LaunchedEffect(Unit) { + viewModel.effect.collect { + + when(it){ + is SendRequestsContract.Effect.Navigation -> onNavigationEvent(it) + } + + } + } + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() Scaffold( @@ -126,7 +138,9 @@ private fun DisplayState( 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 private fun RequestItem( request: SendRequest, - vehicle: Vehicle + vehicle: Vehicle, + onSend: () -> Unit ){ Surface( @@ -148,23 +163,42 @@ private fun RequestItem( modifier = Modifier.fillMaxWidth() ) { - Column( + Row( modifier = Modifier.padding(16.dp) ) { - Text( - text = vehicle.name - ) + Column( + modifier = Modifier.weight(1f) + ) { - Text( - text = "Создан: ${formatter.format(Date(request.date))}", - style = MaterialTheme.typography.bodySmall - ) + Text( + text = vehicle.name + ) - Text( - text = "Статус: ${request.status.localized}", - style = MaterialTheme.typography.bodySmall - ) + Text( + text = "Создан: ${formatter.format(Date(request.date))}", + style = MaterialTheme.typography.bodySmall + ) + + Text( + text = "Статус: ${request.status.localized}", + style = MaterialTheme.typography.bodySmall + ) + + } + + if(request.status == SendRequest.Status.Waiting) { + + IconButton( + onClick = onSend + ) { + Icon( + imageVector = Icons.Rounded.Upload, + contentDescription = null + ) + } + + } } diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsViewModel.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsViewModel.kt index 1b36244..3bb7f62 100644 --- a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsViewModel.kt +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/requests/SendRequestsViewModel.kt @@ -42,7 +42,18 @@ class SendRequestsViewModel @Inject constructor( override fun setInitialState() = SendRequestsContract.State.Loading 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) + } } } \ No newline at end of file diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderContract.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderContract.kt new file mode 100644 index 0000000..2452d93 --- /dev/null +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderContract.kt @@ -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() + + } + + } + +} diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderScreen.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderScreen.kt new file mode 100644 index 0000000..84d279b --- /dev/null +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderScreen.kt @@ -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() + 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 = "Ок" + ) + + } + + } + + } + +} \ No newline at end of file diff --git a/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderViewModel.kt b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderViewModel.kt new file mode 100644 index 0000000..9f03916 --- /dev/null +++ b/vgate/src/main/java/llc/arma/vgate/app/ui/screens/sender/EmailSenderViewModel.kt @@ -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() { + + 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() + + setState { + EmailSenderContract.State.Loading + } + + sendJob = viewModelScope.launch { + sendWaitingReports.invoke(params.requestId).fold( + onSuccess = { + setState { + EmailSenderContract.State.Success + } + }, + onFailure = { + setState { + EmailSenderContract.State.Error(it) + } + } + ) + } + + } + +} \ No newline at end of file diff --git a/vgate/src/main/java/llc/arma/vgate/data/repository/EmailRepositoryImpl.kt b/vgate/src/main/java/llc/arma/vgate/data/repository/EmailRepositoryImpl.kt index 8bd17a1..766ba5c 100644 --- a/vgate/src/main/java/llc/arma/vgate/data/repository/EmailRepositoryImpl.kt +++ b/vgate/src/main/java/llc/arma/vgate/data/repository/EmailRepositoryImpl.kt @@ -18,6 +18,7 @@ import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map +import llc.arma.common.domain.Result import llc.arma.vgate.domain.repository.EmailRepository import java.io.File import java.util.Properties @@ -87,7 +88,7 @@ class EmailRepositoryImpl @Inject constructor( body: String, fileName: String, file: File - ): Boolean { + ): Result { return coroutineScope { @@ -103,10 +104,10 @@ class EmailRepositoryImpl @Inject constructor( body = body, ) ) - true + Result.success(Unit) } catch (err: Throwable) { err.printStackTrace() - false + Result.failure(err) } } diff --git a/vgate/src/main/java/llc/arma/vgate/domain/repository/EmailRepository.kt b/vgate/src/main/java/llc/arma/vgate/domain/repository/EmailRepository.kt index a6ac786..4470423 100644 --- a/vgate/src/main/java/llc/arma/vgate/domain/repository/EmailRepository.kt +++ b/vgate/src/main/java/llc/arma/vgate/domain/repository/EmailRepository.kt @@ -1,6 +1,7 @@ package llc.arma.vgate.domain.repository import kotlinx.coroutines.flow.Flow +import llc.arma.common.domain.Result import java.io.File interface EmailRepository { @@ -10,7 +11,7 @@ interface EmailRepository { body: String, fileName: String, file: File - ) : Boolean + ): Result suspend fun saveEmail(email: String) diff --git a/vgate/src/main/java/llc/arma/vgate/domain/usecase/SendWaitingReports.kt b/vgate/src/main/java/llc/arma/vgate/domain/usecase/SendWaitingReports.kt index 575d1df..34b5f8f 100644 --- a/vgate/src/main/java/llc/arma/vgate/domain/usecase/SendWaitingReports.kt +++ b/vgate/src/main/java/llc/arma/vgate/domain/usecase/SendWaitingReports.kt @@ -1,8 +1,10 @@ package llc.arma.vgate.domain.usecase import android.app.Application +import android.content.res.Resources.NotFoundException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import llc.arma.common.domain.Result import llc.arma.vgate.domain.model.ReadResult import llc.arma.vgate.domain.model.ReadResultPoint import llc.arma.vgate.domain.model.SendRequest @@ -45,65 +47,86 @@ class SendWaitingReports @Inject constructor( return sendRequestRepository.getByStatus(SendRequest.Status.Waiting).all { - send(it) + send(it).isSuccess } } + suspend operator fun invoke(id: Long): Result { + + val request = sendRequestRepository.getById(id) ?: return Result.failure(NotFoundException()) + + return send(request) + + } + private suspend fun send( request: SendRequest - ): Boolean { + ): Result { - val formatter = SimpleDateFormat.getDateInstance() + return withContext(Dispatchers.IO) { - suspend fun finishRequest( - status: SendRequest.Status = SendRequest.Status.Sent - ){ - sendRequestRepository.save( - request.copy( - status = status + val formatter = SimpleDateFormat.getDateInstance() + + suspend fun finishRequest( + status: SendRequest.Status = SendRequest.Status.Sent + ) { + sendRequestRepository.save( + request.copy( + status = status + ) ) - ) - } - - val statistic = getReadResultSummary(request.readResultId) ?: return run { finishRequest(); true} - - val readResult = readResultRepository.getById(request.readResultId) ?: return run { finishRequest(); true} - val vehicle = vehicleRepository.getById(readResult.vehicleId) ?: return run { finishRequest(); true} - val ranges = vibrationRangeRepository.getByVehicleId(vehicle.id) - - val workbook = XSSFWorkbook() - - workbook.createTableSheet( - vehicle = vehicle, - statistic = statistic - ) - - workbook.createRawDataSheet( - vehicle = vehicle, - ranges = ranges, - history = statistic.history - ) - - val reportFile = File.createTempFile("snd", ".xlsx", app.cacheDir) - - withContext(Dispatchers.IO) { - FileOutputStream(reportFile).use { - workbook.write(it) } + + val statistic = getReadResultSummary(request.readResultId) + ?: return@withContext run { finishRequest(); Result.success(Unit) } + + val readResult = readResultRepository.getById(request.readResultId) + ?: 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 workbook = XSSFWorkbook() + + workbook.createTableSheet( + vehicle = vehicle, + statistic = statistic + ) + + workbook.createRawDataSheet( + vehicle = vehicle, + ranges = ranges, + history = statistic.history + ) + + val reportFile = File.createTempFile("snd", ".xlsx", app.cacheDir) + + withContext(Dispatchers.IO) { + FileOutputStream(reportFile).use { + workbook.write(it) + } + } + + val sendResult = emailRepository.sendFile( + subject = "Отчет от vGate по \"${vehicle.name}\"", + fileName = "Report.xlsx", + body = "Статистика активности \"${vehicle.name}\" за ${ + formatter.format( + Date( + readResult.date + ) + ) + }", + file = reportFile + ) + + if (sendResult.isSuccess) finishRequest(SendRequest.Status.Sent) + + return@withContext sendResult + } - - val sendResult = emailRepository.sendFile( - subject = "Отчет от vGate по \"${vehicle.name}\"", - fileName = "Report.xlsx", - body = "Статистика активности \"${vehicle.name}\" за ${formatter.format(Date(readResult.date))}" , - file = reportFile - ) - - if(sendResult) finishRequest(SendRequest.Status.Sent) - - return sendResult }