Browse Source

ImageViewer notch (#2822)

pull/2837/head
dima.avdeev 2 years ago committed by GitHub
parent
commit
3e610c5fdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      experimental/examples/imageviewer/iosApp/iosApp/ContentView.swift
  2. 6
      experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/plarfom.android.kt
  3. 9
      experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/PreviewImage.android.kt
  4. 5
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt
  5. 5
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/plarfom.common.kt
  6. 29
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/CircularButton.kt
  7. 13
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/FullscreenImage.kt
  8. 55
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt
  9. 58
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
  10. 28
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/PreviewImage.common.kt
  11. 31
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/TopLayout.kt
  12. 5
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/plarfom.desktop.kt
  13. 6
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/PreviewImage.desktop.kt
  14. 29
      experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/platform.ios.kt
  15. 6
      experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/PreviewImage.ios.kt

21
experimental/examples/imageviewer/iosApp/iosApp/ContentView.swift

@ -2,6 +2,17 @@ import UIKit
import SwiftUI import SwiftUI
import shared 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 { struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController { func makeUIViewController(context: Context) -> UIViewController {
Main_iosKt.MainViewController() Main_iosKt.MainViewController()
@ -12,8 +23,14 @@ struct ComposeView: UIViewControllerRepresentable {
struct ContentView: View { struct ContentView: View {
var body: some View { var body: some View {
ComposeView() ZStack {
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler ComposeView()
.ignoresSafeArea(.all) // Compose has own keyboard handler
VStack {
gradient.ignoresSafeArea(edges: .top).frame(height: 0)
Spacer()
}
}.preferredColorScheme(.dark)
} }
} }

6
experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/plarfom.android.kt

@ -0,0 +1,6 @@
package example.imageviewer
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.*
actual fun Modifier.notchPadding(): Modifier = displayCutoutPadding().statusBarsPadding()

9
experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/view/PreviewImage.android.kt

@ -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

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

@ -80,8 +80,9 @@ internal fun ImageViewerCommon(
is MemoryPage -> { is MemoryPage -> {
MemoryScreen( MemoryScreen(
page, memoryPage = page,
photoGallery, photoGallery = photoGallery,
localization = dependencies.localization,
onSelectRelatedMemory = { galleryId -> onSelectRelatedMemory = { galleryId ->
navigationStack.push(MemoryPage(galleryId)) navigationStack.push(MemoryPage(galleryId))
}, },

5
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/plarfom.common.kt

@ -0,0 +1,5 @@
package example.imageviewer
import androidx.compose.ui.Modifier
expect fun Modifier.notchPadding(): Modifier

29
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/CircularButton.kt

@ -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)
)
}
}

13
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/FullscreenImage.kt

@ -140,16 +140,8 @@ private fun FullscreenImageBar(
selectedFilters: Set<FilterType>, selectedFilters: Set<FilterType>,
onSelectFilter: (FilterType) -> Unit onSelectFilter: (FilterType) -> Unit
) { ) {
TopAppBar( TopLayout(
modifier = Modifier.background(color = ImageviewerColors.fullScreenImageBackground), alignLeftContent = {
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = ImageviewerColors.Transparent,
titleContentColor = MaterialTheme.colorScheme.onBackground
),
title = {
Text("${localization.picture} ${pictureName ?: "Unknown"}")
},
navigationIcon = {
Tooltip(localization.back) { Tooltip(localization.back) {
CircularButton( CircularButton(
painterResource("arrowleft.png"), painterResource("arrowleft.png"),
@ -157,6 +149,7 @@ private fun FullscreenImageBar(
) )
} }
}, },
alignRightContent = {},
) )
} }

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

@ -1,19 +1,11 @@
@file:OptIn(ExperimentalResourceApi::class)
package example.imageviewer.view package example.imageviewer.view
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
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.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed 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.GalleryPage
import example.imageviewer.model.PhotoGallery import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.bigUrl import example.imageviewer.model.bigUrl
import example.imageviewer.notchPadding
import example.imageviewer.style.ImageviewerColors import example.imageviewer.style.ImageviewerColors
import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
@ -69,17 +62,19 @@ internal fun GalleryScreen(
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Box { Box {
if (needShowPreview()) { PreviewImage(
PreviewImage( getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) }, picture = galleryPage.picture, onClick = {
picture = galleryPage.picture, onClick = { galleryPage.pictureId?.let(onClickPreviewPicture)
galleryPage.pictureId?.let(onClickPreviewPicture) }
}) )
} TopLayout(
TitleBar( alignLeftContent = {},
onRefresh = { photoGallery.updatePictures() }, alignRightContent = {
onToggle = { galleryPage.toggleGalleryStyle() }, CircularButton(painterResource("list_view.png")) {
dependencies galleryPage.toggleGalleryStyle()
}
},
) )
} }
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { 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() }
}
})
}

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

