misc: display offline banner directly in LoggedInFlowNode (and fix window insets)
This commit is contained in:
@@ -53,6 +53,7 @@ import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.home.api.HomeEntryPoint
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer
|
||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||
@@ -77,6 +78,7 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService
|
||||
@@ -122,6 +124,7 @@ class LoggedInFlowNode(
|
||||
private val sessionEnterpriseService: SessionEnterpriseService,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
private val notificationConversationService: NotificationConversationService,
|
||||
private val syncService: SyncService,
|
||||
snackbarDispatcher: SnackbarDispatcher,
|
||||
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
@@ -538,11 +541,17 @@ class LoggedInFlowNode(
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
val ftueState by ftueService.state.collectAsState()
|
||||
BackstackView()
|
||||
if (ftueState is FtueState.Complete) {
|
||||
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
ConnectivityIndicatorContainer(
|
||||
isOnline = isOnline,
|
||||
modifier = modifier,
|
||||
) { contentModifier ->
|
||||
Box(modifier = contentModifier) {
|
||||
val ftueState by ftueService.state.collectAsState()
|
||||
BackstackView()
|
||||
if (ftueState is FtueState.Complete) {
|
||||
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package io.element.android.appnav.room
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -48,7 +47,6 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -71,7 +69,6 @@ class RoomFlowNode(
|
||||
private val client: MatrixClient,
|
||||
private val joinRoomEntryPoint: JoinRoomEntryPoint,
|
||||
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
|
||||
private val syncService: SyncService,
|
||||
private val membershipObserver: RoomMembershipObserver,
|
||||
private val spaceEntryPoint: SpaceEntryPoint,
|
||||
) : BaseFlowNode<RoomFlowNode.NavTarget>(
|
||||
@@ -222,10 +219,8 @@ class RoomFlowNode(
|
||||
}
|
||||
|
||||
private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier ->
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
LoadingRoomNodeView(
|
||||
state = LoadingRoomState.Loading,
|
||||
hasNetworkConnection = isOnline,
|
||||
onBackClick = { navigateUp() },
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
@@ -35,7 +35,6 @@ import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -50,7 +49,6 @@ class JoinedRoomFlowNode(
|
||||
@Assisted val buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory,
|
||||
private val syncService: SyncService,
|
||||
) :
|
||||
BaseFlowNode<JoinedRoomFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
@@ -116,12 +114,10 @@ class JoinedRoomFlowNode(
|
||||
|
||||
private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier ->
|
||||
val loadingRoomState by loadingRoomStateStateFlow.collectAsState()
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
LoadingRoomNodeView(
|
||||
state = loadingRoomState,
|
||||
hasNetworkConnection = isOnline,
|
||||
modifier = modifier,
|
||||
onBackClick = onBackClick
|
||||
onBackClick = onBackClick,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
package io.element.android.appnav.room.joined
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -21,7 +19,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
@@ -38,17 +35,13 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun LoadingRoomNodeView(
|
||||
state: LoadingRoomState,
|
||||
hasNetworkConnection: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
Column {
|
||||
ConnectivityIndicatorView(isOnline = hasNetworkConnection)
|
||||
LoadingRoomTopBar(onBackClick)
|
||||
}
|
||||
LoadingRoomTopBar(onBackClick)
|
||||
},
|
||||
content = { padding ->
|
||||
Box(
|
||||
@@ -85,7 +78,6 @@ private fun LoadingRoomTopBar(
|
||||
title = {
|
||||
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp)
|
||||
},
|
||||
windowInsets = WindowInsets(0.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,7 +86,6 @@ private fun LoadingRoomTopBar(
|
||||
internal fun LoadingRoomNodeViewPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) = ElementPreview {
|
||||
LoadingRoomNodeView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
hasNetworkConnection = false
|
||||
onBackClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
@@ -50,7 +49,6 @@ import io.element.android.features.home.impl.roomlist.RoomListEvents
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.search.RoomListSearchView
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesView
|
||||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer
|
||||
import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@@ -84,56 +82,47 @@ fun HomeView(
|
||||
val state: RoomListState = homeState.roomListState
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val firstThrottler = remember { FirstThrottler(300, coroutineScope) }
|
||||
|
||||
ConnectivityIndicatorContainer(
|
||||
modifier = modifier,
|
||||
isOnline = homeState.hasNetworkConnection,
|
||||
) { topPadding ->
|
||||
Box {
|
||||
if (state.contextMenu is RoomListState.ContextMenu.Shown) {
|
||||
RoomListContextMenu(
|
||||
contextMenu = state.contextMenu,
|
||||
canReportRoom = state.canReportRoom,
|
||||
eventSink = state.eventSink,
|
||||
onRoomSettingsClick = onRoomSettingsClick,
|
||||
onReportRoomClick = onReportRoomClick,
|
||||
)
|
||||
}
|
||||
if (state.declineInviteMenu is RoomListState.DeclineInviteMenu.Shown) {
|
||||
RoomListDeclineInviteMenu(
|
||||
menu = state.declineInviteMenu,
|
||||
canReportRoom = state.canReportRoom,
|
||||
eventSink = state.eventSink,
|
||||
onDeclineAndBlockClick = onDeclineInviteAndBlockUser,
|
||||
)
|
||||
}
|
||||
|
||||
leaveRoomView()
|
||||
|
||||
HomeScaffold(
|
||||
state = homeState,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) },
|
||||
onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() },
|
||||
onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() },
|
||||
onMenuActionClick = onMenuActionClick,
|
||||
modifier = Modifier.padding(top = topPadding),
|
||||
)
|
||||
// This overlaid view will only be visible when state.displaySearchResults is true
|
||||
RoomListSearchView(
|
||||
state = state.searchState,
|
||||
Box(modifier) {
|
||||
if (state.contextMenu is RoomListState.ContextMenu.Shown) {
|
||||
RoomListContextMenu(
|
||||
contextMenu = state.contextMenu,
|
||||
canReportRoom = state.canReportRoom,
|
||||
eventSink = state.eventSink,
|
||||
hideInvitesAvatars = state.hideInvitesAvatars,
|
||||
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) },
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.padding(top = topPadding)
|
||||
.fillMaxSize()
|
||||
.background(ElementTheme.colors.bgCanvasDefault)
|
||||
onRoomSettingsClick = onRoomSettingsClick,
|
||||
onReportRoomClick = onReportRoomClick,
|
||||
)
|
||||
acceptDeclineInviteView()
|
||||
}
|
||||
if (state.declineInviteMenu is RoomListState.DeclineInviteMenu.Shown) {
|
||||
RoomListDeclineInviteMenu(
|
||||
menu = state.declineInviteMenu,
|
||||
canReportRoom = state.canReportRoom,
|
||||
eventSink = state.eventSink,
|
||||
onDeclineAndBlockClick = onDeclineInviteAndBlockUser,
|
||||
)
|
||||
}
|
||||
|
||||
leaveRoomView()
|
||||
|
||||
HomeScaffold(
|
||||
state = homeState,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) },
|
||||
onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() },
|
||||
onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() },
|
||||
onMenuActionClick = onMenuActionClick,
|
||||
)
|
||||
// This overlaid view will only be visible when state.displaySearchResults is true
|
||||
RoomListSearchView(
|
||||
state = state.searchState,
|
||||
eventSink = state.eventSink,
|
||||
hideInvitesAvatars = state.hideInvitesAvatars,
|
||||
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) },
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(ElementTheme.colors.bgCanvasDefault)
|
||||
)
|
||||
acceptDeclineInviteView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
@@ -112,7 +111,6 @@ class MessagesPresenter(
|
||||
private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>,
|
||||
private val roomCallStatePresenter: Presenter<RoomCallState>,
|
||||
private val roomMemberModerationPresenter: Presenter<RoomMemberModerationState>,
|
||||
private val syncService: SyncService,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val clipboardHelper: ClipboardHelper,
|
||||
@@ -193,7 +191,6 @@ class MessagesPresenter(
|
||||
showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L
|
||||
}
|
||||
}
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
|
||||
@@ -250,8 +247,8 @@ class MessagesPresenter(
|
||||
roomName = roomInfo.name,
|
||||
roomAvatar = roomAvatar,
|
||||
heroes = heroes,
|
||||
composerState = composerState,
|
||||
userEventPermissions = userEventPermissions,
|
||||
composerState = composerState,
|
||||
voiceMessageComposerState = voiceMessageComposerState,
|
||||
timelineState = timelineState,
|
||||
timelineProtectionState = timelineProtectionState,
|
||||
@@ -261,19 +258,17 @@ class MessagesPresenter(
|
||||
customReactionState = customReactionState,
|
||||
reactionSummaryState = reactionSummaryState,
|
||||
readReceiptBottomSheetState = readReceiptBottomSheetState,
|
||||
hasNetworkConnection = isOnline,
|
||||
snackbarMessage = snackbarMessage,
|
||||
showReinvitePrompt = showReinvitePrompt,
|
||||
inviteProgress = inviteProgress.value,
|
||||
showReinvitePrompt = showReinvitePrompt,
|
||||
enableTextFormatting = MessageComposerConfig.ENABLE_RICH_TEXT_EDITING,
|
||||
appName = buildMeta.applicationName,
|
||||
roomCallState = roomCallState,
|
||||
appName = buildMeta.applicationName,
|
||||
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
||||
dmUserVerificationState = dmUserVerificationState,
|
||||
roomMemberModerationState = roomMemberModerationState,
|
||||
successorRoom = roomInfo.successorRoom,
|
||||
eventSink = { handleEvents(it) }
|
||||
)
|
||||
successorRoom = roomInfo.successorRoom
|
||||
) { handleEvents(it) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -44,7 +44,6 @@ data class MessagesState(
|
||||
val customReactionState: CustomReactionState,
|
||||
val reactionSummaryState: ReactionSummaryState,
|
||||
val readReceiptBottomSheetState: ReadReceiptBottomSheetState,
|
||||
val hasNetworkConnection: Boolean,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val inviteProgress: AsyncData<Unit>,
|
||||
val showReinvitePrompt: Boolean,
|
||||
|
||||
@@ -56,7 +56,6 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||
override val values: Sequence<MessagesState>
|
||||
get() = sequenceOf(
|
||||
aMessagesState(),
|
||||
aMessagesState(hasNetworkConnection = false),
|
||||
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
|
||||
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
|
||||
aMessagesState(showReinvitePrompt = true),
|
||||
@@ -108,7 +107,6 @@ fun aMessagesState(
|
||||
actionListState: ActionListState = anActionListState(),
|
||||
customReactionState: CustomReactionState = aCustomReactionState(),
|
||||
reactionSummaryState: ReactionSummaryState = aReactionSummaryState(),
|
||||
hasNetworkConnection: Boolean = true,
|
||||
showReinvitePrompt: Boolean = false,
|
||||
roomCallState: RoomCallState = aStandByCallState(),
|
||||
pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(),
|
||||
@@ -132,7 +130,6 @@ fun aMessagesState(
|
||||
actionListState = actionListState,
|
||||
customReactionState = customReactionState,
|
||||
reactionSummaryState = reactionSummaryState,
|
||||
hasNetworkConnection = hasNetworkConnection,
|
||||
snackbarMessage = null,
|
||||
inviteProgress = AsyncData.Uninitialized,
|
||||
showReinvitePrompt = showReinvitePrompt,
|
||||
|
||||
@@ -71,7 +71,6 @@ import io.element.android.features.messages.impl.topbars.MessagesViewTopBar
|
||||
import io.element.android.features.messages.impl.topbars.ThreadTopBar
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessagePermissionRationaleDialog
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageSendingFailedDialog
|
||||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
|
||||
import io.element.android.libraries.androidutils.ui.hideKeyboard
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
|
||||
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout
|
||||
@@ -84,6 +83,7 @@ import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.HideKeyboardWhenDisposed
|
||||
import io.element.android.libraries.designsystem.utils.KeepScreenOn
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
@@ -123,6 +123,8 @@ fun MessagesView(
|
||||
|
||||
KeepScreenOn(state.voiceMessageComposerState.keepScreenOn)
|
||||
|
||||
HideKeyboardWhenDisposed()
|
||||
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
|
||||
// This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose
|
||||
@@ -180,8 +182,6 @@ fun MessagesView(
|
||||
Scaffold(
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
Column {
|
||||
ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
|
||||
if (state.timelineState.timelineMode is Timeline.Mode.Thread) {
|
||||
ThreadTopBar(
|
||||
roomName = state.roomName,
|
||||
@@ -203,7 +203,6 @@ fun MessagesView(
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = { padding ->
|
||||
Box(
|
||||
|
||||
@@ -84,7 +84,6 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
@@ -130,7 +129,6 @@ class MessagesPresenterTest {
|
||||
.isEqualTo(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))
|
||||
assertThat(initialState.userEventPermissions.canSendMessage).isTrue()
|
||||
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
|
||||
assertThat(initialState.hasNetworkConnection).isTrue()
|
||||
assertThat(initialState.snackbarMessage).isNull()
|
||||
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
|
||||
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||
@@ -1274,31 +1272,30 @@ class MessagesPresenterTest {
|
||||
addRecentEmoji: AddRecentEmoji = AddRecentEmoji(FakeMatrixClient(), testCoroutineDispatchers()),
|
||||
): MessagesPresenter {
|
||||
return MessagesPresenter(
|
||||
navigator = navigator,
|
||||
room = joinedRoom,
|
||||
composerPresenter = messageComposerPresenter,
|
||||
voiceMessageComposerPresenterFactory = FakeDefaultVoiceMessageComposerPresenterFactory(backgroundScope),
|
||||
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
|
||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||
identityChangeStatePresenter = { anIdentityChangeState() },
|
||||
linkPresenter = { aLinkState() },
|
||||
actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
|
||||
customReactionPresenter = { aCustomReactionState() },
|
||||
reactionSummaryPresenter = { aReactionSummaryState() },
|
||||
readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() },
|
||||
identityChangeStatePresenter = { anIdentityChangeState() },
|
||||
linkPresenter = { aLinkState() },
|
||||
pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() },
|
||||
roomCallStatePresenter = { aStandByCallState() },
|
||||
roomMemberModerationPresenter = roomMemberModerationPresenter,
|
||||
syncService = FakeSyncService(),
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
navigator = navigator,
|
||||
clipboardHelper = clipboardHelper,
|
||||
buildMeta = aBuildMeta(),
|
||||
dispatchers = coroutineDispatchers,
|
||||
clipboardHelper = clipboardHelper,
|
||||
htmlConverterProvider = FakeHtmlConverterProvider(),
|
||||
buildMeta = aBuildMeta(),
|
||||
timelineController = TimelineController(joinedRoom, timeline),
|
||||
permalinkParser = permalinkParser,
|
||||
encryptionService = encryptionService,
|
||||
analyticsService = analyticsService,
|
||||
encryptionService = encryptionService,
|
||||
featureFlagService = featureFlagService,
|
||||
addRecentEmoji = addRecentEmoji,
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
@@ -32,7 +33,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
internal fun Indicator(
|
||||
internal fun ConnectivityIndicator(
|
||||
verticalPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
@@ -40,7 +42,7 @@ internal fun Indicator(
|
||||
.fillMaxWidth()
|
||||
.background(ElementTheme.colors.bgSubtlePrimary)
|
||||
.statusBarsPadding()
|
||||
.padding(vertical = 6.dp),
|
||||
.padding(vertical = verticalPadding),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
@@ -61,6 +63,6 @@ internal fun Indicator(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun IndicatorPreview() = ElementPreview {
|
||||
Indicator()
|
||||
internal fun ConnectivityIndicatorPreview() = ElementPreview {
|
||||
ConnectivityIndicator(verticalPadding = 6.dp)
|
||||
}
|
||||
@@ -16,55 +16,58 @@ import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
private val INDICATOR_VERTICAL_PADDING = 6.dp
|
||||
|
||||
/**
|
||||
* A view that displays a connectivity indicator when the device is offline, passing the padding
|
||||
* needed to make sure the status bar is not overlapped to its content views.
|
||||
* A view that displays a connectivity indicator when the device is offline.
|
||||
*/
|
||||
@Composable
|
||||
fun ConnectivityIndicatorContainer(
|
||||
isOnline: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (topPadding: Dp) -> Unit = {},
|
||||
content: @Composable (Modifier) -> Unit = {},
|
||||
) {
|
||||
val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline }
|
||||
|
||||
val statusBarTopPadding = if (LocalInspectionMode.current) {
|
||||
// Needed to get valid UI previews
|
||||
24.dp
|
||||
} else {
|
||||
WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 6.dp
|
||||
}
|
||||
val target = remember(isIndicatorVisible.targetState, statusBarTopPadding) {
|
||||
if (!isIndicatorVisible.targetState) 0.dp else statusBarTopPadding
|
||||
}
|
||||
val animationStateOffset by animateDpAsState(
|
||||
targetValue = target,
|
||||
animationSpec = spring(
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
visibilityThreshold = 1.dp,
|
||||
),
|
||||
label = "insets-animation",
|
||||
)
|
||||
|
||||
content(animationStateOffset)
|
||||
|
||||
// Display the network indicator with an animation
|
||||
AnimatedVisibility(
|
||||
visibleState = isIndicatorVisible,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
Indicator(modifier)
|
||||
Column(modifier = modifier) {
|
||||
val statusBarTopPadding = if (LocalInspectionMode.current) {
|
||||
// Needed to get valid UI previews
|
||||
24.dp
|
||||
} else {
|
||||
WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + INDICATOR_VERTICAL_PADDING
|
||||
}
|
||||
val target = if (isIndicatorVisible.targetState) statusBarTopPadding else 0.dp
|
||||
val topWindowInset by animateDpAsState(
|
||||
targetValue = target,
|
||||
animationSpec = spring(
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
visibilityThreshold = 1.dp,
|
||||
),
|
||||
label = "insets-animation",
|
||||
)
|
||||
// Display the network indicator with an animation
|
||||
AnimatedVisibility(
|
||||
visibleState = isIndicatorVisible,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
ConnectivityIndicator(verticalPadding = INDICATOR_VERTICAL_PADDING)
|
||||
}
|
||||
// Consume the window insets to avoid double padding.
|
||||
content(
|
||||
Modifier.consumeWindowInsets(PaddingValues(top = topWindowInset))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.networkmonitor.api.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
/**
|
||||
* A view that displays a connectivity indicator when the device is offline, adding a default
|
||||
* padding to make sure the status bar is not overlapped.
|
||||
*/
|
||||
@Composable
|
||||
fun ConnectivityIndicatorView(
|
||||
isOnline: Boolean,
|
||||
) {
|
||||
val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline }
|
||||
val isStatusBarPaddingVisible = remember { MutableTransitionState(isOnline) }.apply { targetState = isOnline }
|
||||
|
||||
// Display the network indicator with an animation
|
||||
AnimatedVisibility(
|
||||
visibleState = isIndicatorVisible,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
Indicator()
|
||||
}
|
||||
|
||||
// Show missing status bar padding when the indicator is not visible
|
||||
AnimatedVisibility(
|
||||
visibleState = isStatusBarPaddingVisible,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
StatusBarPaddingSpacer()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusBarPaddingSpacer(modifier: Modifier = Modifier) {
|
||||
Spacer(modifier = modifier.statusBarsPadding())
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ConnectivityIndicatorViewPreview() {
|
||||
ElementPreview {
|
||||
ConnectivityIndicatorView(isOnline = false)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user