Merge remote-tracking branch 'origin/develop' into bma/brandColorFix

This commit is contained in:
Benoit Marty 2025-10-22 12:27:00 +02:00
commit acdef75949
44 changed files with 159 additions and 267 deletions

View file

@ -56,6 +56,7 @@ import io.element.android.features.ftue.api.state.FtueState
import io.element.android.features.home.api.HomeEntryPoint import io.element.android.features.home.api.HomeEntryPoint
import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus 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.preferences.api.PreferencesEntryPoint
import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
@ -82,6 +83,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.UserId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias 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.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.SessionVerificationServiceListener
import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.matrix.api.verification.VerificationRequest
import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@ -128,6 +130,7 @@ class LoggedInFlowNode(
private val sessionEnterpriseService: SessionEnterpriseService, private val sessionEnterpriseService: SessionEnterpriseService,
private val networkMonitor: NetworkMonitor, private val networkMonitor: NetworkMonitor,
private val notificationConversationService: NotificationConversationService, private val notificationConversationService: NotificationConversationService,
private val syncService: SyncService,
private val enterpriseService: EnterpriseService, private val enterpriseService: EnterpriseService,
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
@ -556,11 +559,17 @@ class LoggedInFlowNode(
compoundDark = colors.dark, compoundDark = colors.dark,
buildMeta = buildMeta, buildMeta = buildMeta,
) { ) {
Box(modifier = modifier) { val isOnline by syncService.isOnline.collectAsState()
val ftueState by ftueService.state.collectAsState() ConnectivityIndicatorContainer(
BackstackView() isOnline = isOnline,
if (ftueState is FtueState.Complete) { modifier = modifier,
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) ) { contentModifier ->
Box(modifier = contentModifier) {
val ftueState by ftueService.state.collectAsState()
BackstackView()
if (ftueState is FtueState.Complete) {
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
}
} }
} }
} }

View file

@ -9,7 +9,6 @@ package io.element.android.appnav.room
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope 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.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver 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.room.alias.ResolvedRoomAlias
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.LoadingRoomState
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -71,7 +69,6 @@ class RoomFlowNode(
private val client: MatrixClient, private val client: MatrixClient,
private val joinRoomEntryPoint: JoinRoomEntryPoint, private val joinRoomEntryPoint: JoinRoomEntryPoint,
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint, private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
private val syncService: SyncService,
private val membershipObserver: RoomMembershipObserver, private val membershipObserver: RoomMembershipObserver,
private val spaceEntryPoint: SpaceEntryPoint, private val spaceEntryPoint: SpaceEntryPoint,
) : BaseFlowNode<RoomFlowNode.NavTarget>( ) : BaseFlowNode<RoomFlowNode.NavTarget>(
@ -222,10 +219,8 @@ class RoomFlowNode(
} }
private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier -> private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier ->
val isOnline by syncService.isOnline.collectAsState()
LoadingRoomNodeView( LoadingRoomNodeView(
state = LoadingRoomState.Loading, state = LoadingRoomState.Loading,
hasNetworkConnection = isOnline,
onBackClick = { navigateUp() }, onBackClick = { navigateUp() },
modifier = modifier, modifier = modifier,
) )

View file

@ -35,7 +35,6 @@ import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId 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.LoadingRoomState
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
@ -50,7 +49,6 @@ class JoinedRoomFlowNode(
@Assisted val buildContext: BuildContext, @Assisted val buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory, loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory,
private val syncService: SyncService,
) : ) :
BaseFlowNode<JoinedRoomFlowNode.NavTarget>( BaseFlowNode<JoinedRoomFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
@ -116,12 +114,10 @@ class JoinedRoomFlowNode(
private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier -> private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier ->
val loadingRoomState by loadingRoomStateStateFlow.collectAsState() val loadingRoomState by loadingRoomStateStateFlow.collectAsState()
val isOnline by syncService.isOnline.collectAsState()
LoadingRoomNodeView( LoadingRoomNodeView(
state = loadingRoomState, state = loadingRoomState,
hasNetworkConnection = isOnline, onBackClick = onBackClick,
modifier = modifier, modifier = modifier
onBackClick = onBackClick
) )
} }

View file

