mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-03-10 08:11:20 +00:00
ImageViewer notch (#2822)
This commit is contained in:
@@ -2,6 +2,17 @@ import UIKit
|
||||
import SwiftUI
|
||||
import shared
|
||||
|
||||
let gradient = LinearGradient(
|
||||
colors: [
|
||||
Color.black.opacity(0.6),
|
||||
Color.black.opacity(0.6),
|
||||
Color.black.opacity(0.5),
|
||||
Color.black.opacity(0.3),
|
||||
Color.black.opacity(0.0),
|
||||
],
|
||||
startPoint: .top, endPoint: .bottom
|
||||
)
|
||||
|
||||
struct ComposeView: UIViewControllerRepresentable {
|
||||
func makeUIViewController(context: Context) -> UIViewController {
|
||||
Main_iosKt.MainViewController()
|
||||
@@ -12,8 +23,14 @@ struct ComposeView: UIViewControllerRepresentable {
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
ComposeView()
|
||||
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler
|
||||
ZStack {
|
||||
ComposeView()
|
||||
.ignoresSafeArea(.all) // Compose has own keyboard handler
|
||||
VStack {
|
||||
gradient.ignoresSafeArea(edges: .top).frame(height: 0)
|
||||
Spacer()
|
||||
}
|
||||
}.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package example.imageviewer
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
||||
actual fun Modifier.notchPadding(): Modifier = displayCutoutPadding().statusBarsPadding()
|
||||
@@ -1,9 +0,0 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
|
||||
@Composable
|
||||
internal actual fun needShowPreview(): Boolean =
|
||||
LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
@@ -80,8 +80,9 @@ internal fun ImageViewerCommon(
|
||||
|
||||
is MemoryPage -> {
|
||||
MemoryScreen(
|
||||
page,
|
||||
photoGallery,
|
||||
memoryPage = page,
|
||||
photoGallery = photoGallery,
|
||||
localization = dependencies.localization,
|
||||
onSelectRelatedMemory = { galleryId ->
|
||||
navigationStack.push(MemoryPage(galleryId))
|
||||
},
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package example.imageviewer
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
expect fun Modifier.notchPadding(): Modifier
|
||||
@@ -0,0 +1,29 @@
|
||||
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.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.unit.dp
|
||||
import example.imageviewer.style.ImageviewerColors
|
||||
|
||||
@Composable
|
||||
internal fun CircularButton(image: Painter, onClick: () -> Unit) {
|
||||
Box(
|
||||
Modifier.size(40.dp).clip(CircleShape).background(ImageviewerColors.uiLightBlack)
|
||||
.clickable { onClick() }, contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
image,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -140,16 +140,8 @@ private fun FullscreenImageBar(
|
||||
selectedFilters: Set<FilterType>,
|
||||
onSelectFilter: (FilterType) -> Unit
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = Modifier.background(color = ImageviewerColors.fullScreenImageBackground),
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||
containerColor = ImageviewerColors.Transparent,
|
||||
titleContentColor = MaterialTheme.colorScheme.onBackground
|
||||
),
|
||||
title = {
|
||||
Text("${localization.picture} ${pictureName ?: "Unknown"}")
|
||||
},
|
||||
navigationIcon = {
|
||||
TopLayout(
|
||||
alignLeftContent = {
|
||||
Tooltip(localization.back) {
|
||||
CircularButton(
|
||||
painterResource("arrowleft.png"),
|
||||
@@ -157,6 +149,7 @@ private fun FullscreenImageBar(
|
||||
)
|
||||
}
|
||||
},
|
||||
alignRightContent = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
@file:OptIn(ExperimentalResourceApi::class)
|
||||
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
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
|
||||
@@ -39,6 +31,7 @@ import example.imageviewer.model.GalleryId
|
||||
import example.imageviewer.model.GalleryPage
|
||||
import example.imageviewer.model.PhotoGallery
|
||||
import example.imageviewer.model.bigUrl
|
||||
import example.imageviewer.notchPadding
|
||||
import example.imageviewer.style.ImageviewerColors
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
@@ -69,17 +62,19 @@ internal fun GalleryScreen(
|
||||
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||
Box {
|
||||
if (needShowPreview()) {
|
||||
PreviewImage(
|
||||
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
|
||||
picture = galleryPage.picture, onClick = {
|
||||
galleryPage.pictureId?.let(onClickPreviewPicture)
|
||||
})
|
||||
}
|
||||
TitleBar(
|
||||
onRefresh = { photoGallery.updatePictures() },
|
||||
onToggle = { galleryPage.toggleGalleryStyle() },
|
||||
dependencies
|
||||
PreviewImage(
|
||||
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
|
||||
picture = galleryPage.picture, onClick = {
|
||||
galleryPage.pictureId?.let(onClickPreviewPicture)
|
||||
}
|
||||
)
|
||||
TopLayout(
|
||||
alignLeftContent = {},
|
||||
alignRightContent = {
|
||||
CircularButton(painterResource("list_view.png")) {
|
||||
galleryPage.toggleGalleryStyle()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
|
||||
@@ -230,19 +225,3 @@ private fun ListGalleryView(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TitleBar(onRefresh: () -> Unit, onToggle: () -> Unit, dependencies: Dependencies) {
|
||||
TopAppBar(
|
||||
modifier = Modifier.padding(start = 12.dp, end = 12.dp),
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||
containerColor = ImageviewerColors.Transparent,
|
||||
titleContentColor = MaterialTheme.colorScheme.onBackground
|
||||
),
|
||||
title = {
|
||||
Row(Modifier.height(50.dp).fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
CircularButton(painterResource("list_view.png")) { onToggle() }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,25 +3,17 @@ package example.imageviewer.view
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.*
|
||||
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.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -30,12 +22,12 @@ 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.painter.Painter
|
||||
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.Localization
|
||||
import example.imageviewer.model.GalleryEntryWithMetadata
|
||||
import example.imageviewer.model.GalleryId
|
||||
import example.imageviewer.model.MemoryPage
|
||||
@@ -49,6 +41,7 @@ import org.jetbrains.compose.resources.painterResource
|
||||
internal fun MemoryScreen(
|
||||
memoryPage: MemoryPage,
|
||||
photoGallery: PhotoGallery,
|
||||
localization: Localization,
|
||||
onSelectRelatedMemory: (GalleryId) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onHeaderClick: (GalleryId) -> Unit
|
||||
@@ -116,39 +109,20 @@ internal fun MemoryScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
TopAppBar(
|
||||
modifier = Modifier.padding(start = 12.dp, end = 12.dp),
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||
containerColor = ImageviewerColors.Transparent,
|
||||
titleContentColor = MaterialTheme.colorScheme.onBackground
|
||||
),
|
||||
title = {
|
||||
Text("")
|
||||
},
|
||||
navigationIcon = {
|
||||
Tooltip("Back") {
|
||||
CircularButton(painterResource("arrowleft.png"), onClick = { onBack() })
|
||||
TopLayout(
|
||||
alignLeftContent = {
|
||||
Tooltip(localization.back) {
|
||||
CircularButton(
|
||||
painterResource("arrowleft.png"),
|
||||
onClick = { onBack() }
|
||||
)
|
||||
}
|
||||
},
|
||||
alignRightContent = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun CircularButton(image: Painter, onClick: () -> Unit) {
|
||||
Box(
|
||||
Modifier.size(40.dp).clip(CircleShape).background(ImageviewerColors.uiLightBlack)
|
||||
.clickable { onClick() }, contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
image,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.with
|
||||
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.Spacer
|
||||
@@ -22,6 +23,7 @@ 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
|
||||
@@ -34,19 +36,13 @@ internal fun PreviewImage(
|
||||
onClick: () -> Unit,
|
||||
getImage: suspend (Picture) -> ImageBitmap
|
||||
) {
|
||||
var image by remember(picture) { mutableStateOf<ImageBitmap?>(null) }
|
||||
LaunchedEffect(picture) {
|
||||
if (picture != null) {
|
||||
image = getImage(picture)
|
||||
}
|
||||
}
|
||||
Box(Modifier.fillMaxWidth().height(393.dp), contentAlignment = Alignment.Center) {
|
||||
Box(Modifier.fillMaxWidth().height(393.dp).background(Color.Black), contentAlignment = Alignment.Center) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.clickable { onClick() },
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = image,
|
||||
targetState = picture,
|
||||
transitionSpec = {
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { it }, animationSpec = spring(
|
||||
@@ -61,10 +57,16 @@ internal fun PreviewImage(
|
||||
)
|
||||
// slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it })
|
||||
}
|
||||
) { imageBitmap ->
|
||||
if (imageBitmap != null) {
|
||||
) { currentPicture ->
|
||||
var image by remember(currentPicture) { mutableStateOf<ImageBitmap?>(null) }
|
||||
LaunchedEffect(currentPicture) {
|
||||
if (currentPicture != null) {
|
||||
image = getImage(currentPicture)
|
||||
}
|
||||
}
|
||||
if (image != null) {
|
||||
Image(
|
||||
bitmap = imageBitmap,
|
||||
bitmap = image!!,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
@@ -80,8 +82,4 @@ internal fun PreviewImage(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal expect fun needShowPreview(): Boolean
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import example.imageviewer.notchPadding
|
||||
|
||||
@Composable
|
||||
internal fun TopLayout(
|
||||
alignLeftContent: @Composable () -> Unit = {},
|
||||
alignRightContent: @Composable () -> Unit = {},
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.notchPadding()
|
||||
.padding(horizontal = 12.dp)
|
||||
) {
|
||||
Row(Modifier.align(Alignment.CenterStart)) {
|
||||
alignLeftContent()
|
||||
}
|
||||
Row(Modifier.align(Alignment.CenterEnd)) {
|
||||
alignRightContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package example.imageviewer
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
actual fun Modifier.notchPadding(): Modifier = this
|
||||
@@ -1,6 +0,0 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
internal actual fun needShowPreview(): Boolean = true
|
||||
@@ -0,0 +1,29 @@
|
||||
package example.imageviewer
|
||||
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import kotlinx.cinterop.useContents
|
||||
import platform.UIKit.UIApplication
|
||||
import platform.UIKit.safeAreaInsets
|
||||
|
||||
private val iosNotchInset = object : WindowInsets {
|
||||
override fun getTop(density: Density): Int {
|
||||
val safeAreaInsets = UIApplication.sharedApplication.keyWindow?.safeAreaInsets
|
||||
return if (safeAreaInsets != null) {
|
||||
val topInset = safeAreaInsets.useContents { this.top }
|
||||
(topInset * density.density).toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLeft(density: Density, layoutDirection: LayoutDirection): Int = 0
|
||||
override fun getRight(density: Density, layoutDirection: LayoutDirection): Int = 0
|
||||
override fun getBottom(density: Density): Int = 0
|
||||
}
|
||||
|
||||
actual fun Modifier.notchPadding(): Modifier =
|
||||
this.windowInsetsPadding(iosNotchInset)
|
||||
@@ -1,6 +0,0 @@
|
||||
package example.imageviewer.view
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
internal actual fun needShowPreview(): Boolean = true
|
||||
Reference in New Issue
Block a user