Plug input data to EditPageScreen

This commit is contained in:
Pierre-Yves Nicolas
2026-05-05 13:42:10 +02:00
parent 2b63273168
commit d03d411706
8 changed files with 126 additions and 120 deletions

View File

@@ -124,6 +124,7 @@ class MainActivity : ComponentActivity() {
val importState by cameraViewModel.importState.collectAsStateWithLifecycle() val importState by cameraViewModel.importState.collectAsStateWithLifecycle()
val document by viewModel.documentUiModel.collectAsStateWithLifecycle() val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
val documentUiState by viewModel.documentUiState.collectAsStateWithLifecycle() val documentUiState by viewModel.documentUiState.collectAsStateWithLifecycle()
val cropInitialState by viewModel.cropInitState.collectAsStateWithLifecycle()
val exportUiState by exportViewModel.uiState.collectAsStateWithLifecycle() val exportUiState by exportViewModel.uiState.collectAsStateWithLifecycle()
val cameraPermission = rememberCameraPermissionState() val cameraPermission = rememberCameraPermissionState()
CollectCameraEvents(cameraViewModel, viewModel) CollectCameraEvents(cameraViewModel, viewModel)
@@ -179,10 +180,10 @@ class MainActivity : ComponentActivity() {
) )
} }
is Screen.Main.EditImage -> { is Screen.Main.EditImage -> {
val pageIndex = (currentScreen as Screen.Main.EditImage).pageIndex
EditPageScreen( EditPageScreen(
pageId = documentUiState.document.pages[pageIndex].key.pageId, pageId = documentUiState.currentPage?.key?.pageId ?: "",
imageRepository = imageRepository, onLoad = { id -> viewModel.loadCropInitialState(id)},
initState = cropInitialState,
navigation = navigation, navigation = navigation,
onUpdatePageQuad = { id, quad, onComplete -> }, onUpdatePageQuad = { id, quad, onComplete -> },
) )
@@ -467,7 +468,7 @@ class MainActivity : ComponentActivity() {
private fun navigation(viewModel: MainViewModel, launchMode: LaunchMode): Navigation = Navigation( private fun navigation(viewModel: MainViewModel, launchMode: LaunchMode): Navigation = Navigation(
toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) }, toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
toEditImageScreen = { pageIndex -> viewModel.navigateTo(Screen.Main.EditImage(pageIndex)) }, toEditImageScreen = { viewModel.navigateTo(Screen.Main.EditImage) },
toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) }, toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
toExportScreen = { viewModel.navigateTo(Screen.Main.Export) }, toExportScreen = { viewModel.navigateTo(Screen.Main.Export) },
toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) }, toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) },

View File