@ -3,25 +3,17 @@ package example.imageviewer.view
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring import androidx.compose.animation.core.Spring
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.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.*
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.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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.ImageBitmap
import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import example.imageviewer.Localization
import example.imageviewer.model.GalleryEntryWithMetadata import example.imageviewer.model.GalleryEntryWithMetadata
import example.imageviewer.model.GalleryId import example.imageviewer.model.GalleryId
import example.imageviewer.model.MemoryPage import example.imageviewer.model.MemoryPage
@ -49,6 +41,7 @@ import org.jetbrains.compose.resources.painterResource
internal fun MemoryScreen( internal fun MemoryScreen(
memoryPage: MemoryPage, memoryPage: MemoryPage,
photoGallery: PhotoGallery, photoGallery: PhotoGallery,
localization: Localization,
onSelectRelatedMemory: (GalleryId) -> Unit, onSelectRelatedMemory: (GalleryId) -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
onHeaderClick: (GalleryId) -> Unit onHeaderClick: (GalleryId) -> Unit
@ -116,39 +109,20 @@ internal fun MemoryScreen(
} }
} }
} }
TopAppBar( TopLayout(
modifier = Modifier.padding(start = 12.dp, end = 12.dp), alignLeftContent = {
colors = TopAppBarDefaults.smallTopAppBarColors( Tooltip(localization.back) {
containerColor = ImageviewerColors.Transparent, CircularButton(
titleContentColor = MaterialTheme.colorScheme.onBackground painterResource("arrowleft.png"),
), onClick = { onBack() }
title = { )
Text("")
},
navigationIcon = {
Tooltip("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 @Composable
private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) { private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }

28
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/PreviewImage.common.kt

@ -8,6 +8,7 @@ import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.with import androidx.compose.animation.with
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -22,6 +23,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -34,19 +36,13 @@ internal fun PreviewImage(
onClick: () -> Unit, onClick: () -> Unit,
getImage: suspend (Picture) -> ImageBitmap getImage: suspend (Picture) -> ImageBitmap
) { ) {
var image by remember(picture) { mutableStateOf<ImageBitmap?>(null) } Box(Modifier.fillMaxWidth().height(393.dp).background(Color.Black), contentAlignment = Alignment.Center) {
LaunchedEffect(picture) {
if (picture != null) {
image = getImage(picture)
}
}
Box(Modifier.fillMaxWidth().height(393.dp), contentAlignment = Alignment.Center) {
Box( Box(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
.clickable { onClick() }, .clickable { onClick() },
) { ) {
AnimatedContent( AnimatedContent(
targetState = image, targetState = picture,
transitionSpec = { transitionSpec = {
slideInHorizontally( slideInHorizontally(
initialOffsetX = { it }, animationSpec = spring( initialOffsetX = { it }, animationSpec = spring(
@ -61,10 +57,16 @@ internal fun PreviewImage(
) )
// slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it }) // slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it })
} }
) { imageBitmap -> ) { currentPicture ->
if (imageBitmap != null) { var image by remember(currentPicture) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(currentPicture) {
if (currentPicture != null) {
image = getImage(currentPicture)
}
}
if (image != null) {
Image( Image(
bitmap = imageBitmap, bitmap = image!!,
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.fillMaxSize(), .fillMaxSize(),
@ -80,8 +82,4 @@ internal fun PreviewImage(
} }
} }
} }
@Composable
internal expect fun needShowPreview(): Boolean

31
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/TopLayout.kt

@ -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()
}
}
}

5
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/plarfom.desktop.kt

@ -0,0 +1,5 @@
package example.imageviewer
import androidx.compose.ui.Modifier
actual fun Modifier.notchPadding(): Modifier = this

6
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/PreviewImage.desktop.kt

@ -1,6 +0,0 @@
package example.imageviewer.view
import androidx.compose.runtime.Composable
@Composable
internal actual fun needShowPreview(): Boolean = true

29
experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/platform.ios.kt

@ -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)

6
experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/view/PreviewImage.ios.kt

@ -1,6 +0,0 @@
package example.imageviewer.view
import androidx.compose.runtime.Composable
@Composable
internal actual fun needShowPreview(): Boolean = true
Loading…
Cancel
Save