Browse Source

ImageViewer, fix Android rotation (#3007)

pull/3009/head
dima.avdeev 2 years ago committed by GitHub
parent
commit
523368228b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      examples/imageviewer/shared/build.gradle.kts
  2. 16
      examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/model/Page.android.kt
  3. 3
      examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/CameraView.android.kt
  4. 38
      examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt
  5. 15
      examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.common.kt
  6. 10
      examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt
  7. 24
      examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt
  8. 44
      examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
  9. 10
      examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/NavigationStack.kt
  10. 9
      examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/model/Page.desktop.kt
  11. 9
      examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/model/Page.ios.kt

1
examples/imageviewer/shared/build.gradle.kts

@ -6,6 +6,7 @@ plugins {
id("com.android.library")
id("org.jetbrains.compose")
kotlin("plugin.serialization")
id("kotlin-parcelize")
}
version = "1.0-SNAPSHOT"

16
examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/model/Page.android.kt

@ -0,0 +1,16 @@
package example.imageviewer.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
actual class MemoryPage actual constructor(actual val pictureIndex: Int) : Page, Parcelable
@Parcelize
actual class CameraPage : Page, Parcelable
@Parcelize
actual class FullScreenPage actual constructor(actual val pictureIndex: Int) : Page, Parcelable
@Parcelize
actual class GalleryPage : Page, Parcelable

3
examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/CameraView.android.kt

@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -82,7 +83,7 @@ private fun CameraWithGrantedPermission(
val preview = Preview.Builder().build()
val previewView = remember { PreviewView(context) }
val imageCapture: ImageCapture = remember { ImageCapture.Builder().build() }
var isFrontCamera by remember { mutableStateOf(false) }
var isFrontCamera by rememberSaveable { mutableStateOf(false) }
val cameraSelector = remember(isFrontCamera) {
val lensFacing =
if (isFrontCamera) {

38
examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt

@ -3,6 +3,8 @@ package example.imageviewer
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.snapshots.SnapshotStateList
import example.imageviewer.model.*
import example.imageviewer.view.*
@ -33,8 +35,17 @@ fun ImageViewerCommon(
fun ImageViewerWithProvidedDependencies(
pictures: SnapshotStateList<PictureData>
) {
val selectedPictureIndex = remember { mutableStateOf(0) }
val navigationStack = remember { NavigationStack<Page>(GalleryPage()) }
// rememberSaveable is required to properly handle Android configuration changes (such as device rotation)
val selectedPictureIndex = rememberSaveable { mutableStateOf(0) }
val navigationStack = rememberSaveable(
saver = listSaver<NavigationStack<Page>, Page>(
restore = { NavigationStack(*it.toTypedArray()) },
save = { it.stack },
)
) {
NavigationStack(GalleryPage())
}
val externalEvents = LocalInternalEvents.current
LaunchedEffect(Unit) {
externalEvents.collect {
@ -62,8 +73,8 @@ fun ImageViewerWithProvidedDependencies(
GalleryScreen(
pictures = pictures,
selectedPictureIndex = selectedPictureIndex,
onClickPreviewPicture = { previewPictureId ->
navigationStack.push(MemoryPage(mutableStateOf(previewPictureId)))
onClickPreviewPicture = { previewPictureIndex ->
navigationStack.push(MemoryPage(previewPictureIndex))
}
) {
navigationStack.push(CameraPage())
@ -72,7 +83,7 @@ fun ImageViewerWithProvidedDependencies(
is FullScreenPage -> {
FullscreenImageScreen(
picture = page.picture,
picture = pictures[page.pictureIndex],
back = {
navigationStack.back()
}
@ -83,14 +94,19 @@ fun ImageViewerWithProvidedDependencies(
MemoryScreen(
pictures = pictures,
memoryPage = page,
onSelectRelatedMemory = { picture ->
navigationStack.push(MemoryPage(mutableStateOf(picture)))
onSelectRelatedMemory = { pictureIndex ->
navigationStack.push(MemoryPage(pictureIndex))
},
onBack = {
navigationStack.back()
onBack = { resetNavigation ->
if (resetNavigation) {
selectedPictureIndex.value = 0
navigationStack.reset()
} else {
navigationStack.back()
}
},
onHeaderClick = { galleryId ->
navigationStack.push(FullScreenPage(galleryId))
onHeaderClick = { pictureIndex ->
navigationStack.push(FullScreenPage(pictureIndex))
},
)
}

15
examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.common.kt

@ -0,0 +1,15 @@
package example.imageviewer.model
interface Page
expect class MemoryPage(pictureIndex: Int) : Page {
val pictureIndex: Int
}
expect class CameraPage() : Page
expect class FullScreenPage(pictureIndex: Int) : Page {
val pictureIndex: Int
}
expect class GalleryPage() : Page

10
examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt

@ -1,10 +0,0 @@
package example.imageviewer.model
import androidx.compose.runtime.MutableState
sealed interface Page
class MemoryPage(val pictureState: MutableState<PictureData>) : Page
class CameraPage : Page
class FullScreenPage(val picture: PictureData) : Page
class GalleryPage : Page

24
examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt

@ -50,7 +50,7 @@ enum class GalleryStyle {
fun GalleryScreen(
pictures: SnapshotStateList<PictureData>,
selectedPictureIndex: MutableState<Int>,
onClickPreviewPicture: (PictureData) -> Unit,
onClickPreviewPicture: (index: Int) -> Unit,
onMakeNewMemory: () -> Unit
) {
val imageProvider = LocalImageProvider.current
@ -113,17 +113,17 @@ fun GalleryScreen(
Box(
Modifier.fillMaxSize()
.clickable {
onClickPreviewPicture(pictures[pagerState.currentPage])
onClickPreviewPicture(pagerState.currentPage)
}
) {
HorizontalPager(pictures.size, state = pagerState) { idx ->
val picture = pictures[idx]
HorizontalPager(pictures.size, state = pagerState) { index ->
val picture = pictures[index]
var image: ImageBitmap? by remember(picture) { mutableStateOf(null) }
LaunchedEffect(picture) {
image = imageProvider.getImage(picture)
}
if (image != null) {
Box(Modifier.fillMaxSize().animatePageChanges(pagerState, idx)) {
Box(Modifier.fillMaxSize().animatePageChanges(pagerState, index)) {
Image(
bitmap = image!!,
contentDescription = null,
@ -176,7 +176,7 @@ fun GalleryScreen(
private fun SquaresGalleryView(
images: List<PictureData>,
pagerState: PagerState,
onSelect: (Int) -> Unit,
onSelect: (index: Int) -> Unit,
) {
LazyVerticalGrid(
modifier = Modifier.padding(top = 4.dp),
@ -184,11 +184,11 @@ private fun SquaresGalleryView(
verticalArrangement = Arrangement.spacedBy(1.dp),
horizontalArrangement = Arrangement.spacedBy(1.dp)
) {
itemsIndexed(images) { idx, picture ->
itemsIndexed(images) { index, picture ->
SquareThumbnail(
picture = picture,
onClick = { onSelect(idx) },
isHighlighted = pagerState.targetPage == idx
onClick = { onSelect(index) },
isHighlighted = pagerState.targetPage == index
)
}
}
@ -244,8 +244,8 @@ fun SquareThumbnail(
@Composable
private fun ListGalleryView(
pictures: List<PictureData>,
onSelect: (Int) -> Unit,
onFullScreen: (PictureData) -> Unit,
onSelect: (index: Int) -> Unit,
onFullScreen: (index: Int) -> Unit,
) {
val notification = LocalNotification.current
ScrollableColumn(
@ -259,7 +259,7 @@ private fun ListGalleryView(
onSelect(p.index)
},
onClickFullScreen = {
onFullScreen(p.value)
onFullScreen(p.index)
},
onClickInfo = {
notification.notifyImageData(p.value)

44
examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt

@ -38,20 +38,19 @@ import example.imageviewer.model.*
import example.imageviewer.shareIcon
import example.imageviewer.style.ImageviewerColors
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource
@Composable
fun MemoryScreen(
pictures: SnapshotStateList<PictureData>,
memoryPage: MemoryPage,
onSelectRelatedMemory: (PictureData) -> Unit,
onBack: () -> Unit,
onHeaderClick: (PictureData) -> Unit,
onSelectRelatedMemory: (index: Int) -> Unit,
onBack: (resetNavigation: Boolean) -> Unit,
onHeaderClick: (index: Int) -> Unit,
) {
val imageProvider = LocalImageProvider.current
val sharePicture = LocalSharePicture.current
var edit: Boolean by remember { mutableStateOf(false) }
val picture = memoryPage.pictureState.value
val picture = pictures.getOrNull(memoryPage.pictureIndex) ?: return
var headerImage: ImageBitmap? by remember(picture) { mutableStateOf(null) }
val platformContext = getPlatformContext()
LaunchedEffect(picture) {
@ -78,7 +77,7 @@ fun MemoryScreen(
MemoryHeader(
it,
picture = picture,
onClick = { onHeaderClick(picture) }
onClick = { onHeaderClick(memoryPage.pictureIndex) }
)
}
}
@ -106,7 +105,7 @@ fun MemoryScreen(
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
IconWithText(Icons.Default.Delete, "Delete") {
imageProvider.delete(picture)
onBack()
onBack(true)
}
IconWithText(Icons.Default.Edit, "Edit") {
edit = true
@ -123,14 +122,15 @@ fun MemoryScreen(
}
TopLayout(
alignLeftContent = {
BackButton(onBack)
BackButton {
onBack(false)
}
},
alignRightContent = {},
)
if (edit) {
EditMemoryDialog(picture.name, picture.description) { name, description ->
val edited = imageProvider.edit(picture, name, description)
memoryPage.pictureState.value = edited
imageProvider.edit(picture, name, description)
edit = false
}
}
@ -267,7 +267,7 @@ fun Headliner(s: String) {
@Composable
fun RelatedMemoriesVisualizer(
pictures: List<PictureData>,
onSelectRelatedMemory: (PictureData) -> Unit
onSelectRelatedMemory: (index: Int) -> Unit
) {
Box(
modifier = Modifier.padding(10.dp, 0.dp).clip(RoundedCornerShape(10.dp)).fillMaxWidth()
@ -276,22 +276,14 @@ fun RelatedMemoriesVisualizer(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(pictures) { idx, item ->
RelatedMemory(item, onSelectRelatedMemory)
itemsIndexed(pictures) { index, item ->
Box(Modifier.size(130.dp).clip(RoundedCornerShape(8.dp))) {
SquareThumbnail(
picture = item,
isHighlighted = false,
onClick = { onSelectRelatedMemory(index) })
}
}
}
}
}
@Composable
fun RelatedMemory(
galleryEntry: PictureData,
onSelectRelatedMemory: (PictureData) -> Unit
) {
Box(Modifier.size(130.dp).clip(RoundedCornerShape(8.dp))) {
SquareThumbnail(
picture = galleryEntry,
isHighlighted = false,
onClick = { onSelectRelatedMemory(galleryEntry) })
}
}

10
examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/NavigationStack.kt

@ -2,18 +2,22 @@ package example.imageviewer.view
import androidx.compose.runtime.mutableStateListOf
class NavigationStack<T>(initial: T) {
private val stack = mutableStateListOf(initial)
class NavigationStack<T>(vararg initial: T) {
val stack = mutableStateListOf(*initial)
fun push(t: T) {
stack.add(t)
}
fun back() {
if(stack.size > 1) {
if (stack.size > 1) {
// Always keep one element on the view stack
stack.removeLast()
}
}
fun reset() {
stack.removeRange(1, stack.size)
}
fun lastWithIndex() = stack.withIndex().last()
}

9
examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/model/Page.desktop.kt

@ -0,0 +1,9 @@
package example.imageviewer.model
actual class MemoryPage actual constructor(actual val pictureIndex: Int) : Page
actual class CameraPage : Page
actual class FullScreenPage actual constructor(actual val pictureIndex: Int) : Page
actual class GalleryPage : Page

9
examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/model/Page.ios.kt

@ -0,0 +1,9 @@
package example.imageviewer.model
actual class MemoryPage actual constructor(actual val pictureIndex: Int) : Page
actual class CameraPage : Page
actual class FullScreenPage actual constructor(actual val pictureIndex: Int) : Page
actual class GalleryPage : Page
Loading…
Cancel
Save