@ -8,8 +8,6 @@
package io.element.android.appnav.room.joined package io.element.android.appnav.room.joined
import androidx.compose.foundation.layout.Box 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.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding 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.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme 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.atomic.molecules.IconTitlePlaceholdersRowMolecule
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
@ -38,17 +35,13 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun LoadingRoomNodeView( fun LoadingRoomNodeView(
state: LoadingRoomState, state: LoadingRoomState,
hasNetworkConnection: Boolean,
onBackClick: () -> Unit, onBackClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
Column { LoadingRoomTopBar(onBackClick)
ConnectivityIndicatorView(isOnline = hasNetworkConnection)
LoadingRoomTopBar(onBackClick)
}
}, },
content = { padding -> content = { padding ->
Box( Box(
@ -85,7 +78,6 @@ private fun LoadingRoomTopBar(
title = { title = {
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp) 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 { internal fun LoadingRoomNodeViewPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) = ElementPreview {
LoadingRoomNodeView( LoadingRoomNodeView(
state = state, state = state,
onBackClick = {}, onBackClick = {}
hasNetworkConnection = false
) )
} }

View file

@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState 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.roomlist.RoomListState
import io.element.android.features.home.impl.search.RoomListSearchView import io.element.android.features.home.impl.search.RoomListSearchView
import io.element.android.features.home.impl.spaces.HomeSpacesView 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.androidutils.throttler.FirstThrottler
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -84,56 +82,47 @@ fun HomeView(
val state: RoomListState = homeState.roomListState val state: RoomListState = homeState.roomListState
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val firstThrottler = remember { FirstThrottler(300, coroutineScope) } val firstThrottler = remember { FirstThrottler(300, coroutineScope) }
Box(modifier) {
ConnectivityIndicatorContainer( if (state.contextMenu is RoomListState.ContextMenu.Shown) {
modifier = modifier, RoomListContextMenu(
isOnline = homeState.hasNetworkConnection, contextMenu = state.contextMenu,
) { topPadding -> canReportRoom = state.canReportRoom,
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,
eventSink = state.eventSink, eventSink = state.eventSink,
hideInvitesAvatars = state.hideInvitesAvatars, onRoomSettingsClick = onRoomSettingsClick,
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, onReportRoomClick = onReportRoomClick,
modifier = Modifier
.statusBarsPadding()
.padding(top = topPadding)
.fillMaxSize()
.background(ElementTheme.colors.bgCanvasDefault)
) )
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()
} }
} }

View file

@ -20,7 +20,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -51,7 +50,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.copy
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -157,7 +155,6 @@ private fun RoomListSearchContent(
focusRequester.saveFocusedChild() focusRequester.saveFocusedChild()
} }
}, },
windowInsets = TopAppBarDefaults.windowInsets.copy(top = 0)
) )
} }
) { padding -> ) { padding ->

View file

@ -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.canRedactOther
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn 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.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.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.messages.reply.map
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
@ -112,7 +111,6 @@ class MessagesPresenter(
private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>, private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>,
private val roomCallStatePresenter: Presenter<RoomCallState>, private val roomCallStatePresenter: Presenter<RoomCallState>,
private val roomMemberModerationPresenter: Presenter<RoomMemberModerationState>, private val roomMemberModerationPresenter: Presenter<RoomMemberModerationState>,
private val syncService: SyncService,
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val clipboardHelper: ClipboardHelper, private val clipboardHelper: ClipboardHelper,
@ -193,7 +191,6 @@ class MessagesPresenter(
showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L
} }
} }
val isOnline by syncService.isOnline.collectAsState()
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
@ -250,8 +247,8 @@ class MessagesPresenter(
roomName = roomInfo.name, roomName = roomInfo.name,
roomAvatar = roomAvatar, roomAvatar = roomAvatar,
heroes = heroes, heroes = heroes,
composerState = composerState,
userEventPermissions = userEventPermissions, userEventPermissions = userEventPermissions,
composerState = composerState,
voiceMessageComposerState = voiceMessageComposerState, voiceMessageComposerState = voiceMessageComposerState,
timelineState = timelineState, timelineState = timelineState,
timelineProtectionState = timelineProtectionState, timelineProtectionState = timelineProtectionState,
@ -261,19 +258,17 @@ class MessagesPresenter(
customReactionState = customReactionState, customReactionState = customReactionState,
reactionSummaryState = reactionSummaryState, reactionSummaryState = reactionSummaryState,
readReceiptBottomSheetState = readReceiptBottomSheetState, readReceiptBottomSheetState = readReceiptBottomSheetState,
hasNetworkConnection = isOnline,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
showReinvitePrompt = showReinvitePrompt,
inviteProgress = inviteProgress.value, inviteProgress = inviteProgress.value,
showReinvitePrompt = showReinvitePrompt,
enableTextFormatting = MessageComposerConfig.ENABLE_RICH_TEXT_EDITING, enableTextFormatting = MessageComposerConfig.ENABLE_RICH_TEXT_EDITING,
appName = buildMeta.applicationName,
roomCallState = roomCallState, roomCallState = roomCallState,
appName = buildMeta.applicationName,
pinnedMessagesBannerState = pinnedMessagesBannerState, pinnedMessagesBannerState = pinnedMessagesBannerState,
dmUserVerificationState = dmUserVerificationState, dmUserVerificationState = dmUserVerificationState,
roomMemberModerationState = roomMemberModerationState, roomMemberModerationState = roomMemberModerationState,
successorRoom = roomInfo.successorRoom, successorRoom = roomInfo.successorRoom
eventSink = { handleEvents(it) } ) { handleEvents(it) }
)
} }
@Composable @Composable