@@ -14,12 +14,16 @@
*/ */
package org.fairscan.app package org.fairscan.app
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -34,14 +38,17 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.fairscan.app.data.ImageRepository import org.fairscan.app.data.ImageRepository
import org.fairscan.app.domain.CapturedPage import org.fairscan.app.domain.CapturedPage
import org.fairscan.app.domain.Rotation
import org.fairscan.app.domain.ScanPage import org.fairscan.app.domain.ScanPage
import org.fairscan.app.ui.NavigationState import org.fairscan.app.ui.NavigationState
import org.fairscan.app.ui.Screen import org.fairscan.app.ui.Screen
import org.fairscan.app.ui.screens.document.CurrentPageUiState import org.fairscan.app.ui.screens.document.CurrentPageUiState
import org.fairscan.app.ui.screens.document.DocumentUiState import org.fairscan.app.ui.screens.document.DocumentUiState
import org.fairscan.app.ui.screens.edit.CropInitState
import org.fairscan.app.ui.state.DocumentUiModel import org.fairscan.app.ui.state.DocumentUiModel
import org.fairscan.app.ui.state.PageThumbnail import org.fairscan.app.ui.state.PageThumbnail
import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.ColorMode
import org.fairscan.imageprocessing.ImageSize
import kotlin.math.min import kotlin.math.min
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -95,7 +102,8 @@ class MainViewModel(val imageRepository: ImageRepository): ViewModel() {
page?.let { page?.let {
val isLoading = (it.id == loadingId) val isLoading = (it.id == loadingId)
val bitmap = imageRepository.jpegBytes(it.key())?.toBitmap() val bitmap = imageRepository.jpegBytes(it.key())?.toBitmap()
CurrentPageUiState(it.key(), bitmap, it.colorMode, isLoading) val canBeCropped = page.metadata != null
CurrentPageUiState(it.key(), bitmap, it.colorMode, canBeCropped, isLoading)
} }
} }
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)
@@ -212,4 +220,47 @@ class MainViewModel(val imageRepository: ImageRepository): ViewModel() {
_pages.value = pages _pages.value = pages
} }
} }
private val _cropInitState = MutableStateFlow<CropInitState>(CropInitState.Loading)
val cropInitState: StateFlow<CropInitState> = _cropInitState
private var cropInitialStateJob: Job? = null
fun loadCropInitialState(pageId: String) {
cropInitialStateJob?.cancel()
cropInitialStateJob = viewModelScope.launch {
_cropInitState.value = CropInitState.Loading
val page = _pages.value.find { it.id == pageId }
?: return@launch
val metadata = page.metadata
val baseRotation = metadata?.baseRotation ?: Rotation.R0
val rotation = baseRotation.add(page.manualRotation)
val bitmap = withContext(Dispatchers.IO) {
val source = imageRepository.source(page.id)
val bytes = source?.bytes ?: return@withContext null
val original = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
if (original != null && rotation != Rotation.R0) {
val matrix = Matrix().apply { postRotate(rotation.degrees.toFloat()) }
Bitmap.createBitmap(
original, 0, 0, original.width, original.height, matrix, true
)
} else {
original
}
}
val quad = metadata?.normalizedQuad?.rotate90(
rotation.degrees / 90,
ImageSize(1, 1)
)
_cropInitState.value = if (bitmap == null || quad == null)
CropInitState.Error
else
CropInitState.Ready(page.id, bitmap, quad)
}
}
} }

View File

