Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.ramcosta.composedestinations.spec.Direction
import com.wire.android.BuildConfig
import com.wire.android.R
import com.wire.android.util.EmailComposer
import com.wire.android.util.externalShareChooserIntent
import com.wire.android.util.getDeviceIdString
import com.wire.android.util.getGitBuildId
import com.wire.android.util.sha256
Expand Down Expand Up @@ -100,7 +101,7 @@ object GiveFeedbackDestination : IntentDirection {
)
)
intent.selector = Intent(Intent.ACTION_SENDTO).setData(Uri.parse("mailto:"))
return Intent.createChooser(intent, context.getString(R.string.send_feedback_choose_email))
return context.externalShareChooserIntent(intent, context.getString(R.string.send_feedback_choose_email))
}

override val route: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
*/
package com.wire.android.ui

import android.content.Context
import androidx.lifecycle.SavedStateHandle
import com.wire.android.datastore.UserDataStoreProvider
import com.wire.android.di.ApplicationContext
import com.wire.android.di.CurrentAccount
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.ui.analytics.AnalyticsConfiguration
Expand Down Expand Up @@ -51,6 +53,7 @@ import dev.zacsweers.metro.Inject

@Suppress("LongParameterList")
class MiscViewModelFactory @Inject constructor(
@ApplicationContext private val context: Context,
private val analyticsEnabled: AnalyticsConfiguration,
private val selfServerConfig: Lazy<SelfServerConfigUseCase>,
private val observeSyncState: ObserveSyncStateUseCase,
Expand Down Expand Up @@ -110,6 +113,7 @@ class MiscViewModelFactory @Inject constructor(
)

fun importMediaAuthenticatedViewModel() = ImportMediaAuthenticatedViewModel(
context = context,
getSelf = getSelf,
getConversationsPaginated = getConversationsPaginated,
handleUriAsset = handleUriAsset,
Expand Down
20 changes: 18 additions & 2 deletions app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -153,12 +154,14 @@ import com.wire.android.ui.userprofile.self.LocalSelfUserProfileLogoutAction
import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialog
import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialogState
import com.wire.android.util.CurrentScreenManager
import com.wire.android.ui.sharing.hasTrustedWireShareCaller
import com.wire.android.util.LocalSyncStateObserver
import com.wire.android.util.ShakeDetector
import com.wire.android.util.SwitchAccountObserver
import com.wire.android.util.SyncStateObserver
import com.wire.android.util.debug.FeatureVisibilityFlags
import com.wire.android.util.debug.LocalFeatureVisibilityFlags
import com.wire.android.util.getProviderAuthority
import com.wire.android.util.launchUpdateTheApp
import com.wire.kalium.logic.data.user.UserId
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -287,10 +290,19 @@ class WireActivity : BaseActivity() {
handleSynchronizeExternalData(intent)
return
}
setIntent(intent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
setIntentWithCurrentCaller(intent)
} else {
setIntent(intent)
}
handleNewIntent(intent)
}

@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
private fun setIntentWithCurrentCaller(intent: Intent) {
setIntent(intent, getCurrentCaller())
}

private fun handleNewIntent(intent: Intent, savedInstanceState: Bundle? = null) = lifecycleScope.launch {
newIntents.send(intent to savedInstanceState)
}
Expand Down Expand Up @@ -1183,7 +1195,11 @@ class WireActivity : BaseActivity() {
} else {
val handled = viewModel.handleIntentsThatAreNotDeepLinks(intent)
if (!handled) {
viewModel.handleDeepLink(intent)
viewModel.handleDeepLink(
intent = intent,
providerAuthority = getProviderAuthority(),
hasTrustedWireShareCaller = hasTrustedWireShareCaller()
)
intent.putExtra(HANDLED_DEEPLINK_FLAG, true)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.ramcosta.composedestinations.generated.app.destinations.OtherUserProf
import com.ramcosta.composedestinations.generated.app.destinations.WelcomeScreenDestination
import com.wire.android.ui.authentication.login.LoginPasswordPath
import com.wire.android.ui.newauthentication.login.NewLoginViewModel
import com.wire.android.ui.sharing.ImportMediaNavArgs
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -86,10 +87,10 @@ private fun openConversation(action: OpenConversation, navigator: Navigator) {

private fun openImportMediaScreen(navigator: Navigator) {
navigator.navigate(
NavigationCommand(
ImportMediaScreenDestination,
BackStackMode.UPDATE_EXISTED
)
NavigationCommand(
ImportMediaScreenDestination(ImportMediaNavArgs(arrayListOf())),
BackStackMode.UPDATE_EXISTED
)
)
}

Expand Down
37 changes: 35 additions & 2 deletions app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import com.wire.android.ui.common.dialogs.CustomServerDetailsDialogState
import com.wire.android.ui.common.dialogs.CustomServerDialogState
import com.wire.android.ui.common.dialogs.CustomServerNoNetworkDialogState
import com.wire.android.ui.joinConversation.JoinConversationViaCodeState
import com.wire.android.ui.sharing.sharingUris
import com.wire.android.ui.sharing.shouldRejectSharingIntent
import com.wire.android.ui.theme.Accent
import com.wire.android.ui.theme.ThemeOption
import com.wire.android.util.CurrentScreen
Expand Down Expand Up @@ -110,6 +112,8 @@ import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.InputStream
import java.io.InputStreamReader
import dev.zacsweers.metro.Inject
Expand Down Expand Up @@ -413,7 +417,11 @@ class WireActivityViewModel @Inject constructor(
}

@Suppress("ComplexMethod")
fun handleDeepLink(intent: Intent?) {
fun handleDeepLink(
intent: Intent?,
providerAuthority: String? = null,
hasTrustedWireShareCaller: Boolean = false
) {
viewModelScope.launch(dispatchers.io()) {
when (val result = deepLinkProcessor.value.invoke(intent?.data, intent?.action)) {
DeepLinkResult.AuthorizationNeeded -> sendAction(OnAuthorizationNeeded)
Expand Down Expand Up @@ -442,7 +450,16 @@ class WireActivityViewModel @Inject constructor(
is DeepLinkResult.OpenConversation -> sendAction(OpenConversation(result))
is DeepLinkResult.OpenOtherUserProfile -> onOpenUserProfileDeepLink(result)

DeepLinkResult.SharingIntent -> sendAction(OnShowImportMediaScreen)
DeepLinkResult.SharingIntent -> {
val shouldRejectSharingIntent = providerAuthority != null &&
intent?.shouldRejectSharingIntent(providerAuthority, hasTrustedWireShareCaller) == true
if (shouldRejectSharingIntent) {
logRejectedSharingIntent(intent, providerAuthority, hasTrustedWireShareCaller)
sendAction(ShowToast(R.string.public_share_ignored_wire_internal_files))
return@launch
}
sendAction(OnShowImportMediaScreen)
}
DeepLinkResult.Unknown -> {
sendAction(OnUnknownDeepLink)
appLogger.e("unknown deeplink result $result")
Expand All @@ -451,6 +468,22 @@ class WireActivityViewModel @Inject constructor(
}
}

private fun logRejectedSharingIntent(
intent: Intent?,
providerAuthority: String?,
hasTrustedWireShareCaller: Boolean
) {
val logMap = mapOf(
"event" to "public_share_rejected",
"reason" to "wire_file_provider_uri",
"action" to intent?.action.orEmpty(),
"providerAuthority" to providerAuthority.orEmpty(),
"hasTrustedWireShareCaller" to hasTrustedWireShareCaller.toString(),
"uriCount" to (intent?.sharingUris()?.size ?: 0).toString()
)
appLogger.w("Rejected public share intent: ${Json.encodeToString(logMap)}")
}

// Returns whether an intent was handled, or if there was nothing to do
@Suppress("ReturnCount")
suspend fun handleIntentsThatAreNotDeepLinks(intent: Intent?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.res.stringResource
import com.wire.android.R
import com.wire.android.ui.common.bottomsheet.MenuBottomSheetItem
import com.wire.android.ui.common.bottomsheet.MenuItemIcon
import com.wire.android.util.supportsTrustedWireShareCaller

@Composable
fun ShareAssetMenuOption(onShareAsset: () -> Unit) {
Expand All @@ -37,6 +38,47 @@ fun ShareAssetMenuOption(onShareAsset: () -> Unit) {
)
}

fun shareAssetMenuOptions(
onShareAssetExternally: () -> Unit,
onShareAssetViaWire: () -> Unit
): List<@Composable () -> Unit> =
if (supportsTrustedWireShareCaller()) {
listOf({ ShareAssetMenuOption(onShareAssetExternally) })
} else {
listOf(
{ ShareAssetViaWireMenuOption(onShareAssetViaWire) },
{ ShareAssetExternallyMenuOption(onShareAssetExternally) }
)
}

@Composable
fun ShareAssetViaWireMenuOption(onShareAsset: () -> Unit) {
MenuBottomSheetItem(
leading = {
MenuItemIcon(
id = R.drawable.ic_share_file,
contentDescription = stringResource(R.string.content_description_share_the_file),
)
},
title = stringResource(R.string.label_share_via_wire),
onItemClick = onShareAsset
)
}

@Composable
fun ShareAssetExternallyMenuOption(onShareAsset: () -> Unit) {
MenuBottomSheetItem(
leading = {
MenuItemIcon(
id = R.drawable.ic_share_file,
contentDescription = stringResource(R.string.content_description_share_the_file),
)
},
title = stringResource(R.string.label_share_externally),
onItemClick = onShareAsset
)
}

@Composable
fun SharePublicLinkMenuOption(onShareAsset: () -> Unit) {
MenuBottomSheetItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import androidx.paging.compose.itemKey
import com.ramcosta.composedestinations.generated.app.destinations.ConversationScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.GroupConversationDetailsScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ImagesPreviewScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ImportMediaScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.MediaGalleryScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.MessageDetailsScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.OtherUserProfileScreenDestination
Expand Down Expand Up @@ -186,12 +187,14 @@ import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireTypography
import com.wire.android.ui.userprofile.service.ServiceDetailsNavArgs
import com.wire.android.util.DateAndTimeParsers
import com.wire.android.util.fileShareUri
import com.wire.android.util.normalizeLink
import com.wire.android.util.openDownloadFolder
import com.wire.android.util.serverDate
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.android.util.ui.UIText
import com.wire.android.util.ui.collectAsLazyPagingItemsWithLifecycle
import com.wire.android.ui.sharing.ImportMediaNavArgs
import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode
import com.wire.kalium.logic.data.conversation.InteractionAvailability
import com.wire.kalium.logic.data.id.ConversationId
Expand Down Expand Up @@ -253,7 +256,8 @@ fun ConversationScreen(
) {
val coroutineScope = rememberCoroutineScope()
val uriHandler = LocalUriHandler.current
val resources = LocalContext.current.resources
val context = LocalContext.current
val resources = context.resources
val showDialog = remember { mutableStateOf(ConversationScreenDialogType.NONE) }
val messageComposerViewState = messageComposerViewModel.messageComposerViewState
val messageComposerStateHolder = rememberMessageComposerStateHolder(
Expand Down Expand Up @@ -535,7 +539,19 @@ fun ConversationScreen(
},
composerMessages = sendMessageViewModel.infoMessage,
conversationMessages = conversationMessagesViewModel.infoMessage,
shareAsset = conversationMessagesViewModel::shareAsset,
shareAssetExternally = conversationMessagesViewModel::shareAsset,
shareAssetViaWire = { messageId ->
conversationMessagesViewModel.shareAssetViaWire(messageId) { path, assetName ->
navigator.navigate(
NavigationCommand(
ImportMediaScreenDestination(
ImportMediaNavArgs(arrayListOf(context.fileShareUri(path, assetName)))
),
BackStackMode.UPDATE_EXISTED
)
)
}
},
onDownloadAssetClick = conversationMessagesViewModel::openOrFetchAsset,
onOpenAssetClick = conversationMessagesViewModel::downloadAndOpenAsset,
onNavigateToReplyOriginalMessage = conversationMessagesViewModel::navigateToReplyOriginalMessage,
Expand Down Expand Up @@ -800,7 +816,8 @@ private fun ConversationScreen(
onBackButtonClick: () -> Unit,
composerMessages: SharedFlow<SnackBarMessage>,
conversationMessages: SharedFlow<SnackBarMessage>,
shareAsset: (Context, messageId: String) -> Unit,
shareAssetExternally: (Context, messageId: String) -> Unit,
shareAssetViaWire: (messageId: String) -> Unit,
onDownloadAssetClick: (messageId: String) -> Unit,
onOpenAssetClick: (messageId: String) -> Unit,
onNavigateToReplyOriginalMessage: (UIMessage) -> Unit,
Expand Down Expand Up @@ -943,7 +960,8 @@ private fun ConversationScreen(
onDetailsClick = onMessageDetailsClick,
onReplyClick = messageComposerStateHolder::toReply,
onEditClick = messageComposerStateHolder::toEdit,
onShareAssetClick = { shareAsset(context, it) },
onShareAssetExternallyClick = { shareAssetExternally(context, it) },
onShareAssetViaWireClick = shareAssetViaWire,
onDownloadAssetClick = onDownloadAssetClick,
onOpenAssetClick = onOpenAssetClick,
)
Expand Down Expand Up @@ -1810,7 +1828,8 @@ fun PreviewConversationScreen() = WireTheme {
onBackButtonClick = {},
composerMessages = MutableStateFlow(ConversationSnackbarMessages.ErrorDownloadingAsset),
conversationMessages = MutableStateFlow(ConversationSnackbarMessages.ErrorDownloadingAsset),
shareAsset = { _, _ -> },
shareAssetExternally = { _, _ -> },
shareAssetViaWire = {},
onOpenAssetClick = {},
onDownloadAssetClick = {},
onNavigateToReplyOriginalMessage = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import com.wire.android.ui.edit.MessageDetailsMenuOption
import com.wire.android.ui.edit.OpenAssetExternallyOption
import com.wire.android.ui.edit.ReactionOption
import com.wire.android.ui.edit.ReplyMessageOption
import com.wire.android.ui.edit.ShareAssetMenuOption
import com.wire.android.ui.edit.shareAssetMenuOptions

// menu items with both asset options enabled (like share, download, etc.) and message options enabled (like reply, reaction, etc.)
@Composable
Expand All @@ -33,7 +33,8 @@ fun assetMessageOptionsMenuItems(
ownReactions: Set<String>,
onDeleteClick: () -> Unit,
onDetailsClick: () -> Unit,
onShareAsset: () -> Unit,
onShareAssetExternally: () -> Unit,
onShareAssetViaWire: () -> Unit,
onDownloadAsset: () -> Unit,
onReplyClick: () -> Unit,
onReactionClick: (emoji: String) -> Unit,
Expand All @@ -59,7 +60,7 @@ fun assetMessageOptionsMenuItems(
add { MessageDetailsMenuOption(onDetailsClick) }
add { ReplyMessageOption(onReplyClick) }
add { DownloadAssetExternallyOption(onDownloadAsset) }
add { ShareAssetMenuOption(onShareAsset) }
addAll(shareAssetMenuOptions(onShareAssetExternally, onShareAssetViaWire))
if (isOpenable) add { OpenAssetExternallyOption(onOpenAsset) }
add { DeleteItemMenuOption(onDeleteClick) }
}
Expand All @@ -72,15 +73,18 @@ fun assetMessageOptionsMenuItems(
fun assetOptionsMenuItems(
isEphemeral: Boolean,
onDeleteClick: () -> Unit,
onShareAsset: () -> Unit,
onShareAssetExternally: () -> Unit,
onShareAssetViaWire: () -> Unit,
onDownloadAsset: () -> Unit,
isOpenable: Boolean = false,
onOpenAsset: () -> Unit = {},
isUploading: Boolean = false,
): List<@Composable () -> Unit> = buildList {
if (!isUploading) {
add { DownloadAssetExternallyOption(onDownloadAsset) }
if (!isEphemeral) add { ShareAssetMenuOption(onShareAsset) }
if (!isEphemeral) {
addAll(shareAssetMenuOptions(onShareAssetExternally, onShareAssetViaWire))
}
if (isOpenable) add { OpenAssetExternallyOption(onOpenAsset) }
}
add { DeleteItemMenuOption(onDeleteClick) }
Expand Down
Loading
Loading