Merge branch 'develop' into feature/fga/power_level
This commit is contained in:
commit
1d2fd52ce6
188 changed files with 813 additions and 628 deletions
|
|
@ -76,9 +76,9 @@ fun Context.getApplicationLabel(packageName: String): String {
|
|||
/**
|
||||
* Return true it the user has enabled the do not disturb mode.
|
||||
*/
|
||||
fun isDoNotDisturbModeOn(context: Context): Boolean {
|
||||
fun Context.isDoNotDisturbModeOn(): Boolean {
|
||||
// We cannot use NotificationManagerCompat here.
|
||||
val setting = context.getSystemService<NotificationManager>()!!.currentInterruptionFilter
|
||||
val setting = getSystemService<NotificationManager>()!!.currentInterruptionFilter
|
||||
|
||||
return setting == NotificationManager.INTERRUPTION_FILTER_NONE ||
|
||||
setting == NotificationManager.INTERRUPTION_FILTER_ALARMS
|
||||
|
|
@ -92,10 +92,10 @@ fun isDoNotDisturbModeOn(context: Context): Boolean {
|
|||
* will return false and the notification privacy will fallback to "LOW_DETAIL".
|
||||
*/
|
||||
@SuppressLint("BatteryLife")
|
||||
fun requestDisablingBatteryOptimization(activity: Activity, activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||
fun Context.requestDisablingBatteryOptimization(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||
intent.data = Uri.parse("package:" + activity.packageName)
|
||||
intent.data = Uri.parse("package:$packageName")
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
|
|
@ -106,50 +106,48 @@ fun requestDisablingBatteryOptimization(activity: Activity, activityResultLaunch
|
|||
/**
|
||||
* Copy a text to the clipboard, and display a Toast when done.
|
||||
*
|
||||
* @param context the context
|
||||
* @receiver the context
|
||||
* @param text the text to copy
|
||||
* @param toastMessage content of the toast message as a String resource. Null for no toast
|
||||
*/
|
||||
fun copyToClipboard(
|
||||
context: Context,
|
||||
fun Context.copyToClipboard(
|
||||
text: CharSequence,
|
||||
toastMessage: String? = null
|
||||
) {
|
||||
CopyToClipboardUseCase(context).execute(text)
|
||||
toastMessage?.let { context.toast(it) }
|
||||
CopyToClipboardUseCase(this).execute(text)
|
||||
toastMessage?.let { toast(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows notification settings for the current app.
|
||||
* In android O will directly opens the notification settings, in lower version it will show the App settings
|
||||
*/
|
||||
fun startNotificationSettingsIntent(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||
fun Context.startNotificationSettingsIntent(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||
val intent = Intent()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
|
||||
} else {
|
||||
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.data = Uri.fromParts("package", context.packageName, null)
|
||||
intent.data = Uri.fromParts("package", packageName, null)
|
||||
}
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
fun openAppSettingsPage(
|
||||
activity: Activity,
|
||||
noActivityFoundMessage: String = activity.getString(R.string.error_no_compatible_app_found),
|
||||
fun Context.openAppSettingsPage(
|
||||
noActivityFoundMessage: String = getString(R.string.error_no_compatible_app_found),
|
||||
) {
|
||||
try {
|
||||
activity.startActivity(
|
||||
startActivity(
|
||||
Intent().apply {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
data = Uri.fromParts("package", activity.packageName, null)
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
}
|
||||
)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(noActivityFoundMessage)
|
||||
toast(noActivityFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,52 +155,49 @@ fun openAppSettingsPage(
|
|||
* Shows notification system settings for the given channel id.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
fun startNotificationChannelSettingsIntent(activity: Activity, channelID: String) {
|
||||
fun Activity.startNotificationChannelSettingsIntent(channelID: String) {
|
||||
if (!supportNotificationChannels()) return
|
||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, activity.packageName)
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
|
||||
putExtra(Settings.EXTRA_CHANNEL_ID, channelID)
|
||||
}
|
||||
activity.startActivity(intent)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
fun startAddGoogleAccountIntent(
|
||||
context: Context,
|
||||
fun Context.startAddGoogleAccountIntent(
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||
noActivityFoundMessage: String = context.getString(R.string.error_no_compatible_app_found),
|
||||
noActivityFoundMessage: String = getString(R.string.error_no_compatible_app_found),
|
||||
) {
|
||||
val intent = Intent(Settings.ACTION_ADD_ACCOUNT)
|
||||
intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google"))
|
||||
try {
|
||||
activityResultLauncher.launch(intent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
context.toast(noActivityFoundMessage)
|
||||
toast(noActivityFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun startInstallFromSourceIntent(
|
||||
context: Context,
|
||||
fun Context.startInstallFromSourceIntent(
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||
noActivityFoundMessage: String = context.getString(R.string.error_no_compatible_app_found),
|
||||
noActivityFoundMessage: String = getString(R.string.error_no_compatible_app_found),
|
||||
) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
|
||||
.setData(Uri.parse(String.format("package:%s", context.packageName)))
|
||||
.setData(Uri.parse(String.format("package:%s", packageName)))
|
||||
try {
|
||||
activityResultLauncher.launch(intent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
context.toast(noActivityFoundMessage)
|
||||
toast(noActivityFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun startSharePlainTextIntent(
|
||||
context: Context,
|
||||
fun Context.startSharePlainTextIntent(
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>?,
|
||||
chooserTitle: String?,
|
||||
text: String,
|
||||
subject: String? = null,
|
||||
extraTitle: String? = null,
|
||||
noActivityFoundMessage: String = context.getString(R.string.error_no_compatible_app_found),
|
||||
noActivityFoundMessage: String = getString(R.string.error_no_compatible_app_found),
|
||||
) {
|
||||
val share = Intent(Intent.ACTION_SEND)
|
||||
share.type = "text/plain"
|
||||
|
|
@ -220,17 +215,16 @@ fun startSharePlainTextIntent(
|
|||
if (activityResultLauncher != null) {
|
||||
activityResultLauncher.launch(intent)
|
||||
} else {
|
||||
context.startActivity(intent)
|
||||
startActivity(intent)
|
||||
}
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
context.toast(noActivityFoundMessage)
|
||||
toast(noActivityFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun startImportTextFromFileIntent(
|
||||
context: Context,
|
||||
fun Context.startImportTextFromFileIntent(
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||
noActivityFoundMessage: String = context.getString(R.string.error_no_compatible_app_found),
|
||||
noActivityFoundMessage: String = getString(R.string.error_no_compatible_app_found),
|
||||
) {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = "text/plain"
|
||||
|
|
@ -238,7 +232,7 @@ fun startImportTextFromFileIntent(
|
|||
try {
|
||||
activityResultLauncher.launch(intent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
context.toast(noActivityFoundMessage)
|
||||
toast(noActivityFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ object MimeTypes {
|
|||
const val Audio = "audio/*"
|
||||
|
||||
const val Ogg = "audio/ogg"
|
||||
const val Mp3 = "audio/mp3"
|
||||
|
||||
const val PlainText = "text/plain"
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ class InviteFriendsUseCase @Inject constructor(
|
|||
permalinkResult.fold(
|
||||
onSuccess = { permalink ->
|
||||
val appName = buildMeta.applicationName
|
||||
startSharePlainTextIntent(
|
||||
context = activity,
|
||||
activity.startSharePlainTextIntent(
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = stringProvider.getString(CommonStrings.action_invite_friends),
|
||||
text = stringProvider.getString(CommonStrings.invite_friends_text, appName, permalink),
|
||||
|
|
|
|||
|
|
@ -53,82 +53,72 @@ import io.element.android.libraries.theme.ElementTheme
|
|||
fun ElementLogoAtom(
|
||||
size: ElementLogoAtomSize,
|
||||
modifier: Modifier = Modifier,
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
) {
|
||||
val outerSize = when (size) {
|
||||
ElementLogoAtomSize.Large -> 158.dp
|
||||
ElementLogoAtomSize.Medium -> 120.dp
|
||||
}
|
||||
val logoSize = when (size) {
|
||||
ElementLogoAtomSize.Large -> 110.dp
|
||||
ElementLogoAtomSize.Medium -> 83.5.dp
|
||||
}
|
||||
val cornerRadius = when(size) {
|
||||
ElementLogoAtomSize.Large -> 44.dp
|
||||
ElementLogoAtomSize.Medium -> 33.dp
|
||||
}
|
||||
val borderWidth = when (size) {
|
||||
ElementLogoAtomSize.Large -> 1.dp
|
||||
ElementLogoAtomSize.Medium -> 0.38.dp
|
||||
}
|
||||
val blur = if (isSystemInDarkTheme()) {
|
||||
160.dp
|
||||
} else {
|
||||
24.dp
|
||||
}
|
||||
val blur = if (darkTheme) 160.dp else 24.dp
|
||||
//box-shadow: 0px 6.075949668884277px 24.30379867553711px 0px #1B1D2280;
|
||||
val shadowColor = if (isSystemInDarkTheme()) {
|
||||
Color.Black.copy(alpha = 0.4f)
|
||||
} else {
|
||||
Color(0x401B1D22)
|
||||
}
|
||||
val backgroundColor = if (isSystemInDarkTheme()) Color.White.copy(alpha = 0.2f) else Color.White.copy(alpha = 0.4f)
|
||||
val borderColor = if (isSystemInDarkTheme()) Color.White.copy(alpha = 0.8f) else Color.White.copy(alpha = 0.4f)
|
||||
val shadowColor = if (darkTheme) size.shadowColorDark else size.shadowColorLight
|
||||
val backgroundColor = if (darkTheme) Color.White.copy(alpha = 0.2f) else Color.White.copy(alpha = 0.4f)
|
||||
val borderColor = if (darkTheme) Color.White.copy(alpha = 0.8f) else Color.White.copy(alpha = 0.4f)
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(outerSize)
|
||||
.border(borderWidth, borderColor, RoundedCornerShape(cornerRadius)),
|
||||
.size(size.outerSize)
|
||||
.border(size.borderWidth, borderColor, RoundedCornerShape(size.cornerRadius)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(outerSize)
|
||||
.size(size.outerSize)
|
||||
.shapeShadow(
|
||||
color = shadowColor,
|
||||
cornerRadius = cornerRadius,
|
||||
blurRadius = 32.dp,
|
||||
cornerRadius = size.cornerRadius,
|
||||
blurRadius = size.shadowRadius,
|
||||
offsetY = 8.dp,
|
||||
)
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(cornerRadius))
|
||||
.size(outerSize)
|
||||
.clip(RoundedCornerShape(size.cornerRadius))
|
||||
.size(size.outerSize)
|
||||
.background(backgroundColor)
|
||||
.blur(blur)
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier.size(logoSize),
|
||||
modifier = Modifier.size(size.logoSize),
|
||||
painter = painterResource(id = R.drawable.element_logo),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class ElementLogoAtomSize {
|
||||
Medium,
|
||||
Large
|
||||
}
|
||||
sealed class ElementLogoAtomSize(
|
||||
val outerSize: Dp,
|
||||
val logoSize: Dp,
|
||||
val cornerRadius: Dp,
|
||||
val borderWidth: Dp,
|
||||
val shadowColorDark: Color,
|
||||
val shadowColorLight: Color,
|
||||
val shadowRadius: Dp,
|
||||
) {
|
||||
object Medium : ElementLogoAtomSize(
|
||||
outerSize = 120.dp,
|
||||
logoSize = 83.5.dp,
|
||||
cornerRadius = 33.dp,
|
||||
borderWidth = 0.38.dp,
|
||||
shadowColorDark = Color.Black.copy(alpha = 0.4f),
|
||||
shadowColorLight = Color(0x401B1D22),
|
||||
shadowRadius = 32.dp,
|
||||
)
|
||||
|
||||
@Composable
|
||||
@DayNightPreviews
|
||||
internal fun ElementLogoAtomPreview() {
|
||||
ElementPreview {
|
||||
Box(
|
||||
Modifier
|
||||
.size(170.dp)
|
||||
.background(ElementTheme.colors.bgSubtlePrimary))
|
||||
ElementLogoAtom(ElementLogoAtomSize.Large)
|
||||
}
|
||||
object Large : ElementLogoAtomSize(
|
||||
outerSize = 158.dp,
|
||||
logoSize = 110.dp,
|
||||
cornerRadius = 44.dp,
|
||||
borderWidth = 0.5.dp,
|
||||
shadowColorDark = Color.Black,
|
||||
shadowColorLight = Color(0x801B1D22),
|
||||
shadowRadius = 60.dp,
|
||||
)
|
||||
}
|
||||
|
||||
fun Modifier.shapeShadow(
|
||||
|
|
@ -168,3 +158,29 @@ fun Modifier.shapeShadow(
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
@DayNightPreviews
|
||||
internal fun ElementLogoAtomMediumPreview() {
|
||||
ContentToPreview(ElementLogoAtomSize.Medium)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@DayNightPreviews
|
||||
internal fun ElementLogoAtomLargePreview() {
|
||||
ContentToPreview(ElementLogoAtomSize.Large)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize) {
|
||||
ElementPreview {
|
||||
Box(
|
||||
Modifier
|
||||
.size(elementLogoAtomSize.outerSize + elementLogoAtomSize.shadowRadius * 2)
|
||||
.background(ElementTheme.colors.bgSubtlePrimary),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ElementLogoAtom(elementLogoAtomSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.designsystem.atomic.atoms.InfoListItemMolecu
|
|||
import io.element.android.libraries.designsystem.atomic.atoms.InfoListItemPosition
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
|
|
@ -54,7 +55,13 @@ fun InfoListOrganism(
|
|||
else -> InfoListItemPosition.Middle
|
||||
}
|
||||
InfoListItemMolecule(
|
||||
message = { Text(item.message, style = textStyle) },
|
||||
message = {
|
||||
Text(
|
||||
text = item.message,
|
||||
style = textStyle,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
if (item.iconId != null) {
|
||||
Icon(resourceId = item.iconId, contentDescription = null, tint = iconTint)
|
||||
|
|
|
|||
|
|
@ -28,11 +28,10 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.R
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
||||
|
|
@ -52,10 +51,6 @@ fun OnBoardingPage(
|
|||
footer: @Composable () -> Unit = {},
|
||||
content: @Composable () -> Unit = {},
|
||||
) {
|
||||
// Note: having a night variant of R.drawable.onboarding_bg in the folder `drawable-night` is working
|
||||
// at runtime, but is not in Android Studio Preview. So I prefer to handle this manually.
|
||||
val isLight = ElementTheme.colors.isLight
|
||||
val bgDrawableRes = if (isLight) R.drawable.onboarding_bg_light else R.drawable.onboarding_bg_dark
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -64,7 +59,7 @@ fun OnBoardingPage(
|
|||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
painter = painterResource(id = bgDrawableRes),
|
||||
painter = painterResource(id = R.drawable.onboarding_bg),
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
|
@ -92,18 +87,9 @@ fun OnBoardingPage(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun OnBoardingPageLightPreview() =
|
||||
ElementPreviewLight { ContentToPreview() }
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun OnBoardingPageDarkPreview() =
|
||||
ElementPreviewDark { ContentToPreview() }
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
internal fun OnBoardingPagePreview() = ElementPreview {
|
||||
OnBoardingPage(
|
||||
content = {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -20,14 +20,17 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
|
@ -37,21 +40,32 @@ import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
|||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import timber.log.Timber
|
||||
|
||||
@Composable
|
||||
fun ProgressDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String? = null,
|
||||
type: ProgressDialogType = ProgressDialogType.Indeterminate,
|
||||
onDismiss: () -> Unit = {},
|
||||
isCancellable: Boolean = false,
|
||||
onDismissRequest: () -> Unit = {},
|
||||
) {
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
Timber.v("OnDispose progressDialog")
|
||||
}
|
||||
}
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
|
||||
) {
|
||||
ProgressDialogContent(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
isCancellable = isCancellable,
|
||||
onCancelClicked = onDismissRequest,
|
||||
progressIndicator = {
|
||||
when (type) {
|
||||
is ProgressDialogType.Indeterminate -> {
|
||||
|
|
@ -81,6 +95,8 @@ sealed interface ProgressDialogType {
|
|||
private fun ProgressDialogContent(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String? = null,
|
||||
isCancellable: Boolean = false,
|
||||
onCancelClicked: () -> Unit = {},
|
||||
progressIndicator: @Composable () -> Unit = {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
|
|
@ -107,6 +123,17 @@ private fun ProgressDialogContent(
|
|||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
if (isCancellable) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.BottomEnd
|
||||
) {
|
||||
TextButton(onClick = onCancelClicked) {
|
||||
Text(stringResource(id = CommonStrings.action_cancel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +145,6 @@ internal fun ProgressDialogPreview() = ElementThemedPreview { ContentToPreview()
|
|||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
DialogPreview {
|
||||
ProgressDialogContent(text = "test dialog content")
|
||||
ProgressDialogContent(text = "test dialog content", isCancellable = true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 388 KiB |
|
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 223 KiB |
|
|
@ -21,5 +21,5 @@ import java.time.Duration
|
|||
data class AudioInfo(
|
||||
val duration: Duration?,
|
||||
val size: Long?,
|
||||
val mimeType: String?,
|
||||
val mimetype: String?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -95,9 +95,7 @@ interface MatrixRoom : Closeable {
|
|||
|
||||
suspend fun leave(): Result<Unit>
|
||||
|
||||
suspend fun acceptInvitation(): Result<Unit>
|
||||
|
||||
suspend fun rejectInvitation(): Result<Unit>
|
||||
suspend fun join(): Result<Unit>
|
||||
|
||||
suspend fun inviteUserById(id: UserId): Result<Unit>
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ import org.matrix.rustcomponents.sdk.AudioInfo as RustAudioInfo
|
|||
fun RustAudioInfo.map(): AudioInfo = AudioInfo(
|
||||
duration = duration,
|
||||
size = size?.toLong(),
|
||||
mimeType = mimetype
|
||||
mimetype = mimetype
|
||||
)
|
||||
|
||||
fun AudioInfo.map(): RustAudioInfo = RustAudioInfo(
|
||||
duration = duration,
|
||||
size = size?.toULong(),
|
||||
mimetype = mimeType,
|
||||
mimetype = mimetype,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -256,15 +256,9 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun acceptInvitation(): Result<Unit> = withContext(roomDispatcher) {
|
||||
override suspend fun join(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.acceptInvitation()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun rejectInvitation(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.rejectInvitation()
|
||||
innerRoom.join()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,8 +63,7 @@ class FakeMatrixRoom(
|
|||
private var userDisplayNameResult = Result.success<String?>(null)
|
||||
private var userAvatarUrlResult = Result.success<String?>(null)
|
||||
private var updateMembersResult: Result<Unit> = Result.success(Unit)
|
||||
private var acceptInviteResult = Result.success(Unit)
|
||||
private var rejectInviteResult = Result.success(Unit)
|
||||
private var joinRoomResult = Result.success(Unit)
|
||||
private var inviteUserResult = Result.success(Unit)
|
||||
private var canInviteResult = Result.success(true)
|
||||
private val canSendStateResults = mutableMapOf<StateEventType, Result<Boolean>>()
|
||||
|
|
@ -101,11 +100,6 @@ class FakeMatrixRoom(
|
|||
var sendLocationCount: Int = 0
|
||||
private set
|
||||
|
||||
var isInviteAccepted: Boolean = false
|
||||
private set
|
||||
|
||||
var isInviteRejected: Boolean = false
|
||||
private set
|
||||
|
||||
var invitedUserId: UserId? = null
|
||||
private set
|
||||
|
|
@ -196,16 +190,11 @@ class FakeMatrixRoom(
|
|||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
|
||||
override suspend fun leave(): Result<Unit> =
|
||||
leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
|
||||
|
||||
override suspend fun acceptInvitation(): Result<Unit> {
|
||||
isInviteAccepted = true
|
||||
return acceptInviteResult
|
||||
}
|
||||
|
||||
override suspend fun rejectInvitation(): Result<Unit> {
|
||||
isInviteRejected = true
|
||||
return rejectInviteResult
|
||||
override suspend fun join(): Result<Unit> {
|
||||
return joinRoomResult
|
||||
}
|
||||
|
||||
override suspend fun inviteUserById(id: UserId): Result<Unit> = simulateLongTask {
|
||||
|
|
@ -316,12 +305,8 @@ class FakeMatrixRoom(
|
|||
userAvatarUrlResult = avatarUrl
|
||||
}
|
||||
|
||||
fun givenAcceptInviteResult(result: Result<Unit>) {
|
||||
acceptInviteResult = result
|
||||
}
|
||||
|
||||
fun givenRejectInviteResult(result: Result<Unit>) {
|
||||
rejectInviteResult = result
|
||||
fun givenJoinRoomResult(result: Result<Unit>) {
|
||||
joinRoomResult = result
|
||||
}
|
||||
|
||||
fun givenInviteUserResult(result: Result<Unit>) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Attachment
|
||||
import androidx.compose.material.icons.outlined.GraphicEq
|
||||
import androidx.compose.material.icons.outlined.VideoCameraBack
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -44,9 +45,9 @@ fun AttachmentThumbnail(
|
|||
thumbnailSize: Long = 32L,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.surface,
|
||||
) {
|
||||
if (info.mediaSource != null) {
|
||||
if (info.thumbnailSource != null) {
|
||||
val mediaRequestData = MediaRequestData(
|
||||
source = info.mediaSource,
|
||||
source = info.thumbnailSource,
|
||||
kind = MediaRequestData.Kind.Thumbnail(thumbnailSize),
|
||||
)
|
||||
BlurHashAsyncImage(
|
||||
|
|
@ -68,6 +69,12 @@ fun AttachmentThumbnail(
|
|||
contentDescription = info.textContent,
|
||||
)
|
||||
}
|
||||
AttachmentThumbnailType.Audio -> {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.GraphicEq,
|
||||
contentDescription = info.textContent,
|
||||
)
|
||||
}
|
||||
AttachmentThumbnailType.File -> {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Attachment,
|
||||
|
|
@ -88,13 +95,13 @@ fun AttachmentThumbnail(
|
|||
|
||||
@Parcelize
|
||||
enum class AttachmentThumbnailType: Parcelable {
|
||||
Image, Video, File, Location
|
||||
Image, Video, File, Audio, Location
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class AttachmentThumbnailInfo(
|
||||
val mediaSource: MediaSource?,
|
||||
val textContent: String?,
|
||||
val type: AttachmentThumbnailType?,
|
||||
val blurHash: String?,
|
||||
val type: AttachmentThumbnailType,
|
||||
val thumbnailSource: MediaSource? = null,
|
||||
val textContent: String? = null,
|
||||
val blurHash: String? = null,
|
||||
): Parcelable
|
||||
|
|
|
|||
|
|
@ -46,36 +46,43 @@ class MediaSender @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun MatrixRoom.sendMedia(
|
||||
info: MediaUploadInfo,
|
||||
uploadInfo: MediaUploadInfo,
|
||||
progressCallback: ProgressCallback?
|
||||
): Result<Unit> {
|
||||
return when (info) {
|
||||
return when (uploadInfo) {
|
||||
is MediaUploadInfo.Image -> {
|
||||
sendImage(
|
||||
file = info.file,
|
||||
thumbnailFile = info.thumbnailFile,
|
||||
imageInfo = info.info,
|
||||
file = uploadInfo.file,
|
||||
thumbnailFile = uploadInfo.thumbnailFile,
|
||||
imageInfo = uploadInfo.imageInfo,
|
||||
progressCallback = progressCallback
|
||||
)
|
||||
}
|
||||
|
||||
is MediaUploadInfo.Video -> {
|
||||
sendVideo(
|
||||
file = info.file,
|
||||
thumbnailFile = info.thumbnailFile,
|
||||
videoInfo = info.info,
|
||||
file = uploadInfo.file,
|
||||
thumbnailFile = uploadInfo.thumbnailFile,
|
||||
videoInfo = uploadInfo.videoInfo,
|
||||
progressCallback = progressCallback
|
||||
)
|
||||
}
|
||||
is MediaUploadInfo.Audio -> {
|
||||
sendAudio(
|
||||
file = uploadInfo.file,
|
||||
audioInfo = uploadInfo.audioInfo,
|
||||
progressCallback = progressCallback
|
||||
)
|
||||
}
|
||||
|
||||
is MediaUploadInfo.AnyFile -> {
|
||||
sendFile(
|
||||
file = info.file,
|
||||
fileInfo = info.info,
|
||||
file = uploadInfo.file,
|
||||
fileInfo = uploadInfo.fileInfo,
|
||||
progressCallback = progressCallback
|
||||
)
|
||||
}
|
||||
else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $info"))
|
||||
else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $uploadInfo"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ sealed interface MediaUploadInfo {
|
|||
|
||||
val file: File
|
||||
|
||||
data class Image(override val file: File, val info: ImageInfo, val thumbnailFile: File) : MediaUploadInfo
|
||||
data class Video(override val file: File, val info: VideoInfo, val thumbnailFile: File) : MediaUploadInfo
|
||||
data class Audio(override val file: File, val info: AudioInfo) : MediaUploadInfo
|
||||
data class AnyFile(override val file: File, val info: FileInfo) : MediaUploadInfo
|
||||
data class Image(override val file: File, val imageInfo: ImageInfo, val thumbnailFile: File) : MediaUploadInfo
|
||||
data class Video(override val file: File, val videoInfo: VideoInfo, val thumbnailFile: File) : MediaUploadInfo
|
||||
data class Audio(override val file: File, val audioInfo: AudioInfo) : MediaUploadInfo
|
||||
data class AnyFile(override val file: File, val fileInfo: FileInfo) : MediaUploadInfo
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
removeSensitiveImageMetadata(compressionResult.file)
|
||||
return MediaUploadInfo.Image(
|
||||
file = compressionResult.file,
|
||||
info = imageInfo,
|
||||
imageInfo = imageInfo,
|
||||
thumbnailFile = thumbnailResult.file
|
||||
)
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
removeSensitiveImageMetadata(file)
|
||||
return MediaUploadInfo.Image(
|
||||
file = file,
|
||||
info = imageInfo,
|
||||
imageInfo = imageInfo,
|
||||
thumbnailFile = thumbnailResult.file
|
||||
)
|
||||
}
|
||||
|
|
@ -184,7 +184,7 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
val videoInfo = extractVideoMetadata(resultFile, mimeType, thumbnailInfo)
|
||||
return MediaUploadInfo.Video(
|
||||
file = resultFile,
|
||||
info = videoInfo,
|
||||
videoInfo = videoInfo,
|
||||
thumbnailFile = thumbnailInfo.file
|
||||
)
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
val info = AudioInfo(
|
||||
duration = extractDuration(),
|
||||
size = file.length(),
|
||||
mimeType = mimeType,
|
||||
mimetype = mimeType,
|
||||
)
|
||||
|
||||
MediaUploadInfo.Audio(file, info)
|
||||
|
|
|
|||
|
|
@ -165,15 +165,15 @@ class NotificationChannels @Inject constructor(
|
|||
private fun supportNotificationChannels() = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
|
||||
fun openSystemSettingsForSilentCategory(activity: Activity) {
|
||||
startNotificationChannelSettingsIntent(activity, SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
activity.startNotificationChannelSettingsIntent(SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
|
||||
fun openSystemSettingsForNoisyCategory(activity: Activity) {
|
||||
startNotificationChannelSettingsIntent(activity, NOISY_NOTIFICATION_CHANNEL_ID)
|
||||
activity.startNotificationChannelSettingsIntent(NOISY_NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
|
||||
fun openSystemSettingsForCallCategory(activity: Activity) {
|
||||
startNotificationChannelSettingsIntent(activity, CALL_NOTIFICATION_CHANNEL_ID)
|
||||
activity.startNotificationChannelSettingsIntent(CALL_NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ fun TextComposerReplyPreview() = ElementPreview {
|
|||
senderName = "Alice",
|
||||
eventId = EventId("$1234"),
|
||||
attachmentThumbnailInfo = AttachmentThumbnailInfo(
|
||||
mediaSource = MediaSource("https://domain.com/image.jpg"),
|
||||
thumbnailSource = MediaSource("https://domain.com/image.jpg"),
|
||||
textContent = "image.jpg",
|
||||
type = AttachmentThumbnailType.Image,
|
||||
blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr",
|
||||
|
|
@ -501,7 +501,7 @@ fun TextComposerReplyPreview() = ElementPreview {
|
|||
senderName = "Alice",
|
||||
eventId = EventId("$1234"),
|
||||
attachmentThumbnailInfo = AttachmentThumbnailInfo(
|
||||
mediaSource = MediaSource("https://domain.com/video.mp4"),
|
||||
thumbnailSource = MediaSource("https://domain.com/video.mp4"),
|
||||
textContent = "video.mp4",
|
||||
type = AttachmentThumbnailType.Video,
|
||||
blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr",
|
||||
|
|
@ -519,7 +519,7 @@ fun TextComposerReplyPreview() = ElementPreview {
|
|||
senderName = "Alice",
|
||||
eventId = EventId("$1234"),
|
||||
attachmentThumbnailInfo = AttachmentThumbnailInfo(
|
||||
mediaSource = null,
|
||||
thumbnailSource = null,
|
||||
textContent = "logs.txt",
|
||||
type = AttachmentThumbnailType.File,
|
||||
blurHash = null,
|
||||
|
|
@ -537,7 +537,7 @@ fun TextComposerReplyPreview() = ElementPreview {
|
|||
senderName = "Alice",
|
||||
eventId = EventId("$1234"),
|
||||
attachmentThumbnailInfo = AttachmentThumbnailInfo(
|
||||
mediaSource = null,
|
||||
thumbnailSource = null,
|
||||
textContent = null,
|
||||
type = AttachmentThumbnailType.Location,
|
||||
blurHash = null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue