AboutScreen: new button to send the last captured image (#88)
This commit is contained in:
@@ -70,7 +70,6 @@ class AppContainer(context: Context) {
|
|||||||
|
|
||||||
val homeViewModelFactory = viewModelFactory { HomeViewModel(it, context) }
|
val homeViewModelFactory = viewModelFactory { HomeViewModel(it, context) }
|
||||||
val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) }
|
val cameraViewModelFactory = viewModelFactory { CameraViewModel(it) }
|
||||||
val aboutViewModelFactory = viewModelFactory { AboutViewModel(it) }
|
|
||||||
val settingsViewModelFactory = viewModelFactory { SettingsViewModel(it) }
|
val settingsViewModelFactory = viewModelFactory { SettingsViewModel(it) }
|
||||||
|
|
||||||
fun cleanOrphanSessions() {
|
fun cleanOrphanSessions() {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.fairscan.app.data.FileLogger
|
import org.fairscan.app.data.FileLogger
|
||||||
|
import org.fairscan.app.data.ImageRepository
|
||||||
import org.fairscan.app.ui.Navigation
|
import org.fairscan.app.ui.Navigation
|
||||||
import org.fairscan.app.ui.Screen
|
import org.fairscan.app.ui.Screen
|
||||||
import org.fairscan.app.ui.components.rememberCameraPermissionState
|
import org.fairscan.app.ui.components.rememberCameraPermissionState
|
||||||
@@ -58,6 +59,7 @@ import org.fairscan.app.ui.screens.LibrariesScreen
|
|||||||
import org.fairscan.app.ui.screens.about.AboutEvent
|
import org.fairscan.app.ui.screens.about.AboutEvent
|
||||||
import org.fairscan.app.ui.screens.about.AboutScreen
|
import org.fairscan.app.ui.screens.about.AboutScreen
|
||||||
import org.fairscan.app.ui.screens.about.AboutViewModel
|
import org.fairscan.app.ui.screens.about.AboutViewModel
|
||||||
|
import org.fairscan.app.ui.screens.about.createEmailWithImageIntent
|
||||||
import org.fairscan.app.ui.screens.camera.CameraEvent
|
import org.fairscan.app.ui.screens.camera.CameraEvent
|
||||||
import org.fairscan.app.ui.screens.camera.CameraScreen
|
import org.fairscan.app.ui.screens.camera.CameraScreen
|
||||||
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
import org.fairscan.app.ui.screens.camera.CameraViewModel
|
||||||
@@ -101,9 +103,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
ExportViewModel(appContainer, sessionContainer.imageRepository)
|
ExportViewModel(appContainer, sessionContainer.imageRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val aboutViewModel: AboutViewModel by viewModels {
|
||||||
|
appContainer.viewModelFactory {
|
||||||
|
AboutViewModel(appContainer, sessionContainer.imageRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
val homeViewModel: HomeViewModel by viewModels { appContainer.homeViewModelFactory }
|
val homeViewModel: HomeViewModel by viewModels { appContainer.homeViewModelFactory }
|
||||||
val cameraViewModel: CameraViewModel by viewModels { appContainer.cameraViewModelFactory }
|
val cameraViewModel: CameraViewModel by viewModels { appContainer.cameraViewModelFactory }
|
||||||
val aboutViewModel: AboutViewModel by viewModels { appContainer.aboutViewModelFactory }
|
|
||||||
val settingsViewModel: SettingsViewModel
|
val settingsViewModel: SettingsViewModel
|
||||||
by viewModels { appContainer.settingsViewModelFactory }
|
by viewModels { appContainer.settingsViewModelFactory }
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
@@ -119,7 +126,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val cameraPermission = rememberCameraPermissionState()
|
val cameraPermission = rememberCameraPermissionState()
|
||||||
CollectCameraEvents(cameraViewModel, viewModel)
|
CollectCameraEvents(cameraViewModel, viewModel)
|
||||||
CollectExportEvents(context, exportViewModel)
|
CollectExportEvents(context, exportViewModel)
|
||||||
CollectAboutEvents(context, aboutViewModel)
|
CollectAboutEvents(context, aboutViewModel, sessionContainer.imageRepository)
|
||||||
|
|
||||||
FairScanTheme {
|
FairScanTheme {
|
||||||
val navigation = navigation(viewModel, launchMode)
|
val navigation = navigation(viewModel, launchMode)
|
||||||
@@ -187,9 +194,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Screen.Overlay.About -> {
|
is Screen.Overlay.About -> {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
aboutViewModel.refreshLastCapturedImageState()
|
||||||
|
}
|
||||||
|
val aboutUiState by aboutViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
AboutScreen(
|
AboutScreen(
|
||||||
|
aboutUiState = aboutUiState,
|
||||||
onBack = navigation.back,
|
onBack = navigation.back,
|
||||||
onCopyLogs = { aboutViewModel.onCopyLogsClicked() },
|
onCopyLogs = { aboutViewModel.onCopyLogsClicked() },
|
||||||
|
onContactWithLastImageClicked =
|
||||||
|
{ aboutViewModel.onContactWithLastImageClicked() },
|
||||||
onViewLibraries = navigation.toLibrariesScreen)
|
onViewLibraries = navigation.toLibrariesScreen)
|
||||||
}
|
}
|
||||||
is Screen.Overlay.Libraries -> {
|
is Screen.Overlay.Libraries -> {
|
||||||
@@ -262,6 +276,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
private fun CollectAboutEvents(
|
private fun CollectAboutEvents(
|
||||||
context: Context,
|
context: Context,
|
||||||
aboutViewModel: AboutViewModel,
|
aboutViewModel: AboutViewModel,
|
||||||
|
imageRepository: ImageRepository,
|
||||||
) {
|
) {
|
||||||
val clipboard = LocalClipboard.current
|
val clipboard = LocalClipboard.current
|
||||||
val msgCopiedLogs = stringResource(R.string.copied_logs)
|
val msgCopiedLogs = stringResource(R.string.copied_logs)
|
||||||
@@ -274,6 +289,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
Toast.makeText(context, msgCopiedLogs, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, msgCopiedLogs, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
is AboutEvent.PrepareEmailWithLastImage -> {
|
||||||
|
val file = imageRepository.lastAddedSourceFile()
|
||||||
|
if (file != null) {
|
||||||
|
startActivity(createEmailWithImageIntent(context, file))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,8 +391,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun uriForFile(file: File): Uri {
|
private fun uriForFile(file: File): Uri {
|
||||||
val authority = "${applicationContext.packageName}.fileprovider"
|
return org.fairscan.app.ui.uriForFile(this, file)
|
||||||
return FileProvider.getUriForFile(this, authority, file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkPermissionThen(
|
private fun checkPermissionThen(
|
||||||
|
|||||||
@@ -298,6 +298,14 @@ class ImageRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun lastAddedSourceFile(): File? {
|
||||||
|
val sourceFiles = sourceDir.listFiles()?.filter { it.extension == "jpg" }
|
||||||
|
if (sourceFiles.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return sourceFiles.maxByOrNull { it.lastModified() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Quad.toSerializable(): NormalizedQuad =
|
fun Quad.toSerializable(): NormalizedQuad =
|
||||||
|
|||||||
25
app/src/main/java/org/fairscan/app/ui/FileUris.kt
Normal file
25
app/src/main/java/org/fairscan/app/ui/FileUris.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Pierre-Yves Nicolas
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.fairscan.app.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun uriForFile(context: Context, file: File): Uri {
|
||||||
|
val authority = "${context.packageName}.fileprovider"
|
||||||
|
return FileProvider.getUriForFile(context, authority, file)
|
||||||
|
}
|
||||||
@@ -57,6 +57,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
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.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -76,8 +77,10 @@ import org.fairscan.app.ui.theme.FairScanTheme
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AboutScreen(
|
fun AboutScreen(
|
||||||
|
aboutUiState: AboutUiState,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onCopyLogs: () -> Unit,
|
onCopyLogs: () -> Unit,
|
||||||
|
onContactWithLastImageClicked: () -> Unit,
|
||||||
onViewLibraries: () -> Unit,
|
onViewLibraries: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val showLicenseDialog = rememberSaveable { mutableStateOf(false) }
|
val showLicenseDialog = rememberSaveable { mutableStateOf(false) }
|
||||||
@@ -93,7 +96,9 @@ fun AboutScreen(
|
|||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
AboutContent(
|
AboutContent(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
aboutUiState,
|
||||||
onCopyLogs,
|
onCopyLogs,
|
||||||
|
onContactWithLastImageClicked,
|
||||||
showLicenseDialog,
|
showLicenseDialog,
|
||||||
onViewLibraries)
|
onViewLibraries)
|
||||||
}
|
}
|
||||||
@@ -105,7 +110,9 @@ fun AboutScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
fun AboutContent(
|
fun AboutContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
aboutUiState: AboutUiState,
|
||||||
onCopyLogs: () -> Unit,
|
onCopyLogs: () -> Unit,
|
||||||
|
onContactWithLastImageClicked: () -> Unit,
|
||||||
showLicenseDialog: MutableState<Boolean>,
|
showLicenseDialog: MutableState<Boolean>,
|
||||||
onViewLibraries: () -> Unit,
|
onViewLibraries: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -145,16 +152,10 @@ fun AboutContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section(title = stringResource(R.string.contact)) {
|
Section(title = stringResource(R.string.contact)) {
|
||||||
val emailAddress = "contact@fairscan.org"
|
|
||||||
ContactLink(
|
ContactLink(
|
||||||
icon = Icons.Default.Email,
|
icon = Icons.Default.Email,
|
||||||
text = emailAddress,
|
text = EMAIL_ADDRESS,
|
||||||
onClick = {
|
onClick = { context.startActivity(createContactEmailIntent()) }
|
||||||
val intent = Intent(Intent.ACTION_SENDTO).apply {
|
|
||||||
data = "mailto:$emailAddress".toUri()
|
|
||||||
}
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
val websiteUrl = "https://fairscan.org"
|
val websiteUrl = "https://fairscan.org"
|
||||||
ContactLink(
|
ContactLink(
|
||||||
@@ -165,6 +166,10 @@ fun AboutContent(
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Section(title = stringResource(R.string.support)) {
|
||||||
|
EmailImageButton(aboutUiState, onContactWithLastImageClicked)
|
||||||
CopyLogsButton (onClick = onCopyLogs)
|
CopyLogsButton (onClick = onCopyLogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,11 +300,33 @@ fun CopyLogsButton(onClick: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EmailImageButton(
|
||||||
|
aboutUiState: AboutUiState,
|
||||||
|
onContactWithLastImageClicked: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
enabled = aboutUiState.hasLastCapturedImage,
|
||||||
|
onClick = onContactWithLastImageClicked
|
||||||
|
)
|
||||||
|
.alpha(if (aboutUiState.hasLastCapturedImage) 1f else 0.5f)
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Email, contentDescription = null)
|
||||||
|
Spacer(Modifier.width(16.dp))
|
||||||
|
Text(stringResource(R.string.support_last_image))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun AboutScreenPreview() {
|
fun AboutScreenPreview() {
|
||||||
FairScanTheme {
|
FairScanTheme {
|
||||||
AboutScreen(onBack = {}, onCopyLogs = {}, onViewLibraries = {})
|
val state = AboutUiState(true)
|
||||||
|
AboutScreen(state, {}, {}, {}, {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Pierre-Yves Nicolas
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.fairscan.app.ui.screens.about
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class AboutUiState(
|
||||||
|
val hasLastCapturedImage: Boolean = false,
|
||||||
|
)
|
||||||
@@ -18,23 +18,31 @@ import android.os.Build
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.fairscan.app.AppContainer
|
import org.fairscan.app.AppContainer
|
||||||
import org.fairscan.app.BuildConfig
|
import org.fairscan.app.BuildConfig
|
||||||
|
import org.fairscan.app.data.ImageRepository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
sealed interface AboutEvent {
|
sealed interface AboutEvent {
|
||||||
data class CopyLogs(val logs: String) : AboutEvent
|
data class CopyLogs(val logs: String) : AboutEvent
|
||||||
|
object PrepareEmailWithLastImage : AboutEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
class AboutViewModel(container: AppContainer): ViewModel() {
|
class AboutViewModel(container: AppContainer, val imageRepository: ImageRepository): ViewModel() {
|
||||||
|
|
||||||
private val logRepository = container.logRepository
|
private val logRepository = container.logRepository
|
||||||
|
|
||||||
private val _events = MutableSharedFlow<AboutEvent>()
|
private val _events = MutableSharedFlow<AboutEvent>()
|
||||||
val events = _events.asSharedFlow()
|
val events = _events.asSharedFlow()
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(AboutUiState())
|
||||||
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
fun onCopyLogsClicked() {
|
fun onCopyLogsClicked() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val logs = buildFullLogs()
|
val logs = buildFullLogs()
|
||||||
@@ -55,4 +63,15 @@ class AboutViewModel(container: AppContainer): ViewModel() {
|
|||||||
return header + logRepository.getLogs()
|
return header + logRepository.getLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refreshLastCapturedImageState() {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(hasLastCapturedImage = imageRepository.lastAddedSourceFile() != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onContactWithLastImageClicked() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_events.emit(AboutEvent.PrepareEmailWithLastImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Pierre-Yves Nicolas
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.fairscan.app.ui.screens.about
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import org.fairscan.app.BuildConfig
|
||||||
|
import org.fairscan.app.ui.uriForFile
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
const val EMAIL_ADDRESS = "contact@fairscan.org"
|
||||||
|
|
||||||
|
fun createContactEmailIntent(): Intent =
|
||||||
|
Intent(Intent.ACTION_SENDTO).apply {
|
||||||
|
data = "mailto:$EMAIL_ADDRESS".toUri()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createEmailWithImageIntent(context: Context, imageFile: File?): Intent {
|
||||||
|
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
type = "image/jpeg"
|
||||||
|
putExtra(Intent.EXTRA_EMAIL, arrayOf(EMAIL_ADDRESS))
|
||||||
|
putExtra(
|
||||||
|
Intent.EXTRA_SUBJECT,
|
||||||
|
"FairScan ${BuildConfig.VERSION_NAME}"
|
||||||
|
)
|
||||||
|
if (imageFile != null) {
|
||||||
|
val uri = uriForFile(context, imageFile)
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Intent.createChooser(intent, null)
|
||||||
|
}
|
||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">Sdílet</string>
|
<string name="share">Sdílet</string>
|
||||||
<string name="share_document">Sdílet dokument</string>
|
<string name="share_document">Sdílet dokument</string>
|
||||||
<string name="storage_permission_denied">Nelze uložit soubor: oprávnění bylo odmítnuto</string>
|
<string name="storage_permission_denied">Nelze uložit soubor: oprávnění bylo odmítnuto</string>
|
||||||
|
<string name="support">Podpora</string>
|
||||||
|
<string name="support_last_image">Nahlásit problém s posledním zachyceným obrázkem</string>
|
||||||
<string name="turn_off_torch">Vypnout svítilnu</string>
|
<string name="turn_off_torch">Vypnout svítilnu</string>
|
||||||
<string name="turn_on_torch">Zapnout svítilnu</string>
|
<string name="turn_on_torch">Zapnout svítilnu</string>
|
||||||
<string name="unknown_size">Neznámá velikost</string>
|
<string name="unknown_size">Neznámá velikost</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">Teilen</string>
|
<string name="share">Teilen</string>
|
||||||
<string name="share_document">Dokument teilen</string>
|
<string name="share_document">Dokument teilen</string>
|
||||||
<string name="storage_permission_denied">Datei kann nicht gespeichert werden: Berechtigung verweigert</string>
|
<string name="storage_permission_denied">Datei kann nicht gespeichert werden: Berechtigung verweigert</string>
|
||||||
|
<string name="support">Support</string>
|
||||||
|
<string name="support_last_image">Problem mit dem zuletzt aufgenommenen Bild melden</string>
|
||||||
<string name="turn_off_torch">Taschenlampe ausschalten</string>
|
<string name="turn_off_torch">Taschenlampe ausschalten</string>
|
||||||
<string name="turn_on_torch">Taschenlampe einschalten</string>
|
<string name="turn_on_torch">Taschenlampe einschalten</string>
|
||||||
<string name="unknown_size">Unbekannte Größe</string>
|
<string name="unknown_size">Unbekannte Größe</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">Compartir</string>
|
<string name="share">Compartir</string>
|
||||||
<string name="share_document">Compartir documento</string>
|
<string name="share_document">Compartir documento</string>
|
||||||
<string name="storage_permission_denied">No se puede guardar el archivo: permiso denegado</string>
|
<string name="storage_permission_denied">No se puede guardar el archivo: permiso denegado</string>
|
||||||
|
<string name="support">Soporte</string>
|
||||||
|
<string name="support_last_image">Informar de un problema con la última imagen capturada</string>
|
||||||
<string name="turn_off_torch">Apagar linterna</string>
|
<string name="turn_off_torch">Apagar linterna</string>
|
||||||
<string name="turn_on_torch">Encender linterna</string>
|
<string name="turn_on_torch">Encender linterna</string>
|
||||||
<string name="unknown_size">Tamaño desconocido</string>
|
<string name="unknown_size">Tamaño desconocido</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">Partager</string>
|
<string name="share">Partager</string>
|
||||||
<string name="share_document">Partager le document</string>
|
<string name="share_document">Partager le document</string>
|
||||||
<string name="storage_permission_denied">Impossible d’enregistrer le fichier : permission refusée</string>
|
<string name="storage_permission_denied">Impossible d’enregistrer le fichier : permission refusée</string>
|
||||||
|
<string name="support">Support</string>
|
||||||
|
<string name="support_last_image">Signaler un problème avec la dernière image capturée</string>
|
||||||
<string name="turn_off_torch">Éteindre la torche</string>
|
<string name="turn_off_torch">Éteindre la torche</string>
|
||||||
<string name="turn_on_torch">Allumer la torche</string>
|
<string name="turn_on_torch">Allumer la torche</string>
|
||||||
<string name="unknown_size">Taille inconnue</string>
|
<string name="unknown_size">Taille inconnue</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">Condividi</string>
|
<string name="share">Condividi</string>
|
||||||
<string name="share_document">Condividi documento</string>
|
<string name="share_document">Condividi documento</string>
|
||||||
<string name="storage_permission_denied">Impossibile salvare il file: permesso negato</string>
|
<string name="storage_permission_denied">Impossibile salvare il file: permesso negato</string>
|
||||||
|
<string name="support">Supporto</string>
|
||||||
|
<string name="support_last_image">Segnala un problema con l’ultima immagine acquisita</string>
|
||||||
<string name="turn_off_torch">Spegni la torcia</string>
|
<string name="turn_off_torch">Spegni la torcia</string>
|
||||||
<string name="turn_on_torch">Accendi la torcia</string>
|
<string name="turn_on_torch">Accendi la torcia</string>
|
||||||
<string name="unknown_size">Dimensione sconosciuta</string>
|
<string name="unknown_size">Dimensione sconosciuta</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">Compartilhar</string>
|
<string name="share">Compartilhar</string>
|
||||||
<string name="share_document">Compartilhar documento</string>
|
<string name="share_document">Compartilhar documento</string>
|
||||||
<string name="storage_permission_denied">Não foi possível salvar o arquivo: permissão negada</string>
|
<string name="storage_permission_denied">Não foi possível salvar o arquivo: permissão negada</string>
|
||||||
|
<string name="support">Suporte</string>
|
||||||
|
<string name="support_last_image">Relatar um problema com a última imagem capturada</string>
|
||||||
<string name="turn_off_torch">Desligar lanterna</string>
|
<string name="turn_off_torch">Desligar lanterna</string>
|
||||||
<string name="turn_on_torch">Ligar lanterna</string>
|
<string name="turn_on_torch">Ligar lanterna</string>
|
||||||
<string name="unknown_size">Tamanho desconhecido</string>
|
<string name="unknown_size">Tamanho desconhecido</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">Поделиться</string>
|
<string name="share">Поделиться</string>
|
||||||
<string name="share_document">Поделиться документом</string>
|
<string name="share_document">Поделиться документом</string>
|
||||||
<string name="storage_permission_denied">Невозможно сохранить файл: доступ запрещён</string>
|
<string name="storage_permission_denied">Невозможно сохранить файл: доступ запрещён</string>
|
||||||
|
<string name="support">Поддержка</string>
|
||||||
|
<string name="support_last_image">Сообщить о проблеме с последним сделанным изображением</string>
|
||||||
<string name="turn_off_torch">Выключить фонарик</string>
|
<string name="turn_off_torch">Выключить фонарик</string>
|
||||||
<string name="turn_on_torch">Включить фонарик</string>
|
<string name="turn_on_torch">Включить фонарик</string>
|
||||||
<string name="unknown_size">Неизвестный размер</string>
|
<string name="unknown_size">Неизвестный размер</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">分享</string>
|
<string name="share">分享</string>
|
||||||
<string name="share_document">分享文件</string>
|
<string name="share_document">分享文件</string>
|
||||||
<string name="storage_permission_denied">無法儲存檔案:權限遭拒</string>
|
<string name="storage_permission_denied">無法儲存檔案:權限遭拒</string>
|
||||||
|
<string name="support">支援</string>
|
||||||
|
<string name="support_last_image">回報最近拍攝影像的問題</string>
|
||||||
<string name="turn_off_torch">關閉閃光燈</string>
|
<string name="turn_off_torch">關閉閃光燈</string>
|
||||||
<string name="turn_on_torch">開啟閃光燈</string>
|
<string name="turn_on_torch">開啟閃光燈</string>
|
||||||
<string name="unknown_size">未知大小</string>
|
<string name="unknown_size">未知大小</string>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
<string name="share">共享</string>
|
<string name="share">共享</string>
|
||||||
<string name="share_document">分享文档</string>
|
<string name="share_document">分享文档</string>
|
||||||
<string name="storage_permission_denied">无法保存文件:权限被拒绝</string>
|
<string name="storage_permission_denied">无法保存文件:权限被拒绝</string>
|
||||||
|
<string name="support">支持</string>
|
||||||
|
<string name="support_last_image">报告最近拍摄图像的问题</string>
|
||||||
<string name="turn_off_torch">关闭手电筒</string>
|
<string name="turn_off_torch">关闭手电筒</string>
|
||||||
<string name="turn_on_torch">打开手电筒</string>
|
<string name="turn_on_torch">打开手电筒</string>
|
||||||
<string name="unknown_size">未知大小</string>
|
<string name="unknown_size">未知大小</string>
|
||||||
|
|||||||
@@ -56,6 +56,8 @@
|
|||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
<string name="share_document">Share document</string>
|
<string name="share_document">Share document</string>
|
||||||
<string name="storage_permission_denied">Cannot save file: permission was denied</string>
|
<string name="storage_permission_denied">Cannot save file: permission was denied</string>
|
||||||
|
<string name="support">Support</string>
|
||||||
|
<string name="support_last_image">Report a problem with last captured image</string>
|
||||||
<string name="turn_off_torch">Turn off torch</string>
|
<string name="turn_off_torch">Turn off torch</string>
|
||||||
<string name="turn_on_torch">Turn on torch</string>
|
<string name="turn_on_torch">Turn on torch</string>
|
||||||
<string name="unknown_size">Unknown size</string>
|
<string name="unknown_size">Unknown size</string>
|
||||||
|
|||||||
@@ -6,4 +6,8 @@
|
|||||||
<external-path
|
<external-path
|
||||||
name="external_files"
|
name="external_files"
|
||||||
path="." />
|
path="." />
|
||||||
|
<!-- source images (to send the last captured image) -->
|
||||||
|
<files-path
|
||||||
|
name="sources"
|
||||||
|
path="sources/" />
|
||||||
</paths>
|
</paths>
|
||||||
|
|||||||
@@ -242,6 +242,29 @@ class ImageRepositoryTest {
|
|||||||
assertThat(PageV2("1", 42, 0, quad, true).toMetadata()).isNull()
|
assertThat(PageV2("1", 42, 0, quad, true).toMetadata()).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun last_added_source_file() {
|
||||||
|
val repo = repo()
|
||||||
|
assertThat(repo.lastAddedSourceFile()).isNull()
|
||||||
|
repo.add(byteArrayOf(101), byteArrayOf(51), metadata1)
|
||||||
|
assertThat(repo.lastAddedSourceFile()).hasBinaryContent(byteArrayOf(51))
|
||||||
|
Thread.sleep(1)
|
||||||
|
repo.add(byteArrayOf(102), byteArrayOf(52), metadata1)
|
||||||
|
assertThat(repo.lastAddedSourceFile()).hasBinaryContent(byteArrayOf(52))
|
||||||
|
|
||||||
|
val id = repo.imageIds().last()
|
||||||
|
repo.movePage(id, 0)
|
||||||
|
assertThat(repo.lastAddedSourceFile()).hasBinaryContent(byteArrayOf(52))
|
||||||
|
repo.delete(id)
|
||||||
|
assertThat(repo.lastAddedSourceFile()).hasBinaryContent(byteArrayOf(51))
|
||||||
|
|
||||||
|
val repo2 = repo()
|
||||||
|
assertThat(repo2.lastAddedSourceFile()).hasBinaryContent(byteArrayOf(51))
|
||||||
|
|
||||||
|
repo2.clear()
|
||||||
|
assertThat(repo2.lastAddedSourceFile()).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
private fun scanDir(): File = File(getFilesDir(), SCAN_DIR_NAME)
|
private fun scanDir(): File = File(getFilesDir(), SCAN_DIR_NAME)
|
||||||
private fun sourceDir(): File = File(getFilesDir(), SOURCE_DIR_NAME)
|
private fun sourceDir(): File = File(getFilesDir(), SOURCE_DIR_NAME)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user