View file

@ -44,7 +44,6 @@ data class MessagesState(
val customReactionState: CustomReactionState, val customReactionState: CustomReactionState,
val reactionSummaryState: ReactionSummaryState, val reactionSummaryState: ReactionSummaryState,
val readReceiptBottomSheetState: ReadReceiptBottomSheetState, val readReceiptBottomSheetState: ReadReceiptBottomSheetState,
val hasNetworkConnection: Boolean,
val snackbarMessage: SnackbarMessage?, val snackbarMessage: SnackbarMessage?,
val inviteProgress: AsyncData<Unit>, val inviteProgress: AsyncData<Unit>,
val showReinvitePrompt: Boolean, val showReinvitePrompt: Boolean,

View file

@ -56,7 +56,6 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
override val values: Sequence<MessagesState> override val values: Sequence<MessagesState>
get() = sequenceOf( get() = sequenceOf(
aMessagesState(), aMessagesState(),
aMessagesState(hasNetworkConnection = false),
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)), aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)), aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
aMessagesState(showReinvitePrompt = true), aMessagesState(showReinvitePrompt = true),
@ -108,7 +107,6 @@ fun aMessagesState(
actionListState: ActionListState = anActionListState(), actionListState: ActionListState = anActionListState(),
customReactionState: CustomReactionState = aCustomReactionState(), customReactionState: CustomReactionState = aCustomReactionState(),
reactionSummaryState: ReactionSummaryState = aReactionSummaryState(), reactionSummaryState: ReactionSummaryState = aReactionSummaryState(),
hasNetworkConnection: Boolean = true,
showReinvitePrompt: Boolean = false, showReinvitePrompt: Boolean = false,
roomCallState: RoomCallState = aStandByCallState(), roomCallState: RoomCallState = aStandByCallState(),
pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(), pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(),
@ -132,7 +130,6 @@ fun aMessagesState(
actionListState = actionListState, actionListState = actionListState,
customReactionState = customReactionState, customReactionState = customReactionState,
reactionSummaryState = reactionSummaryState, reactionSummaryState = reactionSummaryState,
hasNetworkConnection = hasNetworkConnection,
snackbarMessage = null, snackbarMessage = null,
inviteProgress = AsyncData.Uninitialized, inviteProgress = AsyncData.Uninitialized,
showReinvitePrompt = showReinvitePrompt, showReinvitePrompt = showReinvitePrompt,

View file

@ -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.topbars.ThreadTopBar
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessagePermissionRationaleDialog 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.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.androidutils.ui.hideKeyboard
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout 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.BottomSheetDragHandle
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text 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.KeepScreenOn
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
@ -123,6 +123,8 @@ fun MessagesView(
KeepScreenOn(state.voiceMessageComposerState.keepScreenOn) KeepScreenOn(state.voiceMessageComposerState.keepScreenOn)
HideKeyboardWhenDisposed()
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) 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 // 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( Scaffold(
contentWindowInsets = WindowInsets.statusBars, contentWindowInsets = WindowInsets.statusBars,
topBar = { topBar = {
Column {
ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
if (state.timelineState.timelineMode is Timeline.Mode.Thread) { if (state.timelineState.timelineMode is Timeline.Mode.Thread) {
ThreadTopBar( ThreadTopBar(
roomName = state.roomName, roomName = state.roomName,
@ -203,7 +203,6 @@ fun MessagesView(
onJoinCallClick = onJoinCallClick, onJoinCallClick = onJoinCallClick,
) )
} }
}
}, },
content = { padding -> content = { padding ->
Box( Box(

View file

@ -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.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo 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.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.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails 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)) .isEqualTo(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))
assertThat(initialState.userEventPermissions.canSendMessage).isTrue() assertThat(initialState.userEventPermissions.canSendMessage).isTrue()
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue() assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
assertThat(initialState.hasNetworkConnection).isTrue()
assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.snackbarMessage).isNull()
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
assertThat(initialState.showReinvitePrompt).isFalse() assertThat(initialState.showReinvitePrompt).isFalse()
@ -1274,31 +1272,30 @@ class MessagesPresenterTest {
addRecentEmoji: AddRecentEmoji = AddRecentEmoji(FakeMatrixClient(), testCoroutineDispatchers()), addRecentEmoji: AddRecentEmoji = AddRecentEmoji(FakeMatrixClient(), testCoroutineDispatchers()),
): MessagesPresenter { ): MessagesPresenter {
return MessagesPresenter( return MessagesPresenter(
navigator = navigator,
room = joinedRoom, room = joinedRoom,
composerPresenter = messageComposerPresenter, composerPresenter = messageComposerPresenter,
voiceMessageComposerPresenterFactory = FakeDefaultVoiceMessageComposerPresenterFactory(backgroundScope), voiceMessageComposerPresenterFactory = FakeDefaultVoiceMessageComposerPresenterFactory(backgroundScope),
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) }, timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
timelineProtectionPresenter = { aTimelineProtectionState() }, timelineProtectionPresenter = { aTimelineProtectionState() },
identityChangeStatePresenter = { anIdentityChangeState() },
linkPresenter = { aLinkState() },
actionListPresenter = { anActionListState(eventSink = actionListEventSink) }, actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
customReactionPresenter = { aCustomReactionState() }, customReactionPresenter = { aCustomReactionState() },
reactionSummaryPresenter = { aReactionSummaryState() }, reactionSummaryPresenter = { aReactionSummaryState() },
readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() }, readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() },
identityChangeStatePresenter = { anIdentityChangeState() },
linkPresenter = { aLinkState() },
pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() }, pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() },
roomCallStatePresenter = { aStandByCallState() }, roomCallStatePresenter = { aStandByCallState() },
roomMemberModerationPresenter = roomMemberModerationPresenter, roomMemberModerationPresenter = roomMemberModerationPresenter,
syncService = FakeSyncService(),
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
navigator = navigator,
clipboardHelper = clipboardHelper,
buildMeta = aBuildMeta(),
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,
clipboardHelper = clipboardHelper,
htmlConverterProvider = FakeHtmlConverterProvider(), htmlConverterProvider = FakeHtmlConverterProvider(),
buildMeta = aBuildMeta(),
timelineController = TimelineController(joinedRoom, timeline), timelineController = TimelineController(joinedRoom, timeline),
permalinkParser = permalinkParser, permalinkParser = permalinkParser,
encryptionService = encryptionService,
analyticsService = analyticsService, analyticsService = analyticsService,
encryptionService = encryptionService,
featureFlagService = featureFlagService, featureFlagService = featureFlagService,
addRecentEmoji = addRecentEmoji, addRecentEmoji = addRecentEmoji,
) )

