email sender

This commit is contained in:
Vineyro 2025-06-17 03:52:31 +07:00
parent 39297abc6c
commit 02138f3f2f
14 changed files with 676 additions and 73 deletions

View File

@ -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

View File

@ -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"
} }

View File

@ -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
) )

View File

@ -50,7 +50,7 @@ fun HomeScreen(
} }
) { ) {
Image( Icon(
imageVector = Icons.Rounded.Settings, imageVector = Icons.Rounded.Settings,
contentDescription = null contentDescription = null
) )

View File

@ -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))
} }
} }
) )

View File

@ -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()
} }

View File

@ -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,23 +163,42 @@ private fun RequestItem(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Column( Row(
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
Text( Column(
text = vehicle.name modifier = Modifier.weight(1f)
) ) {
Text( Text(
text = "Создан: ${formatter.format(Date(request.date))}", text = vehicle.name
style = MaterialTheme.typography.bodySmall )
)
Text( Text(
text = "Статус: ${request.status.localized}", text = "Создан: ${formatter.format(Date(request.date))}",
style = MaterialTheme.typography.bodySmall 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
)
}
}
} }

View File

@ -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)
}
} }
} }

View File

@ -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()
}
}
}

View File

@ -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 = "Ок"
)
}
}
}
}

View File

@ -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)
}
}
)
}
}
}

View File

@ -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)
} }
} }

View File

@ -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)

View File

@ -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,65 +47,86 @@ 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> {
val formatter = SimpleDateFormat.getDateInstance() return withContext(Dispatchers.IO) {
suspend fun finishRequest( val formatter = SimpleDateFormat.getDateInstance()
status: SendRequest.Status = SendRequest.Status.Sent
){ suspend fun finishRequest(
sendRequestRepository.save( status: SendRequest.Status = SendRequest.Status.Sent
request.copy( ) {
status = status 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
} }