@@ -17,7 +17,7 @@ package org.fairscan.app.ui
sealed class Screen { sealed class Screen {
sealed class Main : Screen() { sealed class Main : Screen() {
object Camera : Main() object Camera : Main()
data class EditImage(val pageIndex: Int) : Main() object EditImage : Main()
data class Document(val initialPage: Int = 0) : Main() data class Document(val initialPage: Int = 0) : Main()
object Export : Main() object Export : Main()
} }
@@ -30,7 +30,7 @@ sealed class Screen {
data class Navigation( data class Navigation(
val toCameraScreen: () -> Unit, val toCameraScreen: () -> Unit,
val toEditImageScreen: (Int) -> Unit, val toEditImageScreen: () -> Unit,
val toDocumentScreen: () -> Unit, val toDocumentScreen: () -> Unit,
val toExportScreen: () -> Unit, val toExportScreen: () -> Unit,
val toAboutScreen: () -> Unit, val toAboutScreen: () -> Unit,
@@ -64,7 +64,7 @@ data class NavigationState private constructor(val stack: List<Screen>, val root
root -> this // Back handled by system root -> this // Back handled by system
is Screen.Main.Camera -> this // Back handled by system is Screen.Main.Camera -> this // Back handled by system
is Screen.Main.Document -> copy(stack = listOf(Screen.Main.Camera)) is Screen.Main.Document -> copy(stack = listOf(Screen.Main.Camera))
is Screen.Main.EditImage -> copy(stack = listOf(Screen.Main.Document(initialPage = (current as Screen.Main.EditImage).pageIndex))) is Screen.Main.EditImage -> copy(stack = listOf(Screen.Main.Document()))
is Screen.Main.Export -> copy(stack = listOf(Screen.Main.Camera)) is Screen.Main.Export -> copy(stack = listOf(Screen.Main.Camera))
is Screen.Overlay -> copy(stack = stack.dropLast(1)) is Screen.Overlay -> copy(stack = stack.dropLast(1))
} }

View File

@@ -34,6 +34,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AutoFixHigh import androidx.compose.material.icons.filled.AutoFixHigh
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Contrast import androidx.compose.material.icons.filled.Contrast
import androidx.compose.material.icons.filled.Crop
import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.RotateLeft import androidx.compose.material.icons.filled.RotateLeft
@@ -132,6 +133,7 @@ fun DocumentScreen(
{ showDeletePageDialog.value = true }, { showDeletePageDialog.value = true },
onRotateImage, onRotateImage,
onToggleColorMode, onToggleColorMode,
navigation,
modifier modifier
) )
if (showDeletePageDialog.value) { if (showDeletePageDialog.value) {
@@ -150,6 +152,7 @@ private fun DocumentPreview(
onDeleteImage: () -> Unit, onDeleteImage: () -> Unit,
onRotateImage: (Boolean) -> Unit, onRotateImage: (Boolean) -> Unit,
onToggleColorMode: () -> Unit, onToggleColorMode: () -> Unit,
navigation: Navigation,
modifier: Modifier, modifier: Modifier,
) { ) {
val currentPageIndex = uiState.currentPageIndex val currentPageIndex = uiState.currentPageIndex
@@ -194,15 +197,12 @@ private fun DocumentPreview(
CircularProgressIndicator() CircularProgressIndicator()
} }
} }
uiState.currentPage?.colorMode?.let { EditButtons(
ColorModeButton( uiState,
currentColorMode = it, onToggleColorMode,
onToggle = { onToggleColorMode() }, navigation,
modifier = Modifier modifier = Modifier.align(Alignment.BottomStart)
.align(Alignment.BottomStart)
.padding(8.dp)
) )
}
RotationButtons(onRotateImage, Modifier.align(Alignment.BottomCenter)) RotationButtons(onRotateImage, Modifier.align(Alignment.BottomCenter))
SecondaryActionButton( SecondaryActionButton(
Icons.Outlined.Delete, Icons.Outlined.Delete,
@@ -253,6 +253,31 @@ fun RotationButtons(
} }
} }
@Composable
fun EditButtons(
uiState: DocumentUiState,
onToggleColorMode: () -> Unit,
navigation: Navigation,
modifier: Modifier
) {
Row(modifier = modifier.padding(8.dp)) {
uiState.currentPage?.colorMode?.let {
ColorModeButton(
currentColorMode = it,
onToggle = { onToggleColorMode() },
)
}
Spacer(Modifier.width(8.dp))
if (uiState.currentPage?.canBeCropped ?: false) {
SecondaryActionButton(
icon = Icons.Default.Crop,
contentDescription = "Crop", // TODO externalize string
onClick = navigation.toEditImageScreen,
)
}
}
}
@Composable @Composable
fun ColorModeButton( fun ColorModeButton(
currentColorMode: ColorMode, currentColorMode: ColorMode,
@@ -348,7 +373,7 @@ fun DocumentScreenPreview() {
) )
val key = PageViewKey("123", Rotation.R0, null) val key = PageViewKey("123", Rotation.R0, null)
DocumentScreen( DocumentScreen(
uiState = DocumentUiState(1, CurrentPageUiState(key,image, COLOR), document), uiState = DocumentUiState(1, CurrentPageUiState(key,image, COLOR, true), document),
navigation = dummyNavigation(), navigation = dummyNavigation(),
onExportClick = {}, onExportClick = {},
onDeleteImage = { }, onDeleteImage = { },

View File

@@ -29,5 +29,6 @@ data class CurrentPageUiState(
val key: PageViewKey, val key: PageViewKey,
val bitmap: Bitmap?, val bitmap: Bitmap?,
val colorMode: ColorMode?, val colorMode: ColorMode?,
val canBeCropped: Boolean = false,
val isLoading: Boolean = false, val isLoading: Boolean = false,
) )

View File

@@ -16,8 +16,8 @@ package org.fairscan.app.ui.screens.edit
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Matrix
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitEachGesture
@@ -39,7 +39,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
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.geometry.Offset import androidx.compose.ui.geometry.Offset
@@ -50,100 +49,43 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.fairscan.app.R import org.fairscan.app.R
import org.fairscan.app.data.ImageRepository
import org.fairscan.app.data.ImageTransformations
import org.fairscan.app.domain.Rotation
import org.fairscan.app.ui.Navigation import org.fairscan.app.ui.Navigation
import org.fairscan.app.ui.components.AppOverflowMenu import org.fairscan.app.ui.components.AppOverflowMenu
import org.fairscan.app.ui.components.BackButton import org.fairscan.app.ui.components.BackButton
import org.fairscan.app.ui.components.ConfirmationDialog
import org.fairscan.app.ui.components.MainActionButton import org.fairscan.app.ui.components.MainActionButton
import org.fairscan.app.ui.components.isLandscape import org.fairscan.app.ui.components.isLandscape
import org.fairscan.app.ui.dummyNavigation import org.fairscan.app.ui.dummyNavigation
import org.fairscan.app.ui.theme.FairScanTheme import org.fairscan.app.ui.theme.FairScanTheme
import org.fairscan.imageprocessing.ImageSize
import org.fairscan.imageprocessing.Point import org.fairscan.imageprocessing.Point
import org.fairscan.imageprocessing.Quad import org.fairscan.imageprocessing.Quad
import java.io.File
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun EditPageScreen( fun EditPageScreen(
pageId: String, pageId: String,
imageRepository: ImageRepository, onLoad: (String) -> Unit,
initState: CropInitState,
navigation: Navigation, navigation: Navigation,
onUpdatePageQuad: (String, Quad, onComplete: () -> Unit) -> Unit, onUpdatePageQuad: (String, Quad, onComplete: () -> Unit) -> Unit,
onReportProblem: () -> Unit = {},
) { ) {
val showDiscardChangesDialog = rememberSaveable { mutableStateOf(false) }
val state = remember { EditPageScreenState() } val state = remember { EditPageScreenState() }
val quadHandler = remember { QuadEditingHandler() } val quadHandler = remember { QuadEditingHandler() }
val handleBack = { if (initState is CropInitState.Ready && initState.pageId == pageId) {
if (state.hasUnsavedChanges()) { state.bitmap = initState.bitmap
showDiscardChangesDialog.value = true state.setInitialQuad(initState.quad)
} else {
navigation.back()
}
} }
BackHandler { handleBack() } BackHandler { navigation.back() }
val isPreview = LocalInspectionMode.current
if (isPreview) {
val dummyImage = LocalContext.current.assets.open("gallica.bnf.fr-bpt6k5530456s-1.jpg").use { input ->
BitmapFactory.decodeStream(input)
}
state.bitmap = dummyImage
state.setInitialQuad(Quad(Point(.1, .1), Point(.9, .1), Point(.9, .9), Point(.1, .9)))
}
val totalRotation = remember { mutableStateOf(Rotation.R0) }
LaunchedEffect(pageId) { LaunchedEffect(pageId) {
val metadata = imageRepository.getPageMetadata(pageId) onLoad(pageId)
val baseRotation = metadata?.baseRotation ?: Rotation.R0
val manualRotation = imageRepository.getManualRotation(pageId)
val rotation = baseRotation.add(manualRotation)
totalRotation.value = rotation
val bitmap = withContext(Dispatchers.IO) {
val sourceJpegBytes = imageRepository.sourceJpegBytes(pageId)
if (sourceJpegBytes != null) {
val original = BitmapFactory.decodeByteArray(sourceJpegBytes, 0, sourceJpegBytes.size)
if (original != null && rotation != Rotation.R0) {
// Adjust the displayed bitmap's rotation to what is in the metadata
val matrix = Matrix().apply { postRotate(rotation.degrees.toFloat()) }
val rotated = android.graphics.Bitmap.createBitmap(
original, 0, 0, original.width, original.height, matrix, true
)
if (rotated !== original) {
original.recycle()
}
rotated
} else {
original
}
} else null
}
state.bitmap = bitmap // assigned on the main thread after withContext returns
if (metadata?.normalizedQuad != null) {
// Rotate the quad to match the rotated bitmap display
val rotatedQuad = metadata.normalizedQuad.rotate90(
rotation.degrees / 90,
ImageSize(1, 1)
)
state.setInitialQuad(rotatedQuad)
}
} }
val isLandscape = isLandscape(LocalConfiguration.current) val isLandscape = isLandscape(LocalConfiguration.current)
@@ -178,7 +120,7 @@ fun EditPageScreen(
} }
BackButton( BackButton(
onClick = handleBack, onClick = navigation.back,
modifier = Modifier modifier = Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.windowInsetsPadding(WindowInsets.safeDrawing) .windowInsetsPadding(WindowInsets.safeDrawing)
@@ -198,6 +140,7 @@ fun EditPageScreen(
.padding(16.dp) .padding(16.dp)
.windowInsetsPadding(WindowInsets.safeDrawing), .windowInsetsPadding(WindowInsets.safeDrawing),
onConfirm = { onConfirm = {
/*
val quad = state.editableQuad val quad = state.editableQuad
if (quad != null) { if (quad != null) {
// Reverse the total rotation to get back to original source image coordinates // Reverse the total rotation to get back to original source image coordinates
@@ -210,21 +153,11 @@ fun EditPageScreen(
} else { } else {
navigation.back() navigation.back()
} }
*/
} }
) )
} }
} }
if (showDiscardChangesDialog.value) {
ConfirmationDialog(
title = stringResource(R.string.discard_changes),
message = stringResource(R.string.discard_changes_warning),
showDialog = showDiscardChangesDialog
) {
state.revertToInitial()
navigation.back()
}
}
} }
@Composable @Composable
@@ -246,7 +179,7 @@ private fun ActionButtons(
private fun DragQuadOverlay( private fun DragQuadOverlay(
state: EditPageScreenState, state: EditPageScreenState,
quadHandler: QuadEditingHandler, quadHandler: QuadEditingHandler,
bmp: android.graphics.Bitmap bmp: Bitmap
) { ) {
if (state.editableQuad == null || state.containerSize == null) return if (state.editableQuad == null || state.containerSize == null) return
@@ -409,22 +342,16 @@ private fun DragMagnifyingGlass(state: EditPageScreenState) {
@Preview(name = "RTL", locale = "ar", showSystemUi = true) @Preview(name = "RTL", locale = "ar", showSystemUi = true)
fun EditPageScreenPreview() { fun EditPageScreenPreview() {
FairScanTheme { FairScanTheme {
val dummyImage = LocalContext.current.assets.open("gallica.bnf.fr-bpt6k5530456s-1.jpg").use { input ->
// Minimal no-op ImageTransformations implementation used only for preview. BitmapFactory.decodeStream(input)
val dummyTransformations = object : ImageTransformations {
override fun rotate(inputFile: File, outputFile: File, rotationDegrees: Int, jpegQuality: Int) = Unit
override fun resize(inputFile: File, outputFile: File, maxSize: Int) = Unit
} }
val quad = Quad(Point(.1, .1), Point(.9, .1), Point(.9, .9), Point(.1, .9))
// Use a temporary directory for the repository in preview.
val tempDir = File(System.getProperty("java.io.tmpdir") ?: "/tmp")
val dummyImageRepo = ImageRepository(tempDir, dummyTransformations, 128)
EditPageScreen( EditPageScreen(
pageId = "preview-page-id", pageId = "123",
imageRepository = dummyImageRepo, onLoad = {},
initState = CropInitState.Ready("123",dummyImage, quad),
navigation = dummyNavigation(), navigation = dummyNavigation(),
onUpdatePageQuad = { _, _, onComplete -> onComplete() } onUpdatePageQuad = { _,_,_ -> },
) )
} }
} }

View File

@@ -14,6 +14,7 @@
*/ */
package org.fairscan.app.ui.screens.edit package org.fairscan.app.ui.screens.edit
import android.graphics.Bitmap
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -23,6 +24,16 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import org.fairscan.imageprocessing.Quad import org.fairscan.imageprocessing.Quad
sealed interface CropInitState {
object Loading : CropInitState
object Error : CropInitState
data class Ready(
val pageId: String,
val bitmap: Bitmap,
val quad: Quad
) : CropInitState
}
class EditPageScreenState { class EditPageScreenState {
companion object { companion object {
val LIFT_WIGGLE_MAX_DISTANCE = 8.dp val LIFT_WIGGLE_MAX_DISTANCE = 8.dp
@@ -116,12 +127,4 @@ class EditPageScreenState {
initialQuad = quad initialQuad = quad
editableQuad = quad editableQuad = quad
} }
fun hasUnsavedChanges(): Boolean {
return editableQuad != initialQuad
}
fun revertToInitial() {
editableQuad = initialQuad
}
} }

View File

@@ -22,8 +22,6 @@
<string name="delete_page">Delete page</string> <string name="delete_page">Delete page</string>
<string name="delete_page_warning">Do you want to delete this page?</string> <string name="delete_page_warning">Do you want to delete this page?</string>
<string name="developer">Developer</string> <string name="developer">Developer</string>
<string name="discard_changes">Discard changes</string>
<string name="discard_changes_warning">You have unsaved changes. Do you want to discard them?</string>
<string name="discard_scan">Discard scan</string> <string name="discard_scan">Discard scan</string>
<string name="download_dirname">Downloads</string> <string name="download_dirname">Downloads</string>
<string name="error">Error: %1$s</string> <string name="error">Error: %1$s</string>