From 2cea802cecfdf418c4c93b9689abd965aa376e90 Mon Sep 17 00:00:00 2001
From: "dima.avdeev" <99798741+dima-avdeev-jb@users.noreply.github.com>
Date: Mon, 3 Apr 2023 16:21:20 +0300
Subject: [PATCH] ImageViewer delete, edit and share memories (#2957)
---
.../imageviewer/shared/build.gradle.kts | 1 +
.../src/androidMain/AndroidManifest.xml | 11 +-
.../imageviewer/ImageViewerFileProvider.kt | 6 +
.../example/imageviewer/platform.android.kt | 8 ++
.../storage/AndroidImageStorage.kt | 69 ++++++++++--
.../view/EditMemoryDialog.android.kt | 77 +++++++++++++
.../imageviewer/view/ImageViewer.android.kt | 27 ++++-
.../src/androidMain/res/xml/file_paths.xml | 3 +
.../example/imageviewer/Dependencies.kt | 59 ++++++++--
.../example/imageviewer/ImageViewer.common.kt | 10 +-
.../kotlin/example/imageviewer/model/Page.kt | 4 +-
.../example/imageviewer/model/PictureData.kt | 6 +-
.../example/imageviewer/platform.common.kt | 6 +
.../example/imageviewer/view/CameraScreen.kt | 6 +-
.../view/EditMemoryDialog.common.kt | 11 ++
.../example/imageviewer/view/MemoryScreen.kt | 106 ++++++++++++------
.../imageviewer/DesktopImageStorage.kt | 24 ++--
.../example/imageviewer/platform.desktop.kt | 8 ++
.../view/EditMemoryDialog.desktop.kt | 87 ++++++++++++++
.../imageviewer/view/ImageViewer.desktop.kt | 9 +-
.../example/imageviewer/ImageViewer.ios.kt | 37 +++++-
.../example/imageviewer/IosShareIcon.kt | 41 +++++++
.../kotlin/example/imageviewer/main.ios.kt | 5 +-
.../example/imageviewer/platform.ios.kt | 5 +
.../imageviewer/storage/FileExtensions.kt | 4 +
.../storage/IosImageStorage.ios.kt | 48 ++++++--
.../imageviewer/view/EditMemoryDialog.ios.kt | 77 +++++++++++++
.../view/LocationVisualizer.ios.kt | 28 +++--
28 files changed, 683 insertions(+), 100 deletions(-)
create mode 100644 experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/ImageViewerFileProvider.kt
create mode 100644 experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/EditMemoryDialog.android.kt
create mode 100644 experimental/examples/imageviewer/shared/src/androidMain/res/xml/file_paths.xml
create mode 100644 experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/EditMemoryDialog.common.kt
create mode 100644 experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/EditMemoryDialog.desktop.kt
create mode 100644 experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/IosShareIcon.kt
create mode 100644 experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/EditMemoryDialog.ios.kt
diff --git a/experimental/examples/imageviewer/shared/build.gradle.kts b/experimental/examples/imageviewer/shared/build.gradle.kts
index 6826d46797..eaccfc7d17 100755
--- a/experimental/examples/imageviewer/shared/build.gradle.kts
+++ b/experimental/examples/imageviewer/shared/build.gradle.kts
@@ -35,6 +35,7 @@ kotlin {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
+ //implementation(compose.materialIconsExtended) // TODO not working on iOS
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
diff --git a/experimental/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml b/experimental/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml
index a077657347..c77ec92f4d 100755
--- a/experimental/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml
+++ b/experimental/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml
@@ -1,7 +1,16 @@
+ package="example.imageviewer.shared">
+
+
+
+
+
\ No newline at end of file
diff --git a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/ImageViewerFileProvider.kt b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/ImageViewerFileProvider.kt
new file mode 100644
index 0000000000..208dd91882
--- /dev/null
+++ b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/ImageViewerFileProvider.kt
@@ -0,0 +1,6 @@
+package example.imageviewer
+
+import androidx.core.content.FileProvider
+import example.imageviewer.shared.R
+
+class ImageViewerFileProvider : FileProvider(R.xml.file_paths)
diff --git a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt
index 1474fff74e..7f11252044 100644
--- a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt
+++ b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt
@@ -4,8 +4,12 @@ import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
+import example.imageviewer.model.PictureData
import kotlinx.coroutines.Dispatchers
import java.util.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Share
+import androidx.compose.ui.graphics.vector.ImageVector
actual fun Modifier.notchPadding(): Modifier = displayCutoutPadding().statusBarsPadding()
@@ -18,3 +22,7 @@ actual typealias PlatformStorableImage = AndroidStorableImage
actual fun createUUID(): String = UUID.randomUUID().toString()
actual val ioDispatcher = Dispatchers.IO
+
+actual val isShareFeatureSupported: Boolean = true
+
+actual val shareIcon: ImageVector = Icons.Filled.Share
diff --git a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/storage/AndroidImageStorage.kt b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/storage/AndroidImageStorage.kt
index 2943e88f39..5210b17a85 100644
--- a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/storage/AndroidImageStorage.kt
+++ b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/storage/AndroidImageStorage.kt
@@ -2,23 +2,29 @@ package example.imageviewer.storage
import android.content.Context
import android.graphics.Bitmap
+import android.net.Uri
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.asImageBitmap
+import androidx.core.content.FileProvider
import androidx.core.graphics.scale
import example.imageviewer.ImageStorage
import example.imageviewer.PlatformStorableImage
import example.imageviewer.model.PictureData
-import example.imageviewer.toAndroidBitmap
import example.imageviewer.toImageBitmap
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+import org.jetbrains.compose.resources.resource
import java.io.File
+import java.util.UUID
+
private const val maxStorableImageSizePx = 2000
private const val storableThumbnailSizePx = 200
@@ -29,10 +35,15 @@ class AndroidImageStorage(
private val ioScope: CoroutineScope,
context: Context
) : ImageStorage {
- private val savePictureDir = File(context.filesDir, "takenPhotos")
+ private val savePictureDir = File(context.filesDir, "taken_photos")
+ private val sharedImagesDir = File(context.filesDir, "share_images")
private val PictureData.Camera.jpgFile get() = File(savePictureDir, "$id.jpg")
- private val PictureData.Camera.thumbnailJpgFile get() = File(savePictureDir, "$id-thumbnail.jpg")
+ private val PictureData.Camera.thumbnailJpgFile
+ get() = File(
+ savePictureDir,
+ "$id-thumbnail.jpg"
+ )
private val PictureData.Camera.jsonFile get() = File(savePictureDir, "$id.json")
init {
@@ -53,30 +64,64 @@ class AndroidImageStorage(
}
}
- override fun saveImage(pictureData: PictureData.Camera, image: PlatformStorableImage) {
+ override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) {
if (image.imageBitmap.width == 0 || image.imageBitmap.height == 0) {
return
}
ioScope.launch {
with(image.imageBitmap) {
- pictureData.jpgFile.writeJpeg(fitInto(maxStorableImageSizePx))
- pictureData.thumbnailJpgFile.writeJpeg(fitInto(storableThumbnailSizePx))
+ picture.jpgFile.writeJpeg(fitInto(maxStorableImageSizePx))
+ picture.thumbnailJpgFile.writeJpeg(fitInto(storableThumbnailSizePx))
}
- pictures.add(0, pictureData)
- pictureData.jsonFile.writeText(pictureData.toJson())
+ pictures.add(0, picture)
+ picture.jsonFile.writeText(picture.toJson())
+ }
+ }
+
+ override fun delete(picture: PictureData.Camera) {
+ ioScope.launch {
+ picture.jsonFile.delete()
+ picture.jpgFile.delete()
+ picture.thumbnailJpgFile.delete()
}
}
- override suspend fun getThumbnail(pictureData: PictureData.Camera): ImageBitmap =
+ override fun rewrite(picture: PictureData.Camera) {
+ ioScope.launch {
+ picture.jsonFile.delete()
+ picture.jsonFile.writeText(picture.toJson())
+ }
+ }
+
+ override suspend fun getThumbnail(picture: PictureData.Camera): ImageBitmap =
withContext(ioScope.coroutineContext) {
- pictureData.thumbnailJpgFile.readBytes().toImageBitmap()
+ picture.thumbnailJpgFile.readBytes().toImageBitmap()
}
- override suspend fun getImage(pictureData: PictureData.Camera): ImageBitmap =
+ override suspend fun getImage(picture: PictureData.Camera): ImageBitmap =
withContext(ioScope.coroutineContext) {
- pictureData.jpgFile.readBytes().toImageBitmap()
+ picture.jpgFile.readBytes().toImageBitmap()
}
+
+ @OptIn(ExperimentalResourceApi::class)
+ suspend fun getUri(context: Context, picture: PictureData): Uri = withContext(Dispatchers.IO) {
+ val tempFileToShare: File = sharedImagesDir.resolve("share_picture.jpg")
+ when (picture) {
+ is PictureData.Camera -> {
+ picture.jpgFile.copyTo(tempFileToShare, overwrite = true)
+ }
+
+ is PictureData.Resource -> {
+ tempFileToShare.writeBytes(resource(picture.resource).readBytes())
+ }
+ }
+ FileProvider.getUriForFile(
+ context,
+ "example.imageviewer.fileprovider",
+ tempFileToShare
+ )
+ }
}
private fun ImageBitmap.fitInto(px: Int): ImageBitmap {
diff --git a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/EditMemoryDialog.android.kt b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/EditMemoryDialog.android.kt
new file mode 100644
index 0000000000..8d748110ba
--- /dev/null
+++ b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/EditMemoryDialog.android.kt
@@ -0,0 +1,77 @@
+package example.imageviewer.view
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.TextField
+import androidx.compose.material.TextFieldDefaults
+import androidx.compose.runtime.Composable
+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.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+internal actual fun BoxScope.EditMemoryDialog(
+ previousName: String,
+ previousDescription: String,
+ save: (name: String, description: String) -> Unit
+) {
+ var name by remember { mutableStateOf(previousName) }
+ var description by remember { mutableStateOf(previousDescription) }
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Black.copy(alpha = 0.4f))
+ .clickable {
+ save(name, description)
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .padding(30.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color.White),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ TextField(
+ value = name,
+ onValueChange = { name = it },
+ modifier = Modifier.fillMaxWidth(),
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = Color.White,
+ ),
+ textStyle = LocalTextStyle.current.copy(
+ textAlign = TextAlign.Center,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold,
+ ),
+ )
+ TextField(
+ value = description,
+ onValueChange = { description = it },
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = Color.White,
+ unfocusedIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ )
+ )
+ }
+ }
+}
diff --git a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/ImageViewer.android.kt b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/ImageViewer.android.kt
index f3dcb74270..54fa4364e4 100755
--- a/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/ImageViewer.android.kt
+++ b/experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/ImageViewer.android.kt
@@ -1,15 +1,22 @@
package example.imageviewer.view
import android.content.Context
+import android.content.Intent
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import example.imageviewer.*
+import example.imageviewer.filter.PlatformContext
+import example.imageviewer.model.PictureData
import example.imageviewer.storage.AndroidImageStorage
import example.imageviewer.style.ImageViewerTheme
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
@Composable
fun ImageViewerAndroid() {
@@ -27,5 +34,23 @@ private fun getDependencies(context: Context, ioScope: CoroutineScope) = object
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
}
- override val imageStorage: ImageStorage = AndroidImageStorage(pictures, ioScope, context)
+ override val imageStorage: AndroidImageStorage = AndroidImageStorage(pictures, ioScope, context)
+ override val sharePicture: SharePicture = object : SharePicture {
+ override fun share(context: PlatformContext, picture: PictureData) {
+ ioScope.launch {
+ val shareIntent: Intent = Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(
+ Intent.EXTRA_STREAM,
+ imageStorage.getUri(context.androidContext, picture)
+ )
+ type = "image/jpeg"
+ flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ }
+ withContext(Dispatchers.Main) {
+ context.androidContext.startActivity(Intent.createChooser(shareIntent, null))
+ }
+ }
+ }
+ }
}
diff --git a/experimental/examples/imageviewer/shared/src/androidMain/res/xml/file_paths.xml b/experimental/examples/imageviewer/shared/src/androidMain/res/xml/file_paths.xml
new file mode 100644
index 0000000000..30ca54464b
--- /dev/null
+++ b/experimental/examples/imageviewer/shared/src/androidMain/res/xml/file_paths.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt
index 00cda2474f..c976f982e4 100644
--- a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt
@@ -4,6 +4,7 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.ImageBitmap
+import example.imageviewer.filter.PlatformContext
import example.imageviewer.model.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -14,6 +15,7 @@ import org.jetbrains.compose.resources.resource
abstract class Dependencies {
abstract val notification: Notification
abstract val imageStorage: ImageStorage
+ abstract val sharePicture: SharePicture
val pictures: SnapshotStateList = mutableStateListOf(*resourcePictures)
open val externalEvents: Flow = emptyFlow()
val localization: Localization = getCurrentLocalization()
@@ -37,6 +39,40 @@ abstract class Dependencies {
imageStorage.getThumbnail(picture)
}
}
+
+ override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) {
+ imageStorage.saveImage(picture, image)
+ }
+
+ override fun delete(picture: PictureData) {
+ pictures.remove(picture)
+ if (picture is PictureData.Camera) {
+ imageStorage.delete(picture)
+ }
+ }
+
+ override fun edit(picture: PictureData, name: String, description: String): PictureData {
+ when (picture) {
+ is PictureData.Resource -> {
+ val edited = picture.copy(
+ name = name,
+ description = description,
+ )
+ pictures[pictures.indexOf(picture)] = edited
+ return edited
+ }
+
+ is PictureData.Camera -> {
+ val edited = picture.copy(
+ name = name,
+ description = description,
+ )
+ pictures[pictures.indexOf(picture)] = edited
+ imageStorage.rewrite(edited)
+ return edited
+ }
+ }
+ }
}
}
@@ -66,12 +102,21 @@ interface Localization {
interface ImageProvider {
suspend fun getImage(picture: PictureData): ImageBitmap
suspend fun getThumbnail(picture: PictureData): ImageBitmap
+ fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage)
+ fun delete(picture: PictureData)
+ fun edit(picture: PictureData, name: String, description: String): PictureData
}
interface ImageStorage {
- fun saveImage(pictureData: PictureData.Camera, image: PlatformStorableImage)
- suspend fun getThumbnail(pictureData: PictureData.Camera): ImageBitmap
- suspend fun getImage(pictureData: PictureData.Camera): ImageBitmap
+ fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage)
+ fun delete(picture: PictureData.Camera)
+ fun rewrite(picture: PictureData.Camera)
+ suspend fun getThumbnail(picture: PictureData.Camera): ImageBitmap
+ suspend fun getImage(picture: PictureData.Camera): ImageBitmap
+}
+
+interface SharePicture {
+ fun share(context: PlatformContext, picture: PictureData)
}
internal val LocalLocalization = staticCompositionLocalOf {
@@ -86,14 +131,14 @@ internal val LocalImageProvider = staticCompositionLocalOf {
noLocalProvidedFor("LocalImageProvider")
}
-internal val LocalImageStorage = staticCompositionLocalOf {
- noLocalProvidedFor("LocalImageStorage")
-}
-
internal val LocalInternalEvents = staticCompositionLocalOf> {
noLocalProvidedFor("LocalInternalEvents")
}
+internal val LocalSharePicture = staticCompositionLocalOf {
+ noLocalProvidedFor("LocalSharePicture")
+}
+
private fun noLocalProvidedFor(name: String): Nothing {
error("CompositionLocal $name not present")
}
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt
index d4aad33394..f9a7491b6e 100644
--- a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt
@@ -21,8 +21,8 @@ internal fun ImageViewerCommon(
LocalLocalization provides dependencies.localization,
LocalNotification provides dependencies.notification,
LocalImageProvider provides dependencies.imageProvider,
- LocalImageStorage provides dependencies.imageStorage,
- LocalInternalEvents provides dependencies.externalEvents
+ LocalInternalEvents provides dependencies.externalEvents,
+ LocalSharePicture provides dependencies.sharePicture,
) {
ImageViewerWithProvidedDependencies(dependencies.pictures)
}
@@ -63,7 +63,7 @@ internal fun ImageViewerWithProvidedDependencies(
pictures = pictures,
selectedPictureIndex = selectedPictureIndex,
onClickPreviewPicture = { previewPictureId ->
- navigationStack.push(MemoryPage(previewPictureId))
+ navigationStack.push(MemoryPage(mutableStateOf(previewPictureId)))
}
) {
navigationStack.push(CameraPage())
@@ -83,8 +83,8 @@ internal fun ImageViewerWithProvidedDependencies(
MemoryScreen(
pictures = pictures,
memoryPage = page,
- onSelectRelatedMemory = { galleryId ->
- navigationStack.push(MemoryPage(galleryId))
+ onSelectRelatedMemory = { picture ->
+ navigationStack.push(MemoryPage(mutableStateOf(picture)))
},
onBack = {
navigationStack.back()
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt
index 100bbf6e04..6b57aa0362 100644
--- a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt
@@ -1,8 +1,10 @@
package example.imageviewer.model
+import androidx.compose.runtime.MutableState
+
sealed interface Page
-class MemoryPage(val picture: PictureData) : Page
+class MemoryPage(val pictureState: MutableState) : Page
class CameraPage : Page
class FullScreenPage(val picture: PictureData) : Page
class GalleryPage : Page
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/PictureData.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/PictureData.kt
index 63c91a268d..d98d8354e7 100644
--- a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/PictureData.kt
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/PictureData.kt
@@ -19,7 +19,7 @@ sealed interface PictureData {
val gps: GpsPosition
val dateString: String
- class Resource(
+ data class Resource(
val resource: String,
val thumbnailResource: String,
override val name: String,
@@ -29,15 +29,13 @@ sealed interface PictureData {
) : PictureData
@Serializable
- class Camera(
+ data class Camera(
val id: String,
val timeStampSeconds: Long,
override val name: String,
override val description: String,
override val gps: GpsPosition,
) : PictureData {
- override fun equals(other: Any?): Boolean = (other as? Camera)?.id == id
- override fun hashCode(): Int = id.hashCode()
override val dateString: String
get(): String {
val instantTime = Instant.fromEpochSeconds(timeStampSeconds, 0)
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt
index 12e657fb48..2080192de1 100644
--- a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt
@@ -1,6 +1,8 @@
package example.imageviewer
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import example.imageviewer.model.PictureData
import kotlinx.coroutines.CoroutineDispatcher
expect fun Modifier.notchPadding(): Modifier
@@ -10,3 +12,7 @@ expect class PlatformStorableImage
expect fun createUUID(): String
expect val ioDispatcher: CoroutineDispatcher
+
+expect val isShareFeatureSupported: Boolean
+
+expect val shareIcon: ImageVector
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/CameraScreen.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/CameraScreen.kt
index 7495c810ce..fcdc33ada2 100644
--- a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/CameraScreen.kt
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/CameraScreen.kt
@@ -6,12 +6,12 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import example.imageviewer.LocalImageStorage
+import example.imageviewer.LocalImageProvider
import kotlinx.coroutines.delay
@Composable
internal fun CameraScreen(onBack: (resetSelectedPicture: Boolean) -> Unit) {
- val storage = LocalImageStorage.current
+ val imageProvider = LocalImageProvider.current
var showCamera by remember { mutableStateOf(false) }
LaunchedEffect(onBack) {
if (!showCamera) {
@@ -22,7 +22,7 @@ internal fun CameraScreen(onBack: (resetSelectedPicture: Boolean) -> Unit) {
Box(Modifier.fillMaxSize().background(Color.Black)) {
if (showCamera) {
CameraView(Modifier.fillMaxSize(), onCapture = { picture, image ->
- storage.saveImage(picture, image)
+ imageProvider.saveImage(picture, image)
onBack(true)
})
}
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/EditMemoryDialog.common.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/EditMemoryDialog.common.kt
new file mode 100644
index 0000000000..e96bca9222
--- /dev/null
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/EditMemoryDialog.common.kt
@@ -0,0 +1,11 @@
+package example.imageviewer.view
+
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+
+@Composable
+internal expect fun BoxScope.EditMemoryDialog(
+ previousName: String,
+ previousDescription: String,
+ save: (name: String, description: String) -> Unit
+)
diff --git a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
index 7283e8b486..5ccd2679ff 100644
--- a/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
+++ b/experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
@@ -10,6 +10,9 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
@@ -20,18 +23,22 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import example.imageviewer.LocalImageProvider
+import example.imageviewer.LocalSharePicture
+import example.imageviewer.filter.getPlatformContext
+import example.imageviewer.isShareFeatureSupported
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
-@OptIn(ExperimentalResourceApi::class)
@Composable
internal fun MemoryScreen(
pictures: SnapshotStateList,
@@ -41,9 +48,13 @@ internal fun MemoryScreen(
onHeaderClick: (PictureData) -> Unit,
) {
val imageProvider = LocalImageProvider.current
- var headerImage: ImageBitmap? by remember(memoryPage.picture) { mutableStateOf(null) }
- LaunchedEffect(memoryPage.picture) {
- headerImage = imageProvider.getImage(memoryPage.picture)
+ val sharePicture = LocalSharePicture.current
+ var edit: Boolean by remember { mutableStateOf(false) }
+ val picture = memoryPage.pictureState.value
+ var headerImage: ImageBitmap? by remember(picture) { mutableStateOf(null) }
+ val platformContext = getPlatformContext()
+ LaunchedEffect(picture) {
+ headerImage = imageProvider.getImage(picture)
}
Box {
val scrollState = rememberScrollState()
@@ -65,17 +76,20 @@ internal fun MemoryScreen(
headerImage?.let {
MemoryHeader(
it,
- picture = memoryPage.picture,
- onClick = { onHeaderClick(memoryPage.picture) }
+ picture = picture,
+ onClick = { onHeaderClick(picture) }
)
}
}
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
- Column {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
Headliner("Note")
- Collapsible(memoryPage.picture.description)
+ Collapsible(picture.description)
Headliner("Related memories")
- RelatedMemoriesVisualizer(pictures, onSelectRelatedMemory)
+ RelatedMemoriesVisualizer(
+ pictures = remember { (pictures - picture).shuffled().take(8) },
+ onSelectRelatedMemory = onSelectRelatedMemory
+ )
Headliner("Place")
val locationShape = RoundedCornerShape(10.dp)
LocationVisualizer(
@@ -84,30 +98,23 @@ internal fun MemoryScreen(
.border(1.dp, Color.Gray, locationShape)
.fillMaxWidth()
.height(200.dp),
- gps = memoryPage.picture.gps,
- title = memoryPage.picture.name,
+ gps = picture.gps,
+ title = picture.name,
)
Spacer(Modifier.height(50.dp))
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(
- 8.dp,
- Alignment.CenterHorizontally
- ),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Image(
- painterResource("trash.png"),
- contentDescription = null,
- modifier = Modifier.size(14.dp)
- )
- Text(
- text = "Delete Memory",
- textAlign = TextAlign.Left,
- color = ImageviewerColors.onBackground,
- fontSize = 14.sp,
- fontWeight = FontWeight.Normal
- )
+ Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
+ IconWithText(Icons.Default.Delete, "Delete") {
+ imageProvider.delete(picture)
+ onBack()
+ }
+ IconWithText(Icons.Default.Edit, "Edit") {
+ edit = true
+ }
+ if (isShareFeatureSupported) {
+ IconWithText(shareIcon, "Share") {
+ sharePicture.share(platformContext, picture)
+ }
+ }
}
Spacer(Modifier.height(50.dp))
}
@@ -119,6 +126,39 @@ internal fun MemoryScreen(
},
alignRightContent = {},
)
+ if (edit) {
+ EditMemoryDialog(picture.name, picture.description) { name, description ->
+ val edited = imageProvider.edit(picture, name, description)
+ memoryPage.pictureState.value = edited
+ edit = false
+ }
+ }
+ }
+}
+
+@Composable
+private fun IconWithText(icon: ImageVector, text: String, onClick: () -> Unit) {
+ Row(
+ modifier = Modifier.clickable {
+ onClick()
+ },
+ horizontalArrangement = Arrangement.spacedBy(
+ 8.dp,
+ Alignment.CenterHorizontally
+ ),
+ verticalAlignment = Alignment.Bottom
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = text,
+ )
+ Text(
+ text = text,
+ textAlign = TextAlign.Left,
+ color = ImageviewerColors.onBackground,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal
+ )
}
}
@@ -222,7 +262,7 @@ internal fun Headliner(s: String) {
@Composable
internal fun RelatedMemoriesVisualizer(
- ps: List,
+ pictures: List,
onSelectRelatedMemory: (PictureData) -> Unit
) {
Box(
@@ -232,7 +272,7 @@ internal fun RelatedMemoriesVisualizer(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
- itemsIndexed(ps) { idx, item ->
+ itemsIndexed(pictures) { idx, item ->
RelatedMemory(item, onSelectRelatedMemory)
}
}
diff --git a/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt
index a893c619ef..0161ac1474 100644
--- a/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt
+++ b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt
@@ -20,23 +20,31 @@ class DesktopImageStorage(
private val largeImages = mutableMapOf()
private val thumbnails = mutableMapOf()
- override fun saveImage(pictureData: PictureData.Camera, image: PlatformStorableImage) {
+ override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) {
if (image.imageBitmap.width == 0 || image.imageBitmap.height == 0) {
return
}
ioScope.launch {
- largeImages[pictureData] = image.imageBitmap.fitInto(maxStorableImageSizePx)
- thumbnails[pictureData] = image.imageBitmap.fitInto(storableThumbnailSizePx)
- pictures.add(0, pictureData)
+ largeImages[picture] = image.imageBitmap.fitInto(maxStorableImageSizePx)
+ thumbnails[picture] = image.imageBitmap.fitInto(storableThumbnailSizePx)
+ pictures.add(0, picture)
}
}
- override suspend fun getThumbnail(pictureData: PictureData.Camera): ImageBitmap {
- return thumbnails[pictureData]!!
+ override fun delete(picture: PictureData.Camera) {
+ // For now, on Desktop pictures saving in memory. We don't need additional delete logic.
}
- override suspend fun getImage(pictureData: PictureData.Camera): ImageBitmap {
- return largeImages[pictureData]!!
+ override fun rewrite(picture: PictureData.Camera) {
+ // For now, on Desktop pictures saving in memory. We don't need additional rewrite logic.
+ }
+
+ override suspend fun getThumbnail(picture: PictureData.Camera): ImageBitmap {
+ return thumbnails[picture]!!
+ }
+
+ override suspend fun getImage(picture: PictureData.Camera): ImageBitmap {
+ return largeImages[picture]!!
}
}
diff --git a/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt
index 0eb2dc4123..7db77389f9 100644
--- a/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt
+++ b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt
@@ -1,9 +1,13 @@
package example.imageviewer
import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Share
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
+import example.imageviewer.model.PictureData
import kotlinx.coroutines.Dispatchers
import java.util.*
@@ -18,3 +22,7 @@ actual typealias PlatformStorableImage = DesktopStorableImage
actual fun createUUID(): String = UUID.randomUUID().toString()
actual val ioDispatcher = Dispatchers.IO
+
+actual val isShareFeatureSupported: Boolean = false
+
+actual val shareIcon: ImageVector = Icons.Filled.Share
diff --git a/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/EditMemoryDialog.desktop.kt b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/EditMemoryDialog.desktop.kt
new file mode 100644
index 0000000000..8190d2f8ce
--- /dev/null
+++ b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/EditMemoryDialog.desktop.kt
@@ -0,0 +1,87 @@
+package example.imageviewer.view
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.AlertDialog
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.TextField
+import androidx.compose.material.TextFieldDefaults
+import androidx.compose.runtime.Composable
+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.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+internal actual fun BoxScope.EditMemoryDialog(
+ previousName: String,
+ previousDescription: String,
+ save: (name: String, description: String) -> Unit
+) {
+ var name by remember { mutableStateOf(previousName) }
+ var description by remember { mutableStateOf(previousDescription) }
+ AlertDialog(
+ onDismissRequest = {
+ save(name, description)
+ },
+ buttons = {
+ Column(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .padding(30.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color.White),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ TextField(
+ value = name,
+ onValueChange = { name = it },
+ modifier = Modifier.fillMaxWidth(),
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = Color.White,
+ ),
+ textStyle = LocalTextStyle.current.copy(
+ textAlign = TextAlign.Center,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold,
+ ),
+ )
+ TextField(
+ value = description,
+ onValueChange = { description = it },
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = Color.White,
+ unfocusedIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ )
+ )
+ }
+ },
+ )
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Black.copy(alpha = 0.4f))
+ .clickable {
+
+ }
+ ) {
+ }
+}
diff --git a/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt
index d1f478bb9e..7d9abafa43 100755
--- a/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt
+++ b/experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt
@@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import example.imageviewer.*
import example.imageviewer.Notification
+import example.imageviewer.filter.PlatformContext
import example.imageviewer.model.*
import example.imageviewer.style.ImageViewerTheme
import kotlinx.coroutines.CoroutineScope
@@ -23,6 +24,7 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
+import java.awt.Desktop
import java.awt.Dimension
import java.awt.Toolkit
@@ -100,7 +102,12 @@ private fun getDependencies(
toastState.value = ToastState.Shown(text)
}
}
- override val imageStorage: ImageStorage = DesktopImageStorage(pictures, ioScope)
+ override val imageStorage: DesktopImageStorage = DesktopImageStorage(pictures, ioScope)
+ override val sharePicture: SharePicture = object : SharePicture {
+ override fun share(context: PlatformContext, picture: PictureData) {
+ // On Desktop share feature not supported
+ }
+ }
override val externalEvents = events
}
diff --git a/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt
index 6b17e44fe6..d693b51a7d 100755
--- a/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt
+++ b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt
@@ -4,17 +4,28 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
+import example.imageviewer.filter.PlatformContext
+import example.imageviewer.model.PictureData
import example.imageviewer.storage.IosImageStorage
import example.imageviewer.style.ImageViewerTheme
import example.imageviewer.view.Toast
import example.imageviewer.view.ToastState
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import platform.UIKit.UIActivityViewController
+import platform.UIKit.UIApplication
+import platform.UIKit.UIImage
+import platform.UIKit.UIWindow
@Composable
internal fun ImageViewerIos() {
val toastState = remember { mutableStateOf(ToastState.Hidden) }
val ioScope: CoroutineScope = rememberCoroutineScope { ioDispatcher }
- val dependencies = remember(ioScope) { getDependencies(ioScope, toastState) }
+ val dependencies = remember(ioScope) {
+ getDependencies(ioScope, toastState)
+ }
ImageViewerTheme {
Surface(
@@ -35,5 +46,27 @@ fun getDependencies(ioScope: CoroutineScope, toastState: MutableState Boolean) =
?.map { File(this, it) }
?.toTypedArray()
+fun NSURL.delete() {
+ NSFileManager.defaultManager.removeItemAtURL(this, null)
+}
+
suspend fun NSURL.readData(): NSData {
while (true) {
val data = NSData.dataWithContentsOfURL(this)
diff --git a/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt
index ea916a34ca..cf3901ff88 100644
--- a/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt
+++ b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt
@@ -56,26 +56,56 @@ class IosImageStorage(
}
}
- override fun saveImage(pictureData: PictureData.Camera, image: PlatformStorableImage) {
+ override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) {
ioScope.launch {
with(image.rawValue) {
- pictureData.jpgFile.writeJpeg(fitInto(maxStorableImageSizePx))
- pictureData.thumbnailJpgFile.writeJpeg(fitInto(storableThumbnailSizePx))
+ picture.jpgFile.writeJpeg(fitInto(maxStorableImageSizePx))
+ picture.thumbnailJpgFile.writeJpeg(fitInto(storableThumbnailSizePx))
}
- pictures.add(0, pictureData)
- pictureData.jsonFile.writeText(pictureData.toJson())
+ pictures.add(0, picture)
+ picture.jsonFile.writeText(picture.toJson())
}
}
- override suspend fun getThumbnail(pictureData: PictureData.Camera): ImageBitmap =
+ override fun delete(picture: PictureData.Camera) {
+ ioScope.launch {
+ picture.jsonFile.delete()
+ picture.jpgFile.delete()
+ picture.thumbnailJpgFile.delete()
+ }
+ }
+
+ override fun rewrite(picture: PictureData.Camera) {
+ ioScope.launch {
+ picture.jsonFile.delete()
+ picture.jsonFile.writeText(picture.toJson())
+ }
+ }
+
+ override suspend fun getThumbnail(picture: PictureData.Camera): ImageBitmap =
withContext(ioScope.coroutineContext) {
- pictureData.thumbnailJpgFile.readBytes().toImageBitmap()
+ picture.thumbnailJpgFile.readBytes().toImageBitmap()
}
- override suspend fun getImage(pictureData: PictureData.Camera): ImageBitmap =
+ override suspend fun getImage(picture: PictureData.Camera): ImageBitmap =
withContext(ioScope.coroutineContext) {
- pictureData.jpgFile.readBytes().toImageBitmap()
+ picture.jpgFile.readBytes().toImageBitmap()
}
+
+ suspend fun getNSDataToShare(picture: PictureData): NSData = withContext(Dispatchers.IO) {
+ when (picture) {
+ is PictureData.Camera -> {
+ picture.jpgFile
+ }
+
+ is PictureData.Resource -> {
+ NSURL(
+ fileURLWithPath = NSBundle.mainBundle.resourcePath + "/" + picture.resource,
+ isDirectory = false
+ )
+ }
+ }.readData()
+ }
}
private fun UIImage.fitInto(px: Int): UIImage {
diff --git a/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/EditMemoryDialog.ios.kt b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/EditMemoryDialog.ios.kt
new file mode 100644
index 0000000000..8d748110ba
--- /dev/null
+++ b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/EditMemoryDialog.ios.kt
@@ -0,0 +1,77 @@
+package example.imageviewer.view
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.TextField
+import androidx.compose.material.TextFieldDefaults
+import androidx.compose.runtime.Composable
+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.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+internal actual fun BoxScope.EditMemoryDialog(
+ previousName: String,
+ previousDescription: String,
+ save: (name: String, description: String) -> Unit
+) {
+ var name by remember { mutableStateOf(previousName) }
+ var description by remember { mutableStateOf(previousDescription) }
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Black.copy(alpha = 0.4f))
+ .clickable {
+ save(name, description)
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .padding(30.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color.White),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ TextField(
+ value = name,
+ onValueChange = { name = it },
+ modifier = Modifier.fillMaxWidth(),
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = Color.White,
+ ),
+ textStyle = LocalTextStyle.current.copy(
+ textAlign = TextAlign.Center,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold,
+ ),
+ )
+ TextField(
+ value = description,
+ onValueChange = { description = it },
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = Color.White,
+ unfocusedIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ )
+ )
+ }
+ }
+}
diff --git a/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/LocationVisualizer.ios.kt b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/LocationVisualizer.ios.kt
index aaa46d4545..5cc8453ad2 100644
--- a/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/LocationVisualizer.ios.kt
+++ b/experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/LocationVisualizer.ios.kt
@@ -1,6 +1,7 @@
package example.imageviewer.view
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.interop.UIKitInteropView
import example.imageviewer.model.GpsPosition
@@ -11,26 +12,29 @@ import platform.MapKit.MKPointAnnotation
@Composable
internal actual fun LocationVisualizer(modifier: Modifier, gps: GpsPosition, title: String) {
+ val location = CLLocationCoordinate2DMake(gps.latitude, gps.longitude)
+ val annotation = remember {
+ MKPointAnnotation(
+ location,
+ title = null,
+ subtitle = null
+ )
+ }
+ val mkMapView = remember { MKMapView().apply { addAnnotation(annotation) } }
+ annotation.setTitle(title)
UIKitInteropView(
modifier = modifier,
factory = {
- val mkMapView = MKMapView()
- val cityAmsterdam = CLLocationCoordinate2DMake(gps.latitude, gps.longitude)
+ mkMapView
+ },
+ update = {
mkMapView.setRegion(
MKCoordinateRegionMakeWithDistance(
- centerCoordinate = cityAmsterdam,
+ centerCoordinate = location,
10_000.0, 10_000.0
),
animated = false
)
- mkMapView.addAnnotation(
- MKPointAnnotation(
- cityAmsterdam,
- title = title,
- subtitle = null
- )
- )
- mkMapView
- },
+ }
)
}