View file

@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme 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 import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
internal fun Indicator( internal fun ConnectivityIndicator(
verticalPadding: Dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Row( Row(
@ -40,7 +42,7 @@ internal fun Indicator(
.fillMaxWidth() .fillMaxWidth()
.background(ElementTheme.colors.bgSubtlePrimary) .background(ElementTheme.colors.bgSubtlePrimary)
.statusBarsPadding() .statusBarsPadding()
.padding(vertical = 6.dp), .padding(vertical = verticalPadding),
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@ -61,6 +63,6 @@ internal fun Indicator(
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun IndicatorPreview() = ElementPreview { internal fun ConnectivityIndicatorPreview() = ElementPreview {
Indicator() ConnectivityIndicator(verticalPadding = 6.dp)
} }

View file

@ -16,55 +16,58 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically 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.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBars
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.unit.Dp
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 * A view that displays a connectivity indicator when the device is offline.
* needed to make sure the status bar is not overlapped to its content views.
*/ */
@Composable @Composable
fun ConnectivityIndicatorContainer( fun ConnectivityIndicatorContainer(
isOnline: Boolean, isOnline: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
content: @Composable (topPadding: Dp) -> Unit = {}, content: @Composable (Modifier) -> Unit = {},
) { ) {
val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline } val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline }
Column(modifier = modifier) {
val statusBarTopPadding = if (LocalInspectionMode.current) { val statusBarTopPadding = if (LocalInspectionMode.current) {
// Needed to get valid UI previews // Needed to get valid UI previews
24.dp 24.dp
} else { } else {
WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 6.dp WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + INDICATOR_VERTICAL_PADDING
} }
val target = remember(isIndicatorVisible.targetState, statusBarTopPadding) { val target = if (isIndicatorVisible.targetState) statusBarTopPadding else 0.dp
if (!isIndicatorVisible.targetState) 0.dp else statusBarTopPadding val topWindowInset by animateDpAsState(
} targetValue = target,
val animationStateOffset by animateDpAsState( animationSpec = spring(
targetValue = target, stiffness = Spring.StiffnessMediumLow,
animationSpec = spring( visibilityThreshold = 1.dp,
stiffness = Spring.StiffnessMediumLow, ),
visibilityThreshold = 1.dp, label = "insets-animation",
), )
label = "insets-animation", // Display the network indicator with an animation
) AnimatedVisibility(
visibleState = isIndicatorVisible,
content(animationStateOffset) enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
// Display the network indicator with an animation ) {
AnimatedVisibility( ConnectivityIndicator(verticalPadding = INDICATOR_VERTICAL_PADDING)
visibleState = isIndicatorVisible, }
enter = fadeIn() + expandVertically(), // Consume the window insets to avoid double padding.
exit = fadeOut() + shrinkVertically(), content(
) { Modifier.consumeWindowInsets(PaddingValues(top = topWindowInset))
Indicator(modifier) )
} }
} }

View file

@ -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)
}
}

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:513b6de9643106b0c021a60a2791e6227ce81ff1089338a5d3de8ba525e353ab oid sha256:c9583a29e680bee98819258e5b7356dd8677144957d5869da72c47685d986e87
size 8389 size 6494

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7aebe39a70a79e025d392ffb6c97793c828cc29acf7700461399e6cbea0d27a3 oid sha256:f5c19a8b26c9187c9bfd630242b15370ec8f0b874f9d3913ce53c743c0e0bdcd
size 10512 size 8574

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:89bd61aa2d74f720a2b1d73fa5379e712fc61e423cbcbfab83cb291bdbd75766 oid sha256:74ad11143fc70164f67a8326cb87ce90a2c8730317540810ea138e39fc9093fa
size 8013 size 6306

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:831058152c5c6e691afecab498ae07f4b35f7b75438a4efa1841cc04922bb7be oid sha256:bde612ade7298aeef44bbc480ee9d742ccb8c94108e21dcae9e804c744032a08
size 10037 size 8308

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:bc9668d1caef19e9a4fe76ff4ed0eab8107aa493c3adcaef7ebbbb878925ac62 oid sha256:6a249702f230f3dc0c9ee097fc11496df64c11436680e3f790d1fbf9051c4119
size 66408 size 65098

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5bfc8aff1305c39b400c95831a6b07f00c376c328b3ad2281cf4b6b74970f67c oid sha256:ea48147772f8fa99aca0345a5a98f8d2ea6fd26793edab55f7d920a7e3f56414
size 61538 size 60213

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:67981be91a4736680a69b9d2dfe11cab3a69e0d375cf2589e38bed3630b9fc12
size 53199

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4dd9bacf4f467ac0f2b1050fcf5a4246e520674009471c05d2e32eb5d1d1f7bf oid sha256:3917d9d2d5c67e840e613cc8dfc39212e11853ac8710e87af554e20dea31d34d
size 57599 size 39439

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3917d9d2d5c67e840e613cc8dfc39212e11853ac8710e87af554e20dea31d34d oid sha256:432fae13ece80f287afc16411ca162c2ea0b19d66b148adcb13c7710ac442b5d
size 39439 size 60866

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:432fae13ece80f287afc16411ca162c2ea0b19d66b148adcb13c7710ac442b5d oid sha256:cc380e2b1c75c5b38ac410a271ddb9c02d41ebf9ff037fc59f20d23d87241590
size 60866 size 56864

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:cc380e2b1c75c5b38ac410a271ddb9c02d41ebf9ff037fc59f20d23d87241590 oid sha256:0cb2168ef240a788416f0f7d5cf949b282b9c4cf76338c4d547562b15ff3dba1
size 56864 size 55322

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0cb2168ef240a788416f0f7d5cf949b282b9c4cf76338c4d547562b15ff3dba1 oid sha256:17ec77b5053bff9057d6b122140f53c5384abac7998bbc8d770db95a04bc0062
size 55322 size 59963

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:17ec77b5053bff9057d6b122140f53c5384abac7998bbc8d770db95a04bc0062 oid sha256:ac595ce33affb3eec6c465fd68ab1fa57eb942f58944cc16e5e01892a21ba4a7
size 59963 size 50323

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ac595ce33affb3eec6c465fd68ab1fa57eb942f58944cc16e5e01892a21ba4a7 oid sha256:2831b5841aac7cdec50829002aa46fabf530163e0600185f32da0793c3637fb5
size 50323 size 61695

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2831b5841aac7cdec50829002aa46fabf530163e0600185f32da0793c3637fb5 oid sha256:ac8dd9bc435999370adc7b3d4a0eb96c7eeedcb500818b44e05aafe759b720ba
size 61695 size 63860

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ac8dd9bc435999370adc7b3d4a0eb96c7eeedcb500818b44e05aafe759b720ba oid sha256:67981be91a4736680a69b9d2dfe11cab3a69e0d375cf2589e38bed3630b9fc12
size 63860 size 53199

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7fb56dba32bfc9db1ddf201d118c580b21143f1f74575bda95ff43fa0914b2a3
size 52344

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3e64f40aac075bd075f92cf98ffa864ced12b3a43a874d72cd5f293202859623 oid sha256:beb8fc32a4b048ba5c5053ad5d62da55aac8072234af59a2a1af6dd3b03be74c
size 55090 size 37401

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:beb8fc32a4b048ba5c5053ad5d62da55aac8072234af59a2a1af6dd3b03be74c oid sha256:d654cda7c0f4df547ccf00fc3a8121d0c5bd2b337be17b71f7b95e5987833032
size 37401 size 58473

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d654cda7c0f4df547ccf00fc3a8121d0c5bd2b337be17b71f7b95e5987833032 oid sha256:794ef2d62ae364d456930dc7879741848c8f65696a5b7676b032da70e3ef7330
size 58473 size 51156

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:794ef2d62ae364d456930dc7879741848c8f65696a5b7676b032da70e3ef7330 oid sha256:96bdb7695db3a0476480e4e675dfc5d088d210b519a3f6b9592bbe6ec18f4ec6
size 51156 size 52985

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:96bdb7695db3a0476480e4e675dfc5d088d210b519a3f6b9592bbe6ec18f4ec6 oid sha256:e12bc7c029daee03dc31bc889c37ebbd597ac61bf18bf13867a867983a79bb2b
size 52985 size 54085

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e12bc7c029daee03dc31bc889c37ebbd597ac61bf18bf13867a867983a79bb2b oid sha256:72023d9b9eaade788d33ef6c123a89edc079c4cc543421c256676b75fba6adc1
size 54085 size 44505

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:72023d9b9eaade788d33ef6c123a89edc079c4cc543421c256676b75fba6adc1 oid sha256:1ad3ae1b79bf30293e306ebac5806d328569f03b1370adb39c6dec143d8789c1
size 44505 size 59027

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1ad3ae1b79bf30293e306ebac5806d328569f03b1370adb39c6dec143d8789c1 oid sha256:83a7cce4a92980220d2afd24d895154d1a463e685c9f81ee91707ae78c12809d
size 59027 size 64594

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:83a7cce4a92980220d2afd24d895154d1a463e685c9f81ee91707ae78c12809d oid sha256:7fb56dba32bfc9db1ddf201d118c580b21143f1f74575bda95ff43fa0914b2a3
size 64594 size 52344

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7d7cbce14d66654ba69cd6a154eefecebacf1556f29c5ccefb1d6f1975270814
size 5427

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:14927029a06e7f4f5346573d5020fa7582ff716dd552db969bb84304a879d4f0
size 5384