ImageViewer Pager and icons (#2982)
@@ -35,7 +35,7 @@ kotlin {
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material)
|
||||
//implementation(compose.materialIconsExtended) // TODO not working on iOS
|
||||
//implementation(compose.materialIconsExtended) // TODO not working on iOS for now
|
||||
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
|
||||
implementation(compose.components.resources)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||
|
||||
@@ -2,14 +2,13 @@ package example.imageviewer
|
||||
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import java.util.UUID
|
||||
|
||||
actual fun Modifier.notchPadding(): Modifier = displayCutoutPadding().statusBarsPadding()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.location.Location
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCapture.OnImageCapturedCallback
|
||||
@@ -11,10 +10,9 @@ import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -28,8 +26,8 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import com.google.android.gms.location.CurrentLocationRequest
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.gms.tasks.Task
|
||||
import example.imageviewer.*
|
||||
import example.imageviewer.icon.IconPhotoCamera
|
||||
import example.imageviewer.model.GpsPosition
|
||||
import example.imageviewer.model.PictureData
|
||||
import example.imageviewer.model.createCameraPictureData
|
||||
@@ -104,57 +102,57 @@ private fun CameraWithGrantedPermission(
|
||||
}
|
||||
val nameAndDescription = createNewPhotoNameAndDescription()
|
||||
var capturePhotoStarted by remember { mutableStateOf(false) }
|
||||
Box(contentAlignment = Alignment.BottomCenter, modifier = modifier) {
|
||||
Box(modifier = modifier) {
|
||||
AndroidView({ previewView }, modifier = Modifier.fillMaxSize())
|
||||
Button(
|
||||
CircularButton(
|
||||
imageVector = IconPhotoCamera,
|
||||
modifier = Modifier.align(Alignment.BottomCenter).padding(36.dp),
|
||||
enabled = !capturePhotoStarted,
|
||||
onClick = {
|
||||
fun addLocationInfoAndReturnResult(imageBitmap: ImageBitmap) {
|
||||
fun sendToStorage(gpsPosition: GpsPosition) {
|
||||
onCapture(
|
||||
createCameraPictureData(
|
||||
name = nameAndDescription.name,
|
||||
description = nameAndDescription.description,
|
||||
gps = gpsPosition
|
||||
),
|
||||
AndroidStorableImage(imageBitmap)
|
||||
)
|
||||
capturePhotoStarted = false
|
||||
}
|
||||
LocationServices.getFusedLocationProviderClient(context)
|
||||
.getCurrentLocation(CurrentLocationRequest.Builder().build(), null)
|
||||
.apply {
|
||||
addOnSuccessListener {
|
||||
sendToStorage(GpsPosition(it.latitude, it.longitude))
|
||||
}
|
||||
addOnFailureListener {
|
||||
sendToStorage(GpsPosition(0.0, 0.0))
|
||||
}
|
||||
) {
|
||||
fun addLocationInfoAndReturnResult(imageBitmap: ImageBitmap) {
|
||||
fun sendToStorage(gpsPosition: GpsPosition) {
|
||||
onCapture(
|
||||
createCameraPictureData(
|
||||
name = nameAndDescription.name,
|
||||
description = nameAndDescription.description,
|
||||
gps = gpsPosition
|
||||
),
|
||||
AndroidStorableImage(imageBitmap)
|
||||
)
|
||||
capturePhotoStarted = false
|
||||
}
|
||||
LocationServices.getFusedLocationProviderClient(context)
|
||||
.getCurrentLocation(CurrentLocationRequest.Builder().build(), null)
|
||||
.apply {
|
||||
addOnSuccessListener {
|
||||
sendToStorage(GpsPosition(it.latitude, it.longitude))
|
||||
}
|
||||
}
|
||||
addOnFailureListener {
|
||||
sendToStorage(GpsPosition(0.0, 0.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
capturePhotoStarted = true
|
||||
imageCapture.takePicture(executor, object : OnImageCapturedCallback() {
|
||||
override fun onCaptureSuccess(image: ImageProxy) {
|
||||
val byteArray: ByteArray = image.planes[0].buffer.toByteArray()
|
||||
val imageBitmap = byteArray.toImageBitmap()
|
||||
image.close()
|
||||
addLocationInfoAndReturnResult(imageBitmap)
|
||||
}
|
||||
})
|
||||
viewScope.launch {
|
||||
// TODO: There is a known issue with Android emulator
|
||||
// https://partnerissuetracker.corp.google.com/issues/161034252
|
||||
// After 5 seconds delay, let's assume that the bug appears and publish a prepared photo
|
||||
delay(5000)
|
||||
if (capturePhotoStarted) {
|
||||
addLocationInfoAndReturnResult(
|
||||
resource("android-emulator-photo.jpg").readBytes().toImageBitmap()
|
||||
)
|
||||
}
|
||||
capturePhotoStarted = true
|
||||
imageCapture.takePicture(executor, object : OnImageCapturedCallback() {
|
||||
override fun onCaptureSuccess(image: ImageProxy) {
|
||||
val byteArray: ByteArray = image.planes[0].buffer.toByteArray()
|
||||
val imageBitmap = byteArray.toImageBitmap()
|
||||
image.close()
|
||||
addLocationInfoAndReturnResult(imageBitmap)
|
||||
}
|
||||
}) {
|
||||
Text(LocalLocalization.current.takePhoto, color = Color.White)
|
||||
})
|
||||
viewScope.launch {
|
||||
// TODO: There is a known issue with Android emulator
|
||||
// https://partnerissuetracker.corp.google.com/issues/161034252
|
||||
// After 5 seconds delay, let's assume that the bug appears and publish a prepared photo
|
||||
delay(5000)
|
||||
if (capturePhotoStarted) {
|
||||
addLocationInfoAndReturnResult(
|
||||
resource("android-emulator-photo.jpg").readBytes().toImageBitmap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (capturePhotoStarted) {
|
||||
CircularProgressIndicator(
|
||||
|
||||
@@ -33,7 +33,7 @@ internal fun ImageViewerCommon(
|
||||
internal fun ImageViewerWithProvidedDependencies(
|
||||
pictures: SnapshotStateList<PictureData>
|
||||
) {
|
||||
val selectedPictureIndex: MutableState<Int> = mutableStateOf(0)
|
||||
val selectedPictureIndex = remember { mutableStateOf(0) }
|
||||
val navigationStack = remember { NavigationStack<Page>(GalleryPage()) }
|
||||
val externalEvents = LocalInternalEvents.current
|
||||
LaunchedEffect(Unit) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
val IconCustomArrowBack = materialIcon("Filled.CustomArrowBack") {
|
||||
val startY = 12f
|
||||
val startX = 1f
|
||||
val arrowWidth = 8f
|
||||
val arrowHeight = 14f
|
||||
val lineWidth = 14f
|
||||
val lineHeight = 2f
|
||||
materialPath {
|
||||
moveTo(startX, startY)
|
||||
lineToRelative(arrowWidth, arrowHeight / 2)
|
||||
verticalLineToRelative(-arrowHeight)
|
||||
close()
|
||||
moveTo(startX + arrowWidth, startY + lineHeight / 2)
|
||||
verticalLineToRelative(-lineHeight)
|
||||
horizontalLineToRelative(lineWidth)
|
||||
verticalLineToRelative(lineHeight)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
// TODO Copied from "material:material-icons-extended", because this artifact is not working on iOS for now
|
||||
val IconAutoFixHigh = materialIcon(name = "Filled.AutoFixHigh") {
|
||||
materialPath {
|
||||
moveTo(7.5f, 5.6f)
|
||||
lineTo(10.0f, 7.0f)
|
||||
lineTo(8.6f, 4.5f)
|
||||
lineTo(10.0f, 2.0f)
|
||||
lineTo(7.5f, 3.4f)
|
||||
lineTo(5.0f, 2.0f)
|
||||
lineToRelative(1.4f, 2.5f)
|
||||
lineTo(5.0f, 7.0f)
|
||||
close()
|
||||
moveTo(19.5f, 15.4f)
|
||||
lineTo(17.0f, 14.0f)
|
||||
lineToRelative(1.4f, 2.5f)
|
||||
lineTo(17.0f, 19.0f)
|
||||
lineToRelative(2.5f, -1.4f)
|
||||
lineTo(22.0f, 19.0f)
|
||||
lineToRelative(-1.4f, -2.5f)
|
||||
lineTo(22.0f, 14.0f)
|
||||
close()
|
||||
moveTo(22.0f, 2.0f)
|
||||
lineToRelative(-2.5f, 1.4f)
|
||||
lineTo(17.0f, 2.0f)
|
||||
lineToRelative(1.4f, 2.5f)
|
||||
lineTo(17.0f, 7.0f)
|
||||
lineToRelative(2.5f, -1.4f)
|
||||
lineTo(22.0f, 7.0f)
|
||||
lineToRelative(-1.4f, -2.5f)
|
||||
close()
|
||||
moveTo(14.37f, 7.29f)
|
||||
curveToRelative(-0.39f, -0.39f, -1.02f, -0.39f, -1.41f, 0.0f)
|
||||
lineTo(1.29f, 18.96f)
|
||||
curveToRelative(-0.39f, 0.39f, -0.39f, 1.02f, 0.0f, 1.41f)
|
||||
lineToRelative(2.34f, 2.34f)
|
||||
curveToRelative(0.39f, 0.39f, 1.02f, 0.39f, 1.41f, 0.0f)
|
||||
lineTo(16.7f, 11.05f)
|
||||
curveToRelative(0.39f, -0.39f, 0.39f, -1.02f, 0.0f, -1.41f)
|
||||
lineToRelative(-2.33f, -2.35f)
|
||||
close()
|
||||
moveTo(13.34f, 12.78f)
|
||||
lineToRelative(-2.12f, -2.12f)
|
||||
lineToRelative(2.44f, -2.44f)
|
||||
lineToRelative(2.12f, 2.12f)
|
||||
lineToRelative(-2.44f, 2.44f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
// TODO Copied from "material:material-icons-extended", because this artifact is not working on iOS for now
|
||||
val IconIosShare = materialIcon(name = "Filled.IosShare") {
|
||||
materialPath {
|
||||
moveTo(16.0f, 5.0f)
|
||||
lineToRelative(-1.42f, 1.42f)
|
||||
lineToRelative(-1.59f, -1.59f)
|
||||
lineTo(12.99f, 16.0f)
|
||||
horizontalLineToRelative(-1.98f)
|
||||
lineTo(11.01f, 4.83f)
|
||||
lineTo(9.42f, 6.42f)
|
||||
lineTo(8.0f, 5.0f)
|
||||
lineToRelative(4.0f, -4.0f)
|
||||
lineToRelative(4.0f, 4.0f)
|
||||
close()
|
||||
moveTo(20.0f, 10.0f)
|
||||
verticalLineToRelative(11.0f)
|
||||
curveToRelative(0.0f, 1.1f, -0.9f, 2.0f, -2.0f, 2.0f)
|
||||
lineTo(6.0f, 23.0f)
|
||||
curveToRelative(-1.11f, 0.0f, -2.0f, -0.9f, -2.0f, -2.0f)
|
||||
lineTo(4.0f, 10.0f)
|
||||
curveToRelative(0.0f, -1.11f, 0.89f, -2.0f, 2.0f, -2.0f)
|
||||
horizontalLineToRelative(3.0f)
|
||||
verticalLineToRelative(2.0f)
|
||||
lineTo(6.0f, 10.0f)
|
||||
verticalLineToRelative(11.0f)
|
||||
horizontalLineToRelative(12.0f)
|
||||
lineTo(18.0f, 10.0f)
|
||||
horizontalLineToRelative(-3.0f)
|
||||
lineTo(15.0f, 8.0f)
|
||||
horizontalLineToRelative(3.0f)
|
||||
curveToRelative(1.1f, 0.0f, 2.0f, 0.89f, 2.0f, 2.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
// TODO Copied from "material:material-icons-extended", because this artifact is not working on iOS for now
|
||||
val IconMap = materialIcon(name = "Filled.Map") {
|
||||
materialPath {
|
||||
moveTo(20.5f, 3.0f)
|
||||
lineToRelative(-0.16f, 0.03f)
|
||||
lineTo(15.0f, 5.1f)
|
||||
lineTo(9.0f, 3.0f)
|
||||
lineTo(3.36f, 4.9f)
|
||||
curveToRelative(-0.21f, 0.07f, -0.36f, 0.25f, -0.36f, 0.48f)
|
||||
verticalLineTo(20.5f)
|
||||
curveToRelative(0.0f, 0.28f, 0.22f, 0.5f, 0.5f, 0.5f)
|
||||
lineToRelative(0.16f, -0.03f)
|
||||
lineTo(9.0f, 18.9f)
|
||||
lineToRelative(6.0f, 2.1f)
|
||||
lineToRelative(5.64f, -1.9f)
|
||||
curveToRelative(0.21f, -0.07f, 0.36f, -0.25f, 0.36f, -0.48f)
|
||||
verticalLineTo(3.5f)
|
||||
curveToRelative(0.0f, -0.28f, -0.22f, -0.5f, -0.5f, -0.5f)
|
||||
close()
|
||||
moveTo(15.0f, 19.0f)
|
||||
lineToRelative(-6.0f, -2.11f)
|
||||
verticalLineTo(5.0f)
|
||||
lineToRelative(6.0f, 2.11f)
|
||||
verticalLineTo(19.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
// TODO Copied from "material:material-icons-extended", because this artifact is not working on iOS for now
|
||||
val IconMenu = materialIcon(name = "Filled.Menu") {
|
||||
materialPath {
|
||||
moveTo(3.0f, 18.0f)
|
||||
horizontalLineToRelative(18.0f)
|
||||
verticalLineToRelative(-2.0f)
|
||||
lineTo(3.0f, 16.0f)
|
||||
verticalLineToRelative(2.0f)
|
||||
close()
|
||||
moveTo(3.0f, 13.0f)
|
||||
horizontalLineToRelative(18.0f)
|
||||
verticalLineToRelative(-2.0f)
|
||||
lineTo(3.0f, 11.0f)
|
||||
verticalLineToRelative(2.0f)
|
||||
close()
|
||||
moveTo(3.0f, 6.0f)
|
||||
verticalLineToRelative(2.0f)
|
||||
horizontalLineToRelative(18.0f)
|
||||
lineTo(21.0f, 6.0f)
|
||||
lineTo(3.0f, 6.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
// TODO Copied from "material:material-icons-extended", because this artifact is not working on iOS for now
|
||||
val IconMoreVert = materialIcon(name = "Filled.MoreVert") {
|
||||
materialPath {
|
||||
moveTo(12.0f, 8.0f)
|
||||
curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)
|
||||
reflectiveCurveToRelative(-0.9f, -2.0f, -2.0f, -2.0f)
|
||||
reflectiveCurveToRelative(-2.0f, 0.9f, -2.0f, 2.0f)
|
||||
reflectiveCurveToRelative(0.9f, 2.0f, 2.0f, 2.0f)
|
||||
close()
|
||||
moveTo(12.0f, 10.0f)
|
||||
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
|
||||
reflectiveCurveToRelative(0.9f, 2.0f, 2.0f, 2.0f)
|
||||
reflectiveCurveToRelative(2.0f, -0.9f, 2.0f, -2.0f)
|
||||
reflectiveCurveToRelative(-0.9f, -2.0f, -2.0f, -2.0f)
|
||||
close()
|
||||
moveTo(12.0f, 16.0f)
|
||||
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
|
||||
reflectiveCurveToRelative(0.9f, 2.0f, 2.0f, 2.0f)
|
||||
reflectiveCurveToRelative(2.0f, -0.9f, 2.0f, -2.0f)
|
||||
reflectiveCurveToRelative(-0.9f, -2.0f, -2.0f, -2.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
// TODO Copied from "material:material-icons-extended", because this artifact is not working on iOS for now
|
||||
val IconPhotoCamera = materialIcon(name = "Filled.PhotoCamera") {
|
||||
materialPath {
|
||||
moveTo(12.0f, 12.0f)
|
||||
moveToRelative(-3.2f, 0.0f)
|
||||
arcToRelative(3.2f, 3.2f, 0.0f, true, true, 6.4f, 0.0f)
|
||||
arcToRelative(3.2f, 3.2f, 0.0f, true, true, -6.4f, 0.0f)
|
||||
}
|
||||
materialPath {
|
||||
moveTo(9.0f, 2.0f)
|
||||
lineTo(7.17f, 4.0f)
|
||||
lineTo(4.0f, 4.0f)
|
||||
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
|
||||
verticalLineToRelative(12.0f)
|
||||
curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f)
|
||||
horizontalLineToRelative(16.0f)
|
||||
curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)
|
||||
lineTo(22.0f, 6.0f)
|
||||
curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f)
|
||||
horizontalLineToRelative(-3.17f)
|
||||
lineTo(15.0f, 2.0f)
|
||||
lineTo(9.0f, 2.0f)
|
||||
close()
|
||||
moveTo(12.0f, 17.0f)
|
||||
curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f)
|
||||
reflectiveCurveToRelative(2.24f, -5.0f, 5.0f, -5.0f)
|
||||
reflectiveCurveToRelative(5.0f, 2.24f, 5.0f, 5.0f)
|
||||
reflectiveCurveToRelative(-2.24f, 5.0f, -5.0f, 5.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package example.imageviewer.icon
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
|
||||
// TODO Copied from "material:material-icons-extended", because this artifact is not working on iOS for now
|
||||
val IconVisibility = materialIcon(name = "Filled.Visibility") {
|
||||
materialPath {
|
||||
moveTo(12.0f, 4.5f)
|
||||
curveTo(7.0f, 4.5f, 2.73f, 7.61f, 1.0f, 12.0f)
|
||||
curveToRelative(1.73f, 4.39f, 6.0f, 7.5f, 11.0f, 7.5f)
|
||||
reflectiveCurveToRelative(9.27f, -3.11f, 11.0f, -7.5f)
|
||||
curveToRelative(-1.73f, -4.39f, -6.0f, -7.5f, -11.0f, -7.5f)
|
||||
close()
|
||||
moveTo(12.0f, 17.0f)
|
||||
curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f)
|
||||
reflectiveCurveToRelative(2.24f, -5.0f, 5.0f, -5.0f)
|
||||
reflectiveCurveToRelative(5.0f, 2.24f, 5.0f, 5.0f)
|
||||
reflectiveCurveToRelative(-2.24f, 5.0f, -5.0f, 5.0f)
|
||||
close()
|
||||
moveTo(12.0f, 9.0f)
|
||||
curveToRelative(-1.66f, 0.0f, -3.0f, 1.34f, -3.0f, 3.0f)
|
||||
reflectiveCurveToRelative(1.34f, 3.0f, 3.0f, 3.0f)
|
||||
reflectiveCurveToRelative(3.0f, -1.34f, 3.0f, -3.0f)
|
||||
reflectiveCurveToRelative(-1.34f, -3.0f, -3.0f, -3.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,67 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import example.imageviewer.LocalLocalization
|
||||
import example.imageviewer.icon.IconCustomArrowBack
|
||||
import example.imageviewer.style.ImageviewerColors
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Composable
|
||||
internal fun CircularButton(
|
||||
image: Painter,
|
||||
content: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier.size(54.dp).clip(CircleShape).background(ImageviewerColors.uiLightBlack)
|
||||
.clickable { onClick() }, contentAlignment = Alignment.Center
|
||||
modifier
|
||||
.size(60.dp)
|
||||
.clip(CircleShape)
|
||||
.background(ImageviewerColors.uiLightBlack)
|
||||
.run {
|
||||
if (enabled) {
|
||||
clickable { onClick() }
|
||||
} else this
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Image(
|
||||
image,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
@Composable
|
||||
internal fun CircularButton(
|
||||
imageVector: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
CircularButton(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
Icon(imageVector, null, Modifier.size(34.dp), Color.White)
|
||||
},
|
||||
enabled = enabled,
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun BackButton(onClick: () -> Unit) {
|
||||
Tooltip(LocalLocalization.current.back) {
|
||||
CircularButton(
|
||||
painterResource("arrowleft.png"),
|
||||
imageVector = IconCustomArrowBack,
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
@file:OptIn(ExperimentalResourceApi::class)
|
||||
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.AnimationConstants
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -9,26 +14,38 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
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.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import example.imageviewer.*
|
||||
import example.imageviewer.icon.IconMenu
|
||||
import example.imageviewer.icon.IconVisibility
|
||||
import example.imageviewer.model.*
|
||||
import example.imageviewer.style.ImageviewerColors
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
enum class GalleryStyle {
|
||||
SQUARES,
|
||||
LIST
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun GalleryScreen(
|
||||
pictures: SnapshotStateList<PictureData>,
|
||||
@@ -36,22 +53,45 @@ internal fun GalleryScreen(
|
||||
onClickPreviewPicture: (PictureData) -> Unit,
|
||||
onMakeNewMemory: () -> Unit
|
||||
) {
|
||||
val imageProvider = LocalImageProvider.current
|
||||
val viewScope = rememberCoroutineScope()
|
||||
|
||||
val pagerState = rememberPagerState(initialPage = selectedPictureIndex.value)
|
||||
LaunchedEffect(pagerState) {
|
||||
// Subscribe to page changes
|
||||
snapshotFlow { pagerState.currentPage }.collect { page ->
|
||||
selectedPictureIndex.value = page
|
||||
}
|
||||
}
|
||||
|
||||
fun nextImage() {
|
||||
selectedPictureIndex.value =
|
||||
(selectedPictureIndex.value + 1).mod(pictures.size)
|
||||
viewScope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
(pagerState.currentPage + 1).mod(pictures.size)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun previousImage() {
|
||||
selectedPictureIndex.value =
|
||||
(selectedPictureIndex.value - 1).mod(pictures.size)
|
||||
viewScope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
(pagerState.currentPage - 1).mod(pictures.size)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun selectPicture(picture: PictureData) {
|
||||
selectedPictureIndex.value = pictures.indexOfFirst { it == picture }
|
||||
fun selectPicture(index: Int) {
|
||||
viewScope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
index,
|
||||
animationSpec = tween(
|
||||
easing = LinearOutSlowInEasing,
|
||||
durationMillis = AnimationConstants.DefaultDurationMillis * 2
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val picture = pictures.getOrNull(selectedPictureIndex.value)
|
||||
|
||||
var galleryStyle by remember { mutableStateOf(GalleryStyle.SQUARES) }
|
||||
val externalEvents = LocalInternalEvents.current
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -63,20 +103,43 @@ internal fun GalleryScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.background(MaterialTheme.colors.background)) {
|
||||
Box {
|
||||
picture?.let {
|
||||
PreviewImage(
|
||||
picture = it, onClick = {
|
||||
onClickPreviewPicture(it)
|
||||
Box(
|
||||
Modifier.fillMaxWidth().height(393.dp)
|
||||
.background(Color.Black),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
Modifier.fillMaxSize()
|
||||
.clickable {
|
||||
onClickPreviewPicture(pictures[pagerState.currentPage])
|
||||
}
|
||||
) {
|
||||
HorizontalPager(pictures.size, state = pagerState) { idx ->
|
||||
val picture = pictures[idx]
|
||||
var image: ImageBitmap? by remember(picture) { mutableStateOf(null) }
|
||||
LaunchedEffect(picture) {
|
||||
image = imageProvider.getImage(picture)
|
||||
}
|
||||
if (image != null) {
|
||||
Box(Modifier.fillMaxSize().animatePageChanges(pagerState, idx)) {
|
||||
Image(
|
||||
bitmap = image!!,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
MemoryTextOverlay(picture)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
TopLayout(
|
||||
alignLeftContent = {},
|
||||
alignRightContent = {
|
||||
CircularButton(painterResource("list_view.png")) {
|
||||
CircularButton(imageVector = IconMenu) {
|
||||
galleryStyle = when (galleryStyle) {
|
||||
GalleryStyle.SQUARES -> GalleryStyle.LIST
|
||||
GalleryStyle.LIST -> GalleryStyle.SQUARES
|
||||
@@ -89,7 +152,7 @@ internal fun GalleryScreen(
|
||||
when (galleryStyle) {
|
||||
GalleryStyle.SQUARES -> SquaresGalleryView(
|
||||
images = pictures,
|
||||
selectedImage = picture,
|
||||
pagerState = pagerState,
|
||||
onSelect = { selectPicture(it) },
|
||||
)
|
||||
|
||||
@@ -100,7 +163,7 @@ internal fun GalleryScreen(
|
||||
)
|
||||
}
|
||||
CircularButton(
|
||||
image = painterResource("plus.png"),
|
||||
Icons.Filled.Add,
|
||||
modifier = Modifier.align(Alignment.BottomCenter).padding(36.dp),
|
||||
onClick = onMakeNewMemory,
|
||||
)
|
||||
@@ -108,11 +171,12 @@ internal fun GalleryScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun SquaresGalleryView(
|
||||
images: List<PictureData>,
|
||||
selectedImage: PictureData?,
|
||||
onSelect: (PictureData) -> Unit,
|
||||
pagerState: PagerState,
|
||||
onSelect: (Int) -> Unit,
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
@@ -121,17 +185,15 @@ private fun SquaresGalleryView(
|
||||
horizontalArrangement = Arrangement.spacedBy(1.dp)
|
||||
) {
|
||||
itemsIndexed(images) { idx, picture ->
|
||||
val isSelected = picture == selectedImage
|
||||
SquareThumbnail(
|
||||
picture = picture,
|
||||
onClick = { onSelect(picture) },
|
||||
isHighlighted = isSelected
|
||||
onClick = { onSelect(idx) },
|
||||
isHighlighted = pagerState.targetPage == idx
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
@Composable
|
||||
internal fun SquareThumbnail(
|
||||
picture: PictureData,
|
||||
@@ -139,8 +201,7 @@ internal fun SquareThumbnail(
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Box(
|
||||
Modifier.aspectRatio(1.0f).clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.BottomEnd
|
||||
Modifier.aspectRatio(1.0f).clickable(onClick = onClick)
|
||||
) {
|
||||
Tooltip(picture.name) {
|
||||
ThumbnailImage(
|
||||
@@ -148,27 +209,33 @@ internal fun SquareThumbnail(
|
||||
picture = picture,
|
||||
)
|
||||
}
|
||||
if (isHighlighted) {
|
||||
Box(Modifier.fillMaxSize().background(ImageviewerColors.uiLightBlack))
|
||||
Box(
|
||||
Modifier
|
||||
.padding(end = 4.dp, bottom = 4.dp)
|
||||
.clip(CircleShape)
|
||||
.width(32.dp)
|
||||
.background(ImageviewerColors.uiLightBlack)
|
||||
.aspectRatio(1.0f)
|
||||
.clickable {
|
||||
onClick()
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource("eye.png"),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.width(17.dp)
|
||||
.height(17.dp),
|
||||
)
|
||||
val tween = tween<Float>(
|
||||
durationMillis = AnimationConstants.DefaultDurationMillis * 3,
|
||||
delayMillis = 100,
|
||||
easing = LinearOutSlowInEasing,
|
||||
)
|
||||
AnimatedVisibility(isHighlighted, enter = fadeIn(tween), exit = fadeOut(tween)) {
|
||||
Box(Modifier.fillMaxSize().background(ImageviewerColors.uiLightBlack)) {
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(end = 4.dp, bottom = 4.dp)
|
||||
.clip(CircleShape)
|
||||
.width(32.dp)
|
||||
.background(ImageviewerColors.uiLightBlack)
|
||||
.aspectRatio(1.0f)
|
||||
.clickable {
|
||||
onClick()
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = IconVisibility,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(17.dp),
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,7 +244,7 @@ internal fun SquareThumbnail(
|
||||
@Composable
|
||||
private fun ListGalleryView(
|
||||
pictures: List<PictureData>,
|
||||
onSelect: (PictureData) -> Unit,
|
||||
onSelect: (Int) -> Unit,
|
||||
onFullScreen: (PictureData) -> Unit,
|
||||
) {
|
||||
val notification = LocalNotification.current
|
||||
@@ -189,7 +256,7 @@ private fun ListGalleryView(
|
||||
Thumbnail(
|
||||
picture = p.value,
|
||||
onClickSelect = {
|
||||
onSelect(p.value)
|
||||
onSelect(p.index)
|
||||
},
|
||||
onClickFullScreen = {
|
||||
onFullScreen(p.value)
|
||||
@@ -202,3 +269,14 @@ private fun ListGalleryView(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun Modifier.animatePageChanges(pagerState: PagerState, index: Int) =
|
||||
graphicsLayer {
|
||||
val x = (pagerState.currentPage - index + pagerState.currentPageOffsetFraction) * 2
|
||||
alpha = 1f - (x.absoluteValue * 0.7f).coerceIn(0f, 0.7f)
|
||||
val scale = 1f - (x.absoluteValue * 0.4f).coerceIn(0f, 0.4f)
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
rotationY = x * 15f
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.sp
|
||||
import example.imageviewer.LocalImageProvider
|
||||
import example.imageviewer.LocalSharePicture
|
||||
import example.imageviewer.filter.getPlatformContext
|
||||
import example.imageviewer.icon.IconAutoFixHigh
|
||||
import example.imageviewer.isShareFeatureSupported
|
||||
import example.imageviewer.model.*
|
||||
import example.imageviewer.shareIcon
|
||||
@@ -182,9 +183,12 @@ private fun MemoryHeader(bitmap: ImageBitmap, picture: PictureData, onClick: ()
|
||||
@Composable
|
||||
internal fun BoxScope.MagicButtonOverlay(onClick: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(end = 12.dp, bottom = 16.dp)
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(12.dp)
|
||||
) {
|
||||
CircularButton(painterResource("magic.png"), onClick = onClick)
|
||||
CircularButton(
|
||||
imageVector = IconAutoFixHigh,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.with
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import example.imageviewer.LocalImageProvider
|
||||
import example.imageviewer.model.PictureData
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
internal fun PreviewImage(
|
||||
picture: PictureData,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val imageProvider = LocalImageProvider.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
Box(
|
||||
Modifier.fillMaxWidth().height(393.dp).background(Color.Black),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clickable(interactionSource, indication = null, onClick = onClick),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = picture,
|
||||
transitionSpec = {
|
||||
slideIntoContainer(
|
||||
towards = AnimatedContentScope.SlideDirection.Left,
|
||||
animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
|
||||
) with slideOutOfContainer(
|
||||
towards = AnimatedContentScope.SlideDirection.Left,
|
||||
animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
|
||||
)
|
||||
}
|
||||
) { currentPicture ->
|
||||
var image: ImageBitmap? by remember(currentPicture) { mutableStateOf(null) }
|
||||
LaunchedEffect(currentPicture) {
|
||||
image = imageProvider.getImage(currentPicture)
|
||||
}
|
||||
if (image != null) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Image(
|
||||
bitmap = image!!,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
MemoryTextOverlay(currentPicture)
|
||||
}
|
||||
} else {
|
||||
Spacer(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,30 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import example.imageviewer.icon.IconMoreVert
|
||||
import example.imageviewer.model.PictureData
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
@Composable
|
||||
internal fun Thumbnail(
|
||||
picture: PictureData,
|
||||
@@ -53,14 +58,14 @@ internal fun Thumbnail(
|
||||
style = MaterialTheme.typography.subtitle1
|
||||
)
|
||||
|
||||
Image(
|
||||
painterResource("dots.png"),
|
||||
contentDescription = null,
|
||||
Icon(
|
||||
imageVector = IconMoreVert,
|
||||
contentDescription = "more info",
|
||||
modifier = Modifier.height(70.dp)
|
||||
.width(30.dp)
|
||||
.padding(start = 1.dp, top = 25.dp, end = 1.dp, bottom = 25.dp)
|
||||
.clickable { onClickInfo() },
|
||||
contentScale = ContentScale.FillHeight
|
||||
tint = Color.DarkGray
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
@@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import example.imageviewer.*
|
||||
import example.imageviewer.icon.IconPhotoCamera
|
||||
import example.imageviewer.model.PictureData
|
||||
import example.imageviewer.model.createCameraPictureData
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
@@ -51,7 +52,10 @@ internal actual fun CameraView(
|
||||
.padding(20.dp)
|
||||
)
|
||||
val nameAndDescription = createNewPhotoNameAndDescription()
|
||||
Button(onClick = {
|
||||
CircularButton(
|
||||
imageVector = IconPhotoCamera,
|
||||
modifier = Modifier.align(Alignment.BottomCenter).padding(36.dp),
|
||||
) {
|
||||
onCapture(
|
||||
createCameraPictureData(
|
||||
name = nameAndDescription.name,
|
||||
@@ -60,8 +64,6 @@ internal actual fun CameraView(
|
||||
),
|
||||
DesktopStorableImage(imageBitmap)
|
||||
)
|
||||
}, Modifier.align(Alignment.BottomCenter)) {
|
||||
Text(LocalLocalization.current.takePhoto)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,9 @@ import java.awt.Toolkit
|
||||
|
||||
class ExternalNavigationEventBus {
|
||||
private val _events = MutableSharedFlow<ExternalImageViewerEvent>(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_LATEST
|
||||
replay = 0,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
extraBufferCapacity = 1,
|
||||
)
|
||||
val events = _events.asSharedFlow()
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package example.imageviewer
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
// TODO Copied from material3, because "material:material-icons-extended" not working on iOS for now
|
||||
val IosShareIcon: ImageVector =
|
||||
materialIcon(name = "Filled.IosShare") {
|
||||
materialPath {
|
||||
moveTo(16.0f, 5.0f)
|
||||
lineToRelative(-1.42f, 1.42f)
|
||||
lineToRelative(-1.59f, -1.59f)
|
||||
lineTo(12.99f, 16.0f)
|
||||
horizontalLineToRelative(-1.98f)
|
||||
lineTo(11.01f, 4.83f)
|
||||
lineTo(9.42f, 6.42f)
|
||||
lineTo(8.0f, 5.0f)
|
||||
lineToRelative(4.0f, -4.0f)
|
||||
lineToRelative(4.0f, 4.0f)
|
||||
close()
|
||||
moveTo(20.0f, 10.0f)
|
||||
verticalLineToRelative(11.0f)
|
||||
curveToRelative(0.0f, 1.1f, -0.9f, 2.0f, -2.0f, 2.0f)
|
||||
lineTo(6.0f, 23.0f)
|
||||
curveToRelative(-1.11f, 0.0f, -2.0f, -0.9f, -2.0f, -2.0f)
|
||||
lineTo(4.0f, 10.0f)
|
||||
curveToRelative(0.0f, -1.11f, 0.89f, -2.0f, 2.0f, -2.0f)
|
||||
horizontalLineToRelative(3.0f)
|
||||
verticalLineToRelative(2.0f)
|
||||
lineTo(6.0f, 10.0f)
|
||||
verticalLineToRelative(11.0f)
|
||||
horizontalLineToRelative(12.0f)
|
||||
lineTo(18.0f, 10.0f)
|
||||
horizontalLineToRelative(-3.0f)
|
||||
lineTo(15.0f, 8.0f)
|
||||
horizontalLineToRelative(3.0f)
|
||||
curveToRelative(1.1f, 0.0f, 2.0f, 0.89f, 2.0f, 2.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import example.imageviewer.icon.IconIosShare
|
||||
import kotlinx.cinterop.useContents
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
@@ -48,4 +49,4 @@ actual val ioDispatcher = Dispatchers.IO
|
||||
|
||||
actual val isShareFeatureSupported: Boolean = true
|
||||
|
||||
actual val shareIcon: ImageVector = IosShareIcon
|
||||
actual val shareIcon: ImageVector = IconIosShare
|
||||
|
||||
@@ -2,7 +2,6 @@ package example.imageviewer.view
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
@@ -12,9 +11,9 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.interop.UIKitView
|
||||
import androidx.compose.ui.unit.dp
|
||||
import example.imageviewer.IosStorableImage
|
||||
import example.imageviewer.LocalLocalization
|
||||
import example.imageviewer.PlatformStorableImage
|
||||
import example.imageviewer.createNewPhotoNameAndDescription
|
||||
import example.imageviewer.icon.IconPhotoCamera
|
||||
import example.imageviewer.model.GpsPosition
|
||||
import example.imageviewer.model.PictureData
|
||||
import example.imageviewer.model.createCameraPictureData
|
||||
@@ -244,27 +243,25 @@ private fun BoxScope.RealDeviceCamera(
|
||||
CATransaction.commit()
|
||||
},
|
||||
)
|
||||
Button(
|
||||
modifier = Modifier.align(Alignment.BottomCenter).padding(44.dp),
|
||||
CircularButton(
|
||||
imageVector = IconPhotoCamera,
|
||||
modifier = Modifier.align(Alignment.BottomCenter).padding(36.dp),
|
||||
enabled = !capturePhotoStarted,
|
||||
onClick = {
|
||||
capturePhotoStarted = true
|
||||
val photoSettings = AVCapturePhotoSettings.photoSettingsWithFormat(
|
||||
format = mapOf(AVVideoCodecKey to AVVideoCodecTypeJPEG)
|
||||
)
|
||||
if (camera.position == AVCaptureDevicePositionFront) {
|
||||
capturePhotoOutput.connectionWithMediaType(AVMediaTypeVideo)
|
||||
?.automaticallyAdjustsVideoMirroring = false
|
||||
capturePhotoOutput.connectionWithMediaType(AVMediaTypeVideo)
|
||||
?.videoMirrored = true
|
||||
}
|
||||
capturePhotoOutput.capturePhotoWithSettings(
|
||||
settings = photoSettings,
|
||||
delegate = photoCaptureDelegate
|
||||
)
|
||||
}
|
||||
) {
|
||||
Text(LocalLocalization.current.takePhoto)
|
||||
capturePhotoStarted = true
|
||||
val photoSettings = AVCapturePhotoSettings.photoSettingsWithFormat(
|
||||
format = mapOf(AVVideoCodecKey to AVVideoCodecTypeJPEG)
|
||||
)
|
||||
if (camera.position == AVCaptureDevicePositionFront) {
|
||||
capturePhotoOutput.connectionWithMediaType(AVMediaTypeVideo)
|
||||
?.automaticallyAdjustsVideoMirroring = false
|
||||
capturePhotoOutput.connectionWithMediaType(AVMediaTypeVideo)
|
||||
?.videoMirrored = true
|
||||
}
|
||||
capturePhotoOutput.capturePhotoWithSettings(
|
||||
settings = photoSettings,
|
||||
delegate = photoCaptureDelegate
|
||||
)
|
||||
}
|
||||
if (capturePhotoStarted) {
|
||||
CircularProgressIndicator(
|
||||
|
||||