diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index ab3637138..15c63a565 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -30,8 +30,10 @@ 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.WindowInsets -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding @@ -40,13 +42,17 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -70,6 +76,7 @@ import androidx.compose.ui.window.DialogProperties import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.Nip96MediaServers +import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.components.VideoView import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -80,6 +87,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size5dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.FileServersEvent import kotlinx.collections.immutable.toImmutableList @@ -87,6 +95,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3Api::class) @Composable fun NewMediaView( uri: Uri, @@ -121,9 +130,66 @@ fun NewMediaView( decorFitsSystemWindows = false, ), ) { - Surface( - modifier = Modifier.fillMaxWidth(), - ) { + SetDialogToEdgeToEdge() + Scaffold( + topBar = { + TopAppBar( + title = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(modifier = StdHorzSpacer) + + Box { + IconButton( + modifier = Modifier.align(Alignment.Center), + onClick = { showRelaysDialog = true }, + ) { + Icon( + painter = painterResource(R.drawable.relays), + contentDescription = null, + modifier = Modifier.height(25.dp), + tint = MaterialTheme.colorScheme.onBackground, + ) + } + } + + PostButton( + onPost = { + onClose() + postViewModel.upload(context, relayList, mediaQualitySlider) { + accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it) + } + postViewModel.selectedServer?.let { + if (!it.isNip95) { + account.settings.changeDefaultFileServer(it.server) + } + } + }, + isActive = postViewModel.canPost(), + ) + } + }, + navigationIcon = { + Row { + Spacer(modifier = StdHorzSpacer) + CloseButton( + onPress = { + postViewModel.cancel() + onClose() + }, + ) + } + }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + ) + }, + ) { pad -> if (showRelaysDialog) { RelaySelectionDialog( preSelectedList = relayList, @@ -134,61 +200,21 @@ fun NewMediaView( ) } - Column( + Surface( modifier = Modifier - .padding(start = 10.dp, end = 10.dp, top = 10.dp) - .fillMaxWidth() - .fillMaxHeight() + .padding(pad) + .consumeWindowInsets(pad) .imePadding(), ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - CloseButton( - onPress = { - postViewModel.cancel() - onClose() - }, - ) - - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showRelaysDialog = true }, - ) { - Icon( - painter = painterResource(R.drawable.relays), - contentDescription = null, - modifier = Modifier.height(25.dp), - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } - - PostButton( - onPost = { - onClose() - postViewModel.upload(context, relayList, mediaQualitySlider) { - accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it) - } - postViewModel.selectedServer?.let { - if (!it.isNip95) { - account.settings.changeDefaultFileServer(it.server) - } - } - }, - isActive = postViewModel.canPost(), - ) - } - - Row( - modifier = Modifier.fillMaxWidth().weight(1f), + Column( + modifier = + Modifier + .fillMaxSize() + .padding(start = 10.dp, end = 10.dp, bottom = 10.dp), ) { Column( - modifier = Modifier.fillMaxWidth().verticalScroll(scrollState), + modifier = Modifier.fillMaxWidth().weight(1f).verticalScroll(scrollState), ) { ImageVideoPost(postViewModel, accountViewModel) @@ -197,7 +223,6 @@ fun NewMediaView( modifier = Modifier .fillMaxWidth() - .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) .padding(vertical = 8.dp), ) { Column( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SetDialogToEdgeToEdge.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SetDialogToEdgeToEdge.kt new file mode 100644 index 000000000..9ce8e80b1 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SetDialogToEdgeToEdge.kt @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.components + +import android.view.View +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.DialogWindowProvider + +@Composable +fun SetDialogToEdgeToEdge() { + val activityWindow = getActivityWindow() + val dialogWindow = (LocalView.current.parent as? DialogWindowProvider)?.window + val parentView = LocalView.current.parent as View + SideEffect { + if (activityWindow != null && dialogWindow != null) { + val attributes = WindowManager.LayoutParams() + attributes.copyFrom(activityWindow.attributes) + attributes.type = dialogWindow.attributes.type + dialogWindow.attributes = attributes + parentView.layoutParams = + FrameLayout.LayoutParams( + activityWindow.decorView.width, + activityWindow.decorView.height, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/WindowUtils.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/WindowUtils.kt new file mode 100644 index 000000000..15ea0e454 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/WindowUtils.kt @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.components + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.view.Window +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.DialogWindowProvider +import com.vitorpamplona.amethyst.ui.navigation.getActivity + +// Window utils +@Composable +fun getDialogWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window + +@Composable +fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow() + +private tailrec fun Context.getActivityWindow(): Window? = + when (this) { + is Activity -> window + is ContextWrapper -> baseContext.getActivityWindow() + else -> null + } + +@Composable +fun getActivity(): Activity? = LocalView.current.context.getActivity() + +private tailrec fun Context.getActivity(): Activity? = + when (this) { + is Activity -> this + is ContextWrapper -> baseContext.getActivity() + else -> null + } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 2911754e8..06726914d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -20,11 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.components -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper import android.util.Log -import android.view.Window import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -62,7 +58,6 @@ import androidx.compose.ui.text.PlaceholderVerticalAlign import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle -import androidx.compose.ui.window.DialogWindowProvider import androidx.core.net.toUri import coil.annotation.ExperimentalCoilApi import coil.compose.AsyncImage @@ -820,25 +815,3 @@ private fun HashVerificationSymbol(verifiedHash: Boolean) { } } } - -// Window utils -@Composable -fun getDialogWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window - -@Composable fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow() - -private tailrec fun Context.getActivityWindow(): Window? = - when (this) { - is Activity -> window - is ContextWrapper -> baseContext.getActivityWindow() - else -> null - } - -@Composable fun getActivity(): Activity? = LocalView.current.context.getActivity() - -private tailrec fun Context.getActivity(): Activity? = - when (this) { - is Activity -> this - is ContextWrapper -> baseContext.getActivity() - else -> null - }