Merge branch 'main' into refactor-media-compressor

This commit is contained in:
David Kaspar 2024-10-08 14:51:53 +01:00 committed by GitHub
commit ce0c7c842d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 312 additions and 155 deletions

1
.gitignore vendored
View File

@ -13,6 +13,7 @@
/.idea/ktfmt.xml
/.idea/studiobot.xml
/.idea/other.xml
/.idea/runConfigurations.xml
.DS_Store
/build
/captures

View File

@ -126,6 +126,7 @@ fun ZoomableContentView(
val isLandscapeMode = DeviceUtils.isLandscapeMetric(LocalContext.current)
val isFoldableOrLarge = DeviceUtils.windowIsLarge(windowSize = currentWindowSize, isInLandscapeMode = isLandscapeMode)
val isOrientationLocked = DeviceUtils.screenOrientationIsLocked(LocalContext.current)
val contentScale =
if (isFiniteHeight) {
@ -160,9 +161,9 @@ fun ZoomableContentView(
nostrUriCallback = content.uri,
onDialog = {
dialogOpen = true
// if (!isFoldableOrLarge) {
// DeviceUtils.changeDeviceOrientation(isLandscapeMode, activity)
// }
if (!isFoldableOrLarge && !isOrientationLocked) {
DeviceUtils.changeDeviceOrientation(isLandscapeMode, activity)
}
},
accountViewModel = accountViewModel,
)
@ -200,7 +201,7 @@ fun ZoomableContentView(
images,
onDismiss = {
dialogOpen = false
// if (!isFoldableOrLarge) DeviceUtils.changeDeviceOrientation(isLandscapeMode, activity)
if (!isFoldableOrLarge && !isOrientationLocked) DeviceUtils.changeDeviceOrientation(isLandscapeMode, activity)
},
accountViewModel,
)

View File

@ -23,6 +23,8 @@ package com.vitorpamplona.amethyst.ui.components.util
import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.provider.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.window.core.layout.WindowHeightSizeClass
@ -38,6 +40,31 @@ object DeviceUtils {
*/
fun isLandscapeMetric(context: Context): Boolean = context.resources.displayMetrics.heightPixels < context.resources.displayMetrics.widthPixels
/**
* Checks if the device's orientation is set to locked.
*
* Credits: NewPipe devs
*/
fun screenOrientationIsLocked(context: Context): Boolean {
// 1: Screen orientation changes using accelerometer
// 0: Screen orientation is locked
// if the accelerometer sensor is missing completely, assume locked orientation
return (
Settings.System.getInt(
context.contentResolver,
Settings.System.ACCELEROMETER_ROTATION,
0,
) == 0 ||
!context.packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)
)
}
/**
* Changes the device's orientation. This works even if the device's orientation
* is set to locked.
* Thus, to prevent unwanted behaviour,
* it's use can be guarded by conditions such as [screenOrientationIsLocked].
*/
fun changeDeviceOrientation(
isInLandscape: Boolean,
currentActivity: Activity,

View File

@ -24,8 +24,10 @@ import android.content.Context
import android.content.Intent
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
@ -89,6 +91,7 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
@ -146,6 +149,7 @@ import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@ -1325,23 +1329,52 @@ fun ReactionChoicePopup(
.collectAsStateWithLifecycle()
val toRemove = remember { baseNote.reactedBy(accountViewModel.userProfile()).toImmutableSet() }
// Define animation specs
val animationDuration = 250
val fadeAnimationSpec = tween<Float>(durationMillis = animationDuration)
// Prevent multiple calls to onDismiss()
var dismissed by remember { mutableStateOf(false) }
val visibilityState = remember { MutableTransitionState(false).apply { targetState = true } }
LaunchedEffect(visibilityState.targetState) {
if (!visibilityState.targetState && !dismissed) {
delay(animationDuration.toLong())
dismissed = true
onDismiss()
}
}
Popup(
alignment = Alignment.BottomCenter,
offset = IntOffset(0, iconSizePx),
onDismissRequest = { onDismiss() },
onDismissRequest = { visibilityState.targetState = false },
properties = PopupProperties(focusable = true),
) {
ReactionChoicePopupContent(
reactions,
toRemove = toRemove,
onClick = { reactionType ->
accountViewModel.reactToOrDelete(
baseNote,
reactionType,
)
onDismiss()
},
onChangeAmount,
)
AnimatedVisibility(
visibleState = visibilityState,
enter =
slideInVertically(
initialOffsetY = { it / 2 },
) + fadeIn(animationSpec = fadeAnimationSpec),
exit =
slideOutVertically(
targetOffsetY = { it / 2 },
) + fadeOut(animationSpec = fadeAnimationSpec),
) {
ReactionChoicePopupContent(
reactions,
toRemove = toRemove,
onClick = { reactionType ->
accountViewModel.reactToOrDelete(
baseNote,
reactionType,
)
visibilityState.targetState = false
},
onChangeAmount,
)
}
}
}
@ -1503,58 +1536,87 @@ fun ZapAmountChoicePopup(
val yOffset = with(LocalDensity.current) { -popupYOffset.toPx().toInt() }
// Define animation specs
val animationDuration = 250
val fadeAnimationSpec = tween<Float>(durationMillis = animationDuration)
// Prevent multiple calls to onDismiss()
var dismissed by remember { mutableStateOf(false) }
val visibilityState = remember { MutableTransitionState(false).apply { targetState = true } }
LaunchedEffect(visibilityState.targetState) {
if (!visibilityState.targetState && !dismissed) {
delay(animationDuration.toLong())
dismissed = true
onDismiss()
}
}
Popup(
alignment = Alignment.BottomCenter,
offset = IntOffset(0, yOffset),
onDismissRequest = { onDismiss() },
onDismissRequest = { visibilityState.targetState = false },
properties = PopupProperties(focusable = true),
) {
FlowRow(horizontalArrangement = Arrangement.Center) {
zapAmountChoices.forEach { amountInSats ->
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
accountViewModel.zap(
baseNote,
amountInSats * 1000,
null,
zapMessage,
context,
true,
onError,
onProgress,
onPayViaIntent,
)
onDismiss()
},
shape = ButtonBorder,
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
),
) {
Text(
"${showAmount(amountInSats.toBigDecimal().setScale(1))}",
color = Color.White,
textAlign = TextAlign.Center,
modifier =
Modifier.combinedClickable(
onClick = {
accountViewModel.zap(
baseNote,
amountInSats * 1000,
null,
zapMessage,
context,
true,
onError,
onProgress,
onPayViaIntent,
)
onDismiss()
},
onLongClick = { onChangeAmount() },
AnimatedVisibility(
visibleState = visibilityState,
enter =
slideInVertically(
initialOffsetY = { it / 2 },
) + fadeIn(animationSpec = fadeAnimationSpec),
exit =
slideOutVertically(
targetOffsetY = { it / 2 },
) + fadeOut(animationSpec = fadeAnimationSpec),
) {
FlowRow(horizontalArrangement = Arrangement.Center) {
zapAmountChoices.forEach { amountInSats ->
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
accountViewModel.zap(
baseNote,
amountInSats * 1000,
null,
zapMessage,
context,
true,
onError,
onProgress,
onPayViaIntent,
)
visibilityState.targetState = false
},
shape = ButtonBorder,
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
),
)
) {
Text(
"${showAmount(amountInSats.toBigDecimal().setScale(1))}",
color = Color.White,
textAlign = TextAlign.Center,
modifier =
Modifier.combinedClickable(
onClick = {
accountViewModel.zap(
baseNote,
amountInSats * 1000,
null,
zapMessage,
context,
true,
onError,
onProgress,
onPayViaIntent,
)
visibilityState.targetState = false
},
onLongClick = { onChangeAmount() },
),
)
}
}
}
}

View File

@ -109,7 +109,12 @@ class AccountStateViewModel : ViewModel() {
is Nip19Bech32.NEmbed -> null
is Nip19Bech32.NRelay -> null
is Nip19Bech32.NAddress -> null
else -> null
else ->
try {
if (loginWithExternalSigner) Hex.decode(key) else null
} catch (e: Exception) {
null
}
}
if (loginWithExternalSigner && pubKeyParsed == null) {

View File

@ -20,6 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
@ -38,6 +44,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
@ -76,46 +83,56 @@ fun ChannelFabColumn(
}
Column {
if (isOpen) {
FloatingActionButton(
onClick = {
wantsToSendNewMessage = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Text(
text = stringRes(R.string.messages_new_message),
color = Color.White,
textAlign = TextAlign.Center,
fontSize = Font12SP,
)
AnimatedVisibility(
visible = isOpen,
enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
) {
Column {
FloatingActionButton(
onClick = {
wantsToSendNewMessage = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Text(
text = stringRes(R.string.messages_new_message),
color = Color.White,
textAlign = TextAlign.Center,
fontSize = Font12SP,
)
}
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToCreateChannel = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Text(
text = stringRes(R.string.messages_create_public_chat),
color = Color.White,
textAlign = TextAlign.Center,
fontSize = Font12SP,
)
}
Spacer(modifier = Modifier.height(20.dp))
}
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToCreateChannel = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Text(
text = stringRes(R.string.messages_create_public_chat),
color = Color.White,
textAlign = TextAlign.Center,
fontSize = Font12SP,
)
}
Spacer(modifier = Modifier.height(20.dp))
}
val rotationDegree by animateFloatAsState(
targetValue = if (isOpen) 45f else 0f,
)
FloatingActionButton(
onClick = { isOpen = !isOpen },
modifier = Size55Modifier,
@ -125,7 +142,10 @@ fun ChannelFabColumn(
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = stringRes(R.string.messages_create_public_private_chat_description),
modifier = Modifier.size(26.dp),
modifier =
Modifier.size(26.dp).graphicsLayer {
rotationZ = rotationDegree
},
tint = Color.White,
)
}

View File

@ -20,6 +20,11 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
@ -73,7 +78,11 @@ fun SummaryBar(state: NotificationSummaryState) {
UserReactionsRow(state) { showChart = !showChart }
if (showChart) {
AnimatedVisibility(
visible = showChart,
enter = slideInVertically() + expandVertically(),
exit = slideOutVertically() + shrinkVertically(),
) {
val lineChartCount =
lineChart(
lines =

View File

@ -25,7 +25,12 @@ import android.net.Uri
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -36,6 +41,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddPhotoAlternate
import androidx.compose.material.icons.filled.CameraAlt
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
@ -186,44 +192,51 @@ fun NewImageButton(
ShowProgress(postViewModel)
} else {
Column {
if (isOpen) {
FloatingActionButton(
onClick = {
wantsToPostFromCamera = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
imageVector = Icons.Default.CameraAlt,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
// if (isOpen) {
AnimatedVisibility(
visible = isOpen,
enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
) {
Column {
FloatingActionButton(
onClick = {
wantsToPostFromCamera = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
imageVector = Icons.Default.CameraAlt,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToPostFromGallery = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
imageVector = Icons.Default.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
}
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToPostFromGallery = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
imageVector = Icons.Default.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
}
FloatingActionButton(
@ -234,12 +247,31 @@ fun NewImageButton(
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
painter = painterResource(R.drawable.ic_compose),
contentDescription = stringRes(id = R.string.new_short),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
AnimatedVisibility(
visible = isOpen,
enter = fadeIn(),
exit = fadeOut(),
) {
Icon(
imageVector = Icons.Outlined.Close,
contentDescription = stringRes(id = R.string.new_short),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
AnimatedVisibility(
visible = !isOpen,
enter = fadeIn(),
exit = fadeOut(),
) {
Icon(
painter = painterResource(R.drawable.ic_compose),
contentDescription = stringRes(id = R.string.new_short),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
}
}
}

View File

@ -1,7 +1,7 @@
[versions]
accompanistAdaptive = "0.34.0"
activityCompose = "1.9.2"
agp = "8.6.1"
agp = "8.7.0"
android-compileSdk = "34"
android-minSdk = "26"
android-targetSdk = "34"
@ -9,16 +9,16 @@ androidKotlinGeohash = "1.0"
androidxJunit = "1.2.1"
appcompat = "1.7.0"
audiowaveform = "1.1.1"
benchmark = "1.3.1"
benchmarkJunit4 = "1.3.1"
benchmark = "1.3.2"
benchmarkJunit4 = "1.3.2"
biometricKtx = "1.2.0-alpha05"
blurhash = "1.0.0"
coil = "2.7.0"
composeBom = "2024.09.02"
composeBom = "2024.09.03"
coreKtx = "1.13.1"
espressoCore = "3.6.1"
firebaseBom = "33.3.0"
fragmentKtx = "1.8.3"
firebaseBom = "33.4.0"
fragmentKtx = "1.8.4"
gms = "4.4.2"
jacksonModuleKotlin = "2.17.2"
jna = "5.14.0"
@ -36,7 +36,7 @@ markdown = "077a2cde64"
media3 = "1.4.1"
mockk = "1.13.12"
kotlinx-coroutines-test = "1.9.0-RC.2"
navigationCompose = "2.8.1"
navigationCompose = "2.8.2"
okhttp = "5.0.0-alpha.14"
runner = "1.6.2"
rfc3986 = "0.1.0"

View File

@ -1,6 +1,6 @@
#Wed Jan 04 09:23:50 EST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME