Merge branch 'develop' into feature/bma/noWarnings

This commit is contained in:
Benoit Marty 2023-07-21 12:17:50 +02:00 committed by GitHub
commit 5e2e03f054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
102 changed files with 1091 additions and 292 deletions

View file

@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.LocationSearching
import androidx.compose.material.icons.filled.MyLocation
import androidx.compose.material3.ExperimentalMaterial3Api
@ -49,6 +48,7 @@ import io.element.android.features.location.api.Location
import io.element.android.features.location.api.internal.centerBottomEdge
import io.element.android.features.location.api.internal.rememberTileStyleUrl
import io.element.android.features.location.impl.MapDefaults
import io.element.android.features.location.impl.R
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.DayNightPreviews
@ -156,7 +156,11 @@ fun SendLocationView(
navigateUp()
},
leadingContent = {
Icon(Icons.Default.LocationOn, null)
Icon(
resourceId = R.drawable.pin_small,
contentDescription = null,
tint = Color.Unspecified,
)
},
)
Spacer(modifier = Modifier.height(16.dp + navBarPadding))

View file

@ -50,6 +50,13 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
isTrackMyLocation = false,
eventSink = {},
),
ShowLocationState(
Location(1.23, 2.34, 4f),
description = "For some reason I decided to to write a small essay that wraps at just two lines!",
hasLocationPermission = false,
isTrackMyLocation = false,
eventSink = {},
),
ShowLocationState(
Location(1.23, 2.34, 4f),
description = "For some reason I decided to write a small essay in the location description. " +

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26dp"
android:height="28dp"
android:viewportWidth="26"
android:viewportHeight="28">
<path
android:pathData="M12.962,28L9.819,24.889L16.105,24.889L12.962,28Z"
android:fillColor="#EBEEF2"/>
<path
android:pathData="M12.963,12.963m-12.963,0a12.963,12.963 0,1 1,25.926 0a12.963,12.963 0,1 1,-25.926 0"
android:fillColor="#EBEEF2"/>
<group>
<clip-path
android:pathData="M6.74,6.74h12.444v12.444h-12.444z"/>
<path
android:pathData="M12.962,6.74C10.554,6.74 8.606,8.741 8.606,11.215C8.606,13.88 11.357,17.555 12.489,18.955C12.738,19.262 13.192,19.262 13.441,18.955C14.567,17.555 17.318,13.88 17.318,11.215C17.318,8.741 15.37,6.74 12.962,6.74ZM12.962,12.813C12.103,12.813 11.406,12.097 11.406,11.215C11.406,10.333 12.103,9.617 12.962,9.617C13.821,9.617 14.518,10.333 14.518,11.215C14.518,12.097 13.821,12.813 12.962,12.813Z"
android:fillColor="#101317"/>
</group>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26dp"
android:height="28dp"
android:viewportWidth="26"
android:viewportHeight="28">
<path
android:pathData="M12.962,28L9.819,24.889L16.105,24.889L12.962,28Z"
android:fillColor="#1B1D22"/>
<path
android:pathData="M12.963,12.963m-12.963,0a12.963,12.963 0,1 1,25.926 0a12.963,12.963 0,1 1,-25.926 0"
android:fillColor="#1B1D22"/>
<group>
<clip-path
android:pathData="M6.74,6.741h12.444v12.444h-12.444z"/>
<path
android:pathData="M12.962,6.741C10.554,6.741 8.606,8.741 8.606,11.215C8.606,13.88 11.357,17.555 12.489,18.955C12.738,19.262 13.192,19.262 13.441,18.955C14.567,17.555 17.318,13.88 17.318,11.215C17.318,8.741 15.37,6.741 12.962,6.741ZM12.962,12.813C12.103,12.813 11.406,12.097 11.406,11.215C11.406,10.333 12.103,9.617 12.962,9.617C13.821,9.617 14.518,10.333 14.518,11.215C14.518,12.097 13.821,12.813 12.962,12.813Z"
android:fillColor="#ffffff"/>
</group>
</vector>

View file

@ -83,7 +83,7 @@ internal fun AttachmentsBottomSheet(
onDismissRequest = { isVisible = false }
) {
AttachmentSourcePickerMenu(
eventSink = state.eventSink,
state = state,
onSendLocationClicked = onSendLocationClicked,
)
}
@ -93,7 +93,7 @@ internal fun AttachmentsBottomSheet(
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun AttachmentSourcePickerMenu(
eventSink: (MessageComposerEvents) -> Unit,
state: MessageComposerState,
onSendLocationClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -102,33 +102,35 @@ internal fun AttachmentSourcePickerMenu(
// .navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044
) {
ListItem(
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) },
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) },
icon = { Icon(Icons.Default.Collections, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) },
)
ListItem(
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) },
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) },
icon = { Icon(Icons.Default.AttachFile, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_files)) },
)
ListItem(
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) },
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) },
icon = { Icon(Icons.Default.PhotoCamera, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) },
)
ListItem(
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) },
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) },
icon = { Icon(Icons.Default.Videocam, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) },
)
ListItem(
modifier = Modifier.clickable {
eventSink(MessageComposerEvents.PickAttachmentSource.Location)
onSendLocationClicked()
},
icon = { Icon(Icons.Default.LocationOn, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_location)) },
)
if (state.canShareLocation) {
ListItem(
modifier = Modifier.clickable {
state.eventSink(MessageComposerEvents.PickAttachmentSource.Location)
onSendLocationClicked()
},
icon = { Icon(Icons.Default.LocationOn, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_location)) },
)
}
}
}
@ -136,7 +138,9 @@ internal fun AttachmentSourcePickerMenu(
@Composable
internal fun AttachmentSourcePickerMenuPreview() = ElementPreview {
AttachmentSourcePickerMenu(
eventSink = {},
state = aMessageComposerState(
canShareLocation = true,
),
onSendLocationClicked = {},
)
}

View file

@ -74,6 +74,11 @@ class MessageComposerPresenter @Inject constructor(
mutableStateOf<AttachmentsState>(AttachmentsState.None)
}
val canShareLocation = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
canShareLocation.value = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)
}
val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType ->
handlePickedMedia(attachmentsState, uri, mimeType)
}
@ -140,23 +145,23 @@ class MessageComposerPresenter @Inject constructor(
)
)
}
MessageComposerEvents.AddAttachment -> localCoroutineScope.launchIfMediaPickerEnabled {
MessageComposerEvents.AddAttachment -> localCoroutineScope.launch {
showAttachmentSourcePicker = true
}
MessageComposerEvents.DismissAttachmentMenu -> showAttachmentSourcePicker = false
MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launchIfMediaPickerEnabled {
MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launch {
showAttachmentSourcePicker = false
galleryMediaPicker.launch()
}
MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launchIfMediaPickerEnabled {
MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launch {
showAttachmentSourcePicker = false
filesPicker.launch()
}
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launchIfMediaPickerEnabled {
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch {
showAttachmentSourcePicker = false
cameraPhotoPicker.launch()
}
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launchIfMediaPickerEnabled {
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
showAttachmentSourcePicker = false
cameraVideoPicker.launch()
}
@ -173,17 +178,12 @@ class MessageComposerPresenter @Inject constructor(
hasFocus = hasFocus.value,
mode = messageComposerContext.composerMode,
showAttachmentSourcePicker = showAttachmentSourcePicker,
canShareLocation = canShareLocation.value,
attachmentsState = attachmentsState.value,
eventSink = ::handleEvents
)
}
private fun CoroutineScope.launchIfMediaPickerEnabled(action: suspend () -> Unit) = launch {
if (featureFlagService.isFeatureEnabled(FeatureFlags.ShowMediaUploadingFlow)) {
action()
}
}
private fun CoroutineScope.sendMessage(
text: String,
updateComposerMode: (newComposerMode: MessageComposerMode) -> Unit,

View file

@ -28,6 +28,7 @@ data class MessageComposerState(
val hasFocus: Boolean,
val mode: MessageComposerMode,
val showAttachmentSourcePicker: Boolean,
val canShareLocation: Boolean,
val attachmentsState: AttachmentsState,
val eventSink: (MessageComposerEvents) -> Unit
) {

View file

@ -26,12 +26,21 @@ open class MessageComposerStateProvider : PreviewParameterProvider<MessageCompos
)
}
fun aMessageComposerState() = MessageComposerState(
text = "",
isFullScreen = false,
hasFocus = false,
mode = MessageComposerMode.Normal(content = ""),
showAttachmentSourcePicker = false,
attachmentsState = AttachmentsState.None,
eventSink = {}
fun aMessageComposerState(
text: String = "",
isFullScreen: Boolean = false,
hasFocus: Boolean = false,
mode: MessageComposerMode = MessageComposerMode.Normal(content = ""),
showAttachmentSourcePicker: Boolean = false,
canShareLocation: Boolean = true,
attachmentsState: AttachmentsState = AttachmentsState.None,
) = MessageComposerState(
text = text,
isFullScreen = isFullScreen,
hasFocus = hasFocus,
mode = mode,
showAttachmentSourcePicker = showAttachmentSourcePicker,
canShareLocation = canShareLocation,
attachmentsState = attachmentsState,
eventSink = {},
)

View file

@ -117,6 +117,7 @@ private fun TextContent(
.height(reactionEmojiLineHeight.toDp()),
text = text,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.materialColors.primary
)
@Composable
@ -126,7 +127,7 @@ private fun IconContent(
) = Icon(
imageVector = imageVector,
contentDescription = stringResource(id = R.string.screen_room_timeline_add_reaction),
tint = MaterialTheme.colorScheme.secondary,
tint = ElementTheme.materialColors.secondary,
modifier = modifier
.size(reactionEmojiLineHeight.toDp())
)

View file

@ -291,7 +291,7 @@ private fun TimelineItemEventRowContent(
if (event.reactionsState.reactions.isNotEmpty()) {
TimelineItemReactions(
reactionsState = event.reactionsState,
mainAxisAlignment = if (event.isMine) FlowMainAxisAlignment.End else FlowMainAxisAlignment.Start,
isOutgoing = event.isMine,
onReactionClicked = onReactionClicked,
onMoreReactionsClicked = { onMoreReactionsClicked(event) },
modifier = Modifier

View file

@ -0,0 +1,208 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.timeline.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddReaction
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import io.element.android.features.messages.impl.R
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.ElementPreview
/**
* A flow layout for reactions that will show a collapse/expand button when the layout wraps over a defined number of rows.
* It displays an add more button when there are greater than 0 reactions and always displays the reaction and add more button
* on the same row (moving them both to a new row if necessary).
* @param expandButton The expand button
* @param addMoreButton The add more button
* @param modifier The modifier to apply to this layout
* @param itemSpacing The horizontal spacing between items
* @param rowSpacing The vertical spacing between rows
* @param expanded Whether the layout should display in expanded or collapsed state
* @param rowsBeforeCollapsible The number of rows before the collapse/expand button is shown
* @param reactions The reaction buttons
*/
@Composable
fun TimelineItemReactionsLayout(
expandButton: @Composable () -> Unit,
addMoreButton: @Composable () -> Unit,
modifier: Modifier = Modifier,
itemSpacing: Dp = 0.dp,
rowSpacing: Dp = 0.dp,
expanded: Boolean = false,
rowsBeforeCollapsible: Int? = 2,
reactions: @Composable () -> Unit,
) {
SubcomposeLayout(modifier) { constraints ->
// Given the placeables and returns a structure representing
// how they should wrap on to multiple rows given the constraints max width.
fun calculateRows(measurables: List<Placeable>): List<List<Placeable>> {
val rows = mutableListOf<List<Placeable>>()
var currentRow = mutableListOf<Placeable>()
var rowX = 0
measurables.forEach { placeable ->
val horizontalSpacing = if (currentRow.isEmpty()) 0 else itemSpacing.toPx().toInt()
// If the current view does not fit on this row bump to the next
if (rowX + placeable.width > constraints.maxWidth) {
rows.add(currentRow)
currentRow = mutableListOf()
rowX = 0
}
rowX += horizontalSpacing + placeable.width
currentRow.add(placeable)
}
// If there are items in the current row remember to append it to the returned value
if (currentRow.size > 0) {
rows.add(currentRow)
}
return rows
}
// Used to render the collapsed state, this takes the rows inputted and adds the extra button to the last row,
// removing only as many trailing reactions as needed to make space for it.
fun replaceTrailingItemsWithButtons(rowsIn: List<List<Placeable>>, expandButton: Placeable, addMoreButton: Placeable): List<List<Placeable>> {
val rows = rowsIn.toMutableList()
val lastRow = rows.last()
val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + addMoreButton.width
var rowX = 0
lastRow.forEachIndexed { i, placeable ->
val horizontalSpacing = if (i == 0) 0 else itemSpacing.toPx().toInt()
rowX += placeable.width + horizontalSpacing
if (rowX > (constraints.maxWidth - (buttonsWidth + horizontalSpacing))) {
val lastRowWithButton = lastRow.take(i) + listOf(expandButton, addMoreButton)
rows[rows.size - 1] = lastRowWithButton
return rows
}
}
val lastRowWithButton = lastRow + listOf(expandButton, addMoreButton)
rows[rows.size - 1] = lastRowWithButton
return rows
}
// To prevent the add more and expand buttons from wrapping on to separate lines.
// If there is one item on the last line, it moves the expand button down.
fun ensureCollapseAndAddMoreButtonsAreOnTheSameRow(rowsIn: List<List<Placeable>>): List<List<Placeable>> {
val lastRow = rowsIn.last().toMutableList()
if (lastRow.size != 1) {
return rowsIn
}
val rows = rowsIn.toMutableList()
val secondLastRow = rows[rows.size - 2].toMutableList()
val expandButtonPlaceable = secondLastRow.removeLast()
lastRow.add(0, expandButtonPlaceable)
rows[rows.size - 2] = secondLastRow
rows[rows.size - 1] = lastRow
return rows
}
/// Given a list of rows place them in the layout.
fun layoutRows(rows: List<List<Placeable>>): MeasureResult {
var width = 0
var height = 0
val placeables = rows.mapIndexed { i, row ->
var rowX = 0
var rowHeight = 0
val verticalSpacing = if (i == 0) 0 else rowSpacing.toPx().toInt()
val rowWithPoints = row.mapIndexed { j, placeable ->
val horizontalSpacing = if (j == 0) 0 else itemSpacing.toPx().toInt()
val point = IntOffset(rowX + horizontalSpacing, height + verticalSpacing)
rowX += placeable.width + horizontalSpacing
rowHeight = maxOf(rowHeight, placeable.height)
Pair(placeable, point)
}
height += rowHeight + verticalSpacing
width = maxOf(width, rowX)
rowWithPoints
}.flatten()
return layout(width = width, height = height) {
placeables.forEach {
val (placeable, origin) = it
placeable.placeRelative(origin.x, origin.y)
}
}
}
val reactionsPlaceables = subcompose(0, reactions).map { it.measure(constraints) }
if (reactionsPlaceables.isEmpty()) {
return@SubcomposeLayout layoutRows(listOf())
}
val addMorePlaceable = subcompose(1, addMoreButton).first().measure(constraints)
val expandPlaceable = subcompose(2, expandButton).first().measure(constraints)
// Calculate the layout of the rows with the reactions button and add more button
val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOf(addMorePlaceable))
// If we have extended beyond the defined number of rows we are showing the expand/collapse ui
if (rowsBeforeCollapsible?.let { reactionsAndAddMore.size > it } == true) {
if (expanded) {
// Show all subviews with the add more button at the end
var reactionsAndButtons = calculateRows(reactionsPlaceables + listOf(expandPlaceable, addMorePlaceable))
reactionsAndButtons = ensureCollapseAndAddMoreButtonsAreOnTheSameRow(reactionsAndButtons)
layoutRows(reactionsAndButtons)
} else {
// Truncate to `rowsBeforeCollapsible` number of rows and replace the reactions at the end of the last row with the buttons
val collapsedRows = reactionsAndAddMore.take(rowsBeforeCollapsible)
val collapsedRowsWithButtons = replaceTrailingItemsWithButtons(collapsedRows, expandPlaceable, addMorePlaceable)
layoutRows(collapsedRowsWithButtons)
}
} else {
// Otherwise we are just showing all items without the expand button
layoutRows(reactionsAndAddMore)
}
}
}
@DayNightPreviews
@Composable
internal fun TimelineItemReactionsLayoutPreview() = ElementPreview {
TimelineItemReactionsLayout(
expanded = false,
expandButton = {
MessagesReactionButton(
content = MessagesReactionsButtonContent.Text(
text = stringResource(id = R.string.screen_room_timeline_less_reactions)
),
onClick = { },
)
},
addMoreButton = {
MessagesReactionButton(
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
onClick = {}
)
},
reactions = {
io.element.android.features.messages.impl.timeline.aTimelineItemReactions(count = 18).reactions.forEach {
MessagesReactionButton(
content = MessagesReactionsButtonContent.Reaction(
it
),
onClick = {}
)
}
}
)
}

View file

@ -19,18 +19,16 @@ package io.element.android.features.messages.impl.timeline.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddReaction
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.google.accompanist.flowlayout.FlowMainAxisAlignment
import com.google.accompanist.flowlayout.FlowRow
import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
@ -38,162 +36,119 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemReac
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.ElementPreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
/**
* The maximum number of items that can be displayed before some items will be hidden
*
* TODO The threshold should be based on the number of rows, rather than items.
* Once items would spill onto a third row, they should be hidden.
* Note this could be particularly worthwhile to handle reactions that are
* longer than a single character (as annotation keys are free text).
*/
private const val COLLAPSE_ITEMS_THRESHOLD = 8
@Composable
fun TimelineItemReactions(
reactionsState: TimelineItemReactions,
mainAxisAlignment: FlowMainAxisAlignment,
isOutgoing: Boolean,
onReactionClicked: (emoji: String) -> Unit,
onMoreReactionsClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
var expanded: Boolean by rememberSaveable { mutableStateOf(false) }
val reactions by remember(reactionsState, expanded) {
derivedStateOf {
val numToDisplay = if (expanded) {
reactionsState.reactions.count()
} else {
COLLAPSE_ITEMS_THRESHOLD
}
reactionsState.reactions.take(numToDisplay).toPersistentList()
}
// In LTR languages we want an incoming message's reactions to be LRT and outgoing to be RTL.
// For RTL languages it should be the opposite.
val reactionsLayoutDirection = if (!isOutgoing) LocalLayoutDirection.current
else if (LocalLayoutDirection.current == LayoutDirection.Ltr)
LayoutDirection.Rtl
else
LayoutDirection.Ltr
CompositionLocalProvider(LocalLayoutDirection provides reactionsLayoutDirection) {
TimelineItemReactionsView(
modifier = modifier,
reactions = reactionsState.reactions,
expanded = expanded,
onReactionClick = onReactionClicked,
onMoreReactionsClick = onMoreReactionsClicked,
onToggleExpandClick = { expanded = !expanded },
)
}
val expandableState by remember {
derivedStateOf {
if (expanded) {
ExpandableState.Expanded
} else {
val hiddenItems = reactionsState.reactions.count() - reactions.count()
if (hiddenItems > 0) {
ExpandableState.Collapsed(hidden = hiddenItems)
} else {
ExpandableState.None
}
}
}
}
TimelineItemReactionsView(
modifier = modifier,
reactions = reactions,
expandableState = expandableState,
mainAxisAlignment = mainAxisAlignment,
onReactionClick = onReactionClicked,
onMoreReactionsClick = onMoreReactionsClicked,
onExpandClick = { expanded = true },
onCollapseClick = { expanded = false }
)
}
private sealed class ExpandableState {
object None: ExpandableState()
data class Collapsed(val hidden: Int): ExpandableState()
object Expanded : ExpandableState()
}
@Composable
private fun TimelineItemReactionsView(
reactions: ImmutableList<AggregatedReaction>,
expandableState: ExpandableState,
mainAxisAlignment: FlowMainAxisAlignment,
expanded: Boolean,
onReactionClick: (emoji: String) -> Unit,
onMoreReactionsClick: () -> Unit,
onExpandClick: () -> Unit,
onCollapseClick: () -> Unit,
onToggleExpandClick: () -> Unit,
modifier: Modifier = Modifier
) =
FlowRow(
modifier = modifier,
mainAxisSpacing = 4.dp,
crossAxisSpacing = 4.dp,
mainAxisAlignment = mainAxisAlignment,
) {
) = TimelineItemReactionsLayout(
modifier = modifier,
itemSpacing = 4.dp,
rowSpacing = 4.dp,
expanded = expanded,
expandButton = {
MessagesReactionButton(
content = MessagesReactionsButtonContent.Text(
text = stringResource(id = if (expanded) R.string.screen_room_reactions_show_less else R.string.screen_room_reactions_show_more)
),
onClick = onToggleExpandClick,
)
},
addMoreButton = {
MessagesReactionButton(
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
onClick = onMoreReactionsClick
)
},
reactions = {
reactions.forEach { reaction ->
MessagesReactionButton(
content = MessagesReactionsButtonContent.Reaction(reaction = reaction),
onClick = { onReactionClick(reaction.key) }
)
}
when (expandableState) {
ExpandableState.Expanded ->
MessagesReactionButton(
content = MessagesReactionsButtonContent.Text(
text = stringResource(id = R.string.screen_room_timeline_less_reactions)
),
onClick = onCollapseClick,
)
is ExpandableState.Collapsed -> {
val hidden = expandableState.hidden
MessagesReactionButton(
content = MessagesReactionsButtonContent.Text(
text = pluralStringResource(id = R.plurals.screen_room_timeline_more_reactions, hidden, hidden)
),
onClick = onExpandClick,
)
}
ExpandableState.None -> {
// No expand or collapse action available
}
}
MessagesReactionButton(
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
onClick = onMoreReactionsClick
)
}
)
@DayNightPreviews
@Composable
fun TimelineItemReactionsViewPreview() = ElementPreview {
ContentToPreview(
reactions = aTimelineItemReactions(count = 1).reactions,
expandableState = ExpandableState.None,
reactions = aTimelineItemReactions(count = 1).reactions
)
}
@DayNightPreviews
@Composable
fun TimelineItemReactionsViewCollapsedPreview() = ElementPreview {
fun TimelineItemReactionsViewFewPreview() = ElementPreview {
ContentToPreview(
reactions = aTimelineItemReactions(count = 3).reactions,
expandableState = ExpandableState.Collapsed(hidden = 7),
reactions = aTimelineItemReactions(count = 3).reactions
)
}
@DayNightPreviews
@Composable
fun TimelineItemReactionsViewExpandedPreview() = ElementPreview {
fun TimelineItemReactionsViewIncomingPreview() = ElementPreview {
ContentToPreview(
reactions = aTimelineItemReactions(count = 10).reactions,
expandableState = ExpandableState.Expanded,
reactions = aTimelineItemReactions(count = 18).reactions
)
}
@DayNightPreviews
@Composable
fun TimelineItemReactionsViewOutgoingPreview() = ElementPreview {
ContentToPreview(
reactions = aTimelineItemReactions(count = 18).reactions,
isOutgoing = true
)
}
@Composable
private fun ContentToPreview(
reactions: ImmutableList<AggregatedReaction>,
expandableState: ExpandableState
isOutgoing: Boolean = false
) {
TimelineItemReactionsView(
reactions = reactions,
expandableState = expandableState,
mainAxisAlignment = FlowMainAxisAlignment.Center,
onReactionClick = {},
onMoreReactionsClick = {},
onExpandClick = {},
onCollapseClick = {}
TimelineItemReactions(
reactionsState = TimelineItemReactions(
reactions
),
isOutgoing = isOutgoing,
onReactionClicked = {},
onMoreReactionsClicked = {},
)
}

View file

@ -81,6 +81,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.roomId).isEqualTo(A_ROOM_ID)
}
@ -94,6 +95,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
assertThat(room.myReactions.count()).isEqualTo(1)
@ -113,6 +115,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
assertThat(room.myReactions.count()).isEqualTo(1)
@ -129,6 +132,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
@ -144,6 +148,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
@ -157,6 +162,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
val finalState = awaitItem()
@ -171,6 +177,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
@ -185,6 +192,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
val mediaMessage = aMessageEvent(
content = TimelineItemImageContent(
@ -215,6 +223,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
val mediaMessage = aMessageEvent(
content = TimelineItemVideoContent(
@ -246,6 +255,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
val mediaMessage = aMessageEvent(
content = TimelineItemFileContent(
@ -272,6 +282,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
val finalState = awaitItem()
@ -288,6 +299,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Redact, aMessageEvent()))
assertThat(matrixRoom.redactEventEventIdParam).isEqualTo(AN_EVENT_ID)
@ -302,6 +314,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent()))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
@ -315,6 +328,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.Dismiss)
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
@ -328,6 +342,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Developer, aMessageEvent()))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
@ -342,6 +357,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
// Initially the composer doesn't have focus, so we don't show the alert
assertThat(initialState.showReinvitePrompt).isFalse()
@ -363,6 +379,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.showReinvitePrompt).isFalse()
initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true))
@ -378,6 +395,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.showReinvitePrompt).isFalse()
initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true))
@ -401,6 +419,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
skipItems(1)
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
@ -429,6 +448,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
skipItems(1)
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
@ -449,6 +469,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
skipItems(1)
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
@ -476,6 +497,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
skipItems(1)
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
@ -495,6 +517,7 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
}
}
@ -509,7 +532,7 @@ class MessagesPresenterTest {
}.test {
// Default value
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
skipItems(1)
skipItems(2)
assertThat(awaitItem().userHasPermissionToSendMessage).isFalse()
}
}

View file

@ -68,7 +68,7 @@ class MessageComposerPresenterTest {
givenResult(mockk()) // Uri is not available in JVM, so the only way to have a non-null Uri is using Mockk
}
private val featureFlagService = FakeFeatureFlagService(
mapOf(FeatureFlags.ShowMediaUploadingFlow.key to true)
mapOf(FeatureFlags.LocationSharing.key to true)
)
private val mediaPreProcessor = FakeMediaPreProcessor()
private val snackbarDispatcher = SnackbarDispatcher()
@ -81,11 +81,13 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.isFullScreen).isFalse()
assertThat(initialState.text).isEqualTo("")
assertThat(initialState.mode).isEqualTo(MessageComposerMode.Normal(""))
assertThat(initialState.showAttachmentSourcePicker).isFalse()
assertThat(initialState.canShareLocation).isTrue()
assertThat(initialState.attachmentsState).isEqualTo(AttachmentsState.None)
assertThat(initialState.isSendButtonVisible).isFalse()
}
@ -97,6 +99,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState)
val fullscreenState = awaitItem()
@ -113,6 +116,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE))
val withMessageState = awaitItem()
@ -131,6 +135,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
var state = awaitItem()
val mode = anEditMode()
state.eventSink.invoke(MessageComposerEvents.SetMode(mode))
@ -149,6 +154,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
var state = awaitItem()
val mode = aReplyMode()
state.eventSink.invoke(MessageComposerEvents.SetMode(mode))
@ -166,6 +172,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
var state = awaitItem()
val mode = aQuoteMode()
state.eventSink.invoke(MessageComposerEvents.SetMode(mode))
@ -183,6 +190,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE))
val withMessageState = awaitItem()
@ -205,6 +213,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.text).isEqualTo("")
val mode = anEditMode()
@ -236,6 +245,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.text).isEqualTo("")
val mode = anEditMode(eventId = null, transactionId = A_TRANSACTION_ID)
@ -267,6 +277,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.text).isEqualTo("")
val mode = aReplyMode()
@ -294,6 +305,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.showAttachmentSourcePicker).isEqualTo(false)
initialState.eventSink(MessageComposerEvents.AddAttachment)
@ -307,6 +319,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.AddAttachment)
skipItems(1)
@ -341,6 +354,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery)
val previewingState = awaitItem()
@ -375,6 +389,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery)
val previewingState = awaitItem()
@ -393,6 +408,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery)
// No crashes here, otherwise it fails
@ -413,6 +429,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles)
val sendingState = awaitItem()
@ -434,6 +451,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera)
val previewingState = awaitItem()
@ -450,6 +468,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera)
val previewingState = awaitItem()
@ -467,6 +486,7 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles)
val sendingState = awaitItem()

View file

@ -6,7 +6,7 @@
android_gradle_plugin = "8.0.2"
kotlin = "1.8.22"
ksp = "1.8.22-1.0.11"
molecule = "1.0.0"
molecule = "1.1.0"
# AndroidX
material = "1.9.0"

View file

@ -22,16 +22,8 @@ enum class FeatureFlags(
override val description: String? = null,
override val defaultValue: Boolean = true
) : Feature {
CollapseRoomStateEvents(
key = "feature.collapseroomstateevents",
title = "Collapse room state events",
),
ShowStartChatFlow(
key = "feature.showstartchatflow",
title = "Show start chat flow",
),
ShowMediaUploadingFlow(
key = "feature.showmediauploadingflow",
title = "Show media uploading flow",
LocationSharing(
key = "feature.locationsharing",
title = "Allow user to share location",
)
}

View file

@ -29,9 +29,7 @@ class BuildtimeFeatureFlagProvider @Inject constructor() :
override suspend fun isFeatureEnabled(feature: Feature): Boolean {
return if (feature is FeatureFlags) {
when (feature) {
FeatureFlags.CollapseRoomStateEvents -> false
FeatureFlags.ShowStartChatFlow -> false
FeatureFlags.ShowMediaUploadingFlow -> false
FeatureFlags.LocationSharing -> true
}
} else {
false

View file

@ -26,14 +26,14 @@ class DefaultFeatureFlagServiceTest {
@Test
fun `given service without provider when feature is checked then it returns the default value`() = runTest {
val featureFlagService = DefaultFeatureFlagService(emptySet())
val isFeatureEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow)
assertThat(isFeatureEnabled).isEqualTo(FeatureFlags.ShowStartChatFlow.defaultValue)
val isFeatureEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)
assertThat(isFeatureEnabled).isEqualTo(FeatureFlags.LocationSharing.defaultValue)
}
@Test
fun `given service without provider when set enabled feature is called then it returns false`() = runTest {
val featureFlagService = DefaultFeatureFlagService(emptySet())
val result = featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true)
val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true)
assertThat(result).isEqualTo(false)
}
@ -41,7 +41,7 @@ class DefaultFeatureFlagServiceTest {
fun `given service with a runtime provider when set enabled feature is called then it returns true`() = runTest {
val featureFlagProvider = FakeRuntimeFeatureFlagProvider(0)
val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider))
val result = featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true)
val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true)
assertThat(result).isEqualTo(true)
}
@ -49,10 +49,10 @@ class DefaultFeatureFlagServiceTest {
fun `given service with a runtime provider and feature enabled when feature is checked then it returns the correct value`() = runTest {
val featureFlagProvider = FakeRuntimeFeatureFlagProvider(0)
val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider))
featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true)
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow)).isEqualTo(true)
featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, false)
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow)).isEqualTo(false)
featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true)
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(true)
featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, false)
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(false)
}
@Test
@ -60,8 +60,8 @@ class DefaultFeatureFlagServiceTest {
val lowPriorityfeatureFlagProvider = FakeRuntimeFeatureFlagProvider(LOW_PRIORITY)
val highPriorityfeatureFlagProvider = FakeRuntimeFeatureFlagProvider(HIGH_PRIORITY)
val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityfeatureFlagProvider, highPriorityfeatureFlagProvider))
lowPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, false)
highPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true)
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow)).isEqualTo(true)
lowPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, false)
highPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, true)
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(true)
}
}

View file

@ -33,6 +33,7 @@ import io.element.android.libraries.push.api.notifications.NotificationDrawerMan
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState
import io.element.android.services.appnavstate.api.currentSessionId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -74,9 +75,16 @@ class DefaultNotificationDrawerManager @Inject constructor(
}
}
private var currentAppNavigationState: NavigationState? = null
private fun onAppNavigationStateChange(navigationState: NavigationState) {
when (navigationState) {
NavigationState.Root -> {}
NavigationState.Root -> {
currentAppNavigationState?.currentSessionId()?.let { sessionId ->
// User signed out, clear all notifications related to the session.
clearAllEvents(sessionId)
}
}
is NavigationState.Session -> {}
is NavigationState.Space -> {}
is NavigationState.Room -> {
@ -91,6 +99,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
)
}
}
currentAppNavigationState = navigationState
}
private fun createInitialNotificationState(): NotificationState {
@ -131,12 +140,21 @@ class DefaultNotificationDrawerManager @Inject constructor(
/**
* Clear all known events and refresh the notification drawer.
*/
fun clearAllEvents(sessionId: SessionId) {
fun clearAllMessagesEvents(sessionId: SessionId) {
updateEvents {
it.clearMessagesForSession(sessionId)
}
}
/**
* Clear all notifications related to the session and refresh the notification drawer.
*/
fun clearAllEvents(sessionId: SessionId) {
updateEvents {
it.clearAllForSession(sessionId)
}
}
/**
* Should be called when the application is currently opened and showing timeline for the given roomId.
* Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room.

View file

@ -52,7 +52,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
defaultNotificationDrawerManager.clearMessagesForRoom(sessionId, roomId)
}
actionIds.dismissSummary ->
defaultNotificationDrawerManager.clearAllEvents(sessionId)
defaultNotificationDrawerManager.clearAllMessagesEvents(sessionId)
actionIds.dismissInvite -> if (roomId != null) {
defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId)
}

View file

@ -154,6 +154,11 @@ data class NotificationEventQueue constructor(
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId }
}
fun clearAllForSession(sessionId: SessionId) {
Timber.d("clearAllForSession $sessionId")
queue.removeAll { it.sessionId == sessionId }
}
fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) {
Timber.d("clearMessageEventOfRoom $sessionId, $roomId")
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId }

View file

@ -143,8 +143,8 @@
<string name="error_failed_loading_map">"%1$s could not load the map. Please try again later."</string>
<string name="error_failed_loading_messages">"Failed loading messages"</string>
<string name="error_failed_locating_user">"%1$s could not access your location. Please try again later."</string>
<string name="error_missing_location_auth_android">"To send a location, allow %1$s to access your location from its settings screen."</string>
<string name="error_missing_location_rationale_android">"To send a location, allow %1$s to access your location in the next dialog."</string>
<string name="error_missing_location_auth_android">"%1$s does not have permission to access your location. You can enable access in Settings."</string>
<string name="error_missing_location_rationale_android">"%1$s does not have permission to access your location. Enable access below."</string>
<string name="error_some_messages_have_not_been_sent">"Some messages have not been sent"</string>
<string name="error_unknown">"Sorry, an error occurred"</string>
<string name="invite_friends_rich_title">"🔐️ Join me on %1$s"</string>

View file

@ -17,10 +17,18 @@
import org.gradle.api.JavaVersion
import org.gradle.jvm.toolchain.JavaLanguageVersion
object Versions {
const val versionCode = 100200
const val versionName = "0.2.0"
// Note: 2 digits max for each value
private const val versionMajor = 0
private const val versionMinor = 1
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
private const val versionPatch = 2
object Versions {
val versionCode = (versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch) * 10
val versionName = "$versionMajor.$versionMinor.$versionPatch"
const val compileSdk = 33
const val targetSdk = 33
const val minSdk = 23

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:69535debd585127a4ce8b490ef6682c2e6c3c4d16478e6b9e9687ee1c1133637
size 20879
oid sha256:84581aac943c5065f1e5438465ee7d1845555e68aa005d8b596542ce3830dc83
size 21258

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:356756de9f08042c3c2f3033d3f8a39cd9b49c5cfbcfbc274933c3efedd80d3d
size 34534
oid sha256:bb559f5cd8b391ab4406c903a59376061255142317b035bd084f153779036eb9
size 36589

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:51ac3c4bb27d78419f73b99cb24327514ce56a64a03bf74ce41f158c2c3bd516
size 33605
oid sha256:ae73b980357c9def721335ecd69ffb8d921963d422e38c84735f53b6f4a596f2
size 35101

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:69535debd585127a4ce8b490ef6682c2e6c3c4d16478e6b9e9687ee1c1133637
size 20879
oid sha256:84581aac943c5065f1e5438465ee7d1845555e68aa005d8b596542ce3830dc83
size 21258

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1fa3b5aae9cee5e2fec8929697b0606b655c45b10a65bcd641da315e98c48e1e
size 20951
oid sha256:46ad7d3a46b54543f226e82fa4199e2e2de7e2e91748d663bed88dfb7afc5b61
size 21350

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce74fa2b0364763152e69dcfa4f8d598504b630fa1227f2fa685bc886ccd5afa
size 19434
oid sha256:6fb273e816484cb326ea4ba00948e749362542c1b964e23c5dc48b306b909136
size 19843

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:12a78fbc2d2e84e93d40d1e075d8b46c5366f3d323dd85f27be286a64231f884
size 32120
oid sha256:fac71b82a06f65e799ca345cc9b2e22af6c0000b9900bdcdc584ca6b0f6c2c4d
size 34178

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1bd4187a3713153d6e9ed94594385c3806f59c4d36cc6f2867a38d630551505
size 31302
oid sha256:e66b4ae355ae37f7810db1923c4018a44b1056c6471a3ed675032f036d30ff34
size 32685

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce74fa2b0364763152e69dcfa4f8d598504b630fa1227f2fa685bc886ccd5afa
size 19434
oid sha256:6fb273e816484cb326ea4ba00948e749362542c1b964e23c5dc48b306b909136
size 19843

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9390a3b8111ab0d31b3bbf3b6bf8794432d135130b37404ce4a646a74369d85b
size 19502
oid sha256:91378a7a126d1d9f9f234368930635ce85db3666721dc32b0646effa59d0fdee
size 19991

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c936d2d804bc9e98fcc49430f11ddaa572b05fc8d3a0df93ad6521ee8e78f708
size 21806
oid sha256:6d98aabf3bd99793367632e18d5cf678e75fb5ad872a1cb300a7e939ad0c2683
size 19725

View file

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

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0bb88c64bc68b10b1fb709135f445de5a5e4d78623448d0fef97504e025d5f6d
size 24551
oid sha256:94be795b626868e8afbcc26c3f0161ddcb946a122c02eeed7e823825c9aeba19
size 22263

View file

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

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:724bc9b0db8f581119201a8db3c405c9a7e7261cccea45a354fd37a5e33fcb41
size 11141
oid sha256:6c73a80923ec5b269bbb5f5108dde9617dac39e9c51f9199ab9229c4dd7ca2b0
size 11532

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:109a1a9c6359a9fd7fe4258f022e845c9b8142f9e590a03e5c2efa9273cb572f
size 10742
oid sha256:9eee3a5873231554cc0335fe7b552a62459f35e4ffb8c43b91a9746b34314bdc
size 11171

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:38c6d3f4a47ed89c3deb4150024b49c0a533a859f21a1592a82309b8c7316ea4
size 152242
oid sha256:85f9960cebee2e04d09f3ef17bd81e9adf9f463b24edae73a56d6d0c0a09ce48
size 152228

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f82840398e396e038ad68a5fe855394a0088c413e7aade339998b8ed63fb1c09
size 157273
oid sha256:39226828ae899c8b765827cad36f9966ec80c0a39ed406c1257224a56255af9b
size 157243

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c5ef7519aef3badeda3983b2ec5474f31404bc06b4b46b9829848dc7884fe1d
size 81749
oid sha256:88bdceab1a4e44f971ce507b5b6aeb5513657dfe6c4d61ec5483e215d83254d5
size 81534

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9e9b769422e4714e87ace97058e5a8f064e3b927e3a7c6042494de8381d6b09
size 85856
oid sha256:461683659d023323184049e122d02e4c63f217157e0bb3165f50be833d19ba7e
size 85517

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4970830b042e7b6588c03bf632ae3ade5b39de7339e27618ee341cbd9861c0e9
size 129351
oid sha256:c00bf46d1ef6337bad18f6454cf4601141ed2190c6a697ddc46d5a156d4997ca
size 127950

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4aba48e03e8424e5ff131c22fd5a606280745dd318f5822e2167f3b39794ff41
size 134569
oid sha256:88cd76c95c31061bf4e9e7528ed5d66a8b49813d868f21b2d2615e77318a9706
size 133068

View file

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

View file

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

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4e892d14d11fb576228418acd216468438f41a631a073e80638675ec48a91ed6
size 12334

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:64c2e18e97bba9ff6ae6bb24a17fe567352cd49f1b5905065b7de8854369e22f
size 12209

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d0d29cc0acc0d9009c6fd6aebccf72d6244cec8ec3e3bf3bd445e7cced52561
size 26004

View file

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

View file

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

View file

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

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54c12970e3563de958f88e4c538dd368f9810266060627393256a91741f7c6cf
size 53340
oid sha256:cfc4f0cc8e252cdc9200286cf330c4bbdf3cd66ced2c23364cca680aca79954d
size 53293

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3b7ae3084cb9d1ecee2e4db49c228516bdd7352683e797edf737c8d216922dec
size 65601
oid sha256:01c7f94b759eea738fb42baa4b8b835c27d48e3fbfdd40d0a925080db8196a80
size 65563

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dd034d439c08793e0dfd59f6bd5dcd88c06ded6cbe98172bf3cf296888e6d575
size 51244
oid sha256:e4360b6f5be55e5b468e735f83b6263ae3b0f1a3f7c0084e6dc3c07396723abd
size 51206

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9d835cb1a420117b4d967181e2ca0fad71ba243d6a17bea08b82cae41f6b8e2
size 68760
oid sha256:0a58bb336bbf56b05584d73fa7fd3bd2e2fe97c82829a01988343d4c3b4c77ab
size 68727

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4ad09278ae2ebb8171adba96d9f6e91d0cc4f120b0b2368087796dadb37eeb87
size 58539
oid sha256:ff2c7a701945b72afc6731c9da722e0bf178879f0bd80bc40da7ebb1d6e90d28
size 58505

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:082206122d4e6d9e6171b3b2444c576ff7bd47fb3946d8d98e2812654f39cd40
size 73641
oid sha256:4591b08ec297c0b024870e60ac18b03aadeaa8ff20a726dd991b18a80ec353ef
size 73607

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dc695bfc589e8e5a852eeb9b2828ce968d17519bb5be957cf2734a7d6d9cc356
size 89482
oid sha256:6586d62bdbd3d4331105f561669c5383d578ad8af559270a26c7d9ff741b473b
size 89412

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9a243d53d10ca2eaa249b22af8a9fddf1a8ccf60db4f3ad9374e31cf494fe878
size 54909
oid sha256:8981ec8fd5e68e6e332f98ee2c95fc0f902b9b912a05201cad1c6a9feb5dbc89
size 54868

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3fdd478be89b47fcaf9725ca14c1e775087f1ccf2a266f3476a537bbc2b29922
size 67476
oid sha256:a5e787ce25a3542e398bec3aadd2b9b923c81dee4f3fbf8936c1c15e5aa1816b
size 67450

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1a3b5bbcdd1593e81384b335045bdcb0b3e01782868993e9f6437a15ca39dbca
size 51380
oid sha256:1a8af5ff868170d946d03db115271e7ea9c0a15db35bf6815bf95cc4e4f7fa81
size 51357

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7e26db0a0a8d767d6e732c1d2742cba3c3475d761b0c3017ff01cee7e3d362a5
size 62773
oid sha256:438bc82c007c9cd72228f47681981e833645c591c0a48e8a5cb70bd51721d35a
size 62756

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3d5cae7d73178aecc12fbf5f1f27c9e66608b90d462deec862a881b403469e93
size 49475
oid sha256:a0926170ca4277e47f9ae49f5092c195cee2a84d67259ba8dc3aa3539d4faecf
size 49454

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe9456f4446104142221e28167a578a2b5cac772dc35aeb14352c0d422dd6fb0
size 65726
oid sha256:68ce3ada02e8ed6cb1e257e49d8c703bd0ff3401bf0e520fa617ed1ea91c6756
size 65691

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cad3b5ce023d890fd4a5ff0f5bbabb678bc6d5c76d3476e8053673c06f360c8b
oid sha256:c902787fb2d84a0e92832020cdd447fc039264fc5add63abea32241251c92bf7
size 56102

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:41a6c2dd81698696708802276b615a78ff22cc7cb8ae2d4b8d8cc862bfe44a24
size 70856
oid sha256:bc956e4fdab7bf15d71e10585947f9f5c24bc1d9e4eaa071de9b747fae879f53
size 70883

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:167f8368e0b94aa05e5d1cc30055e3b0c2c910752d719092ef2d34aae09dc832
size 84649
oid sha256:fdf1b66ffe0b7042356d22aa835ceea2c5b697c4ce62287b5ec040f528210216
size 84635

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:789ee5ccad8356198cdc0634b4e9a65ed44be2d26e7ce83a8662598c1bd8d4c2
size 52765
oid sha256:e438a2e542780851a7b570407e02f125e191d23b9c5299d9457baad78523de84
size 52740

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7b40a2e5d60a906d7c35c3ece1854671f5b319b00ad077322d094cbf906c07f7
size 64803
oid sha256:17f11b2cb93b8736a2d1224b7ad70c7e3a1a45bcb7e5ceb60475dba734e6efec
size 64787

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c724bc77185a9ceec2cf092cb1d7865b13718d5320bd7ac4850bb85590f05b2
size 52294
oid sha256:de7f21b5fcc1235a80ea2e7f9e26a93503ea2d0c0793a89db8657979afb33a7d
size 52267

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a303894134ed06348b609e41cf109dcafcd994c3dffabc6d9ab436fe92605245
size 53710
oid sha256:3d368f7cc84b5a8850577fac658aaeb89a1c83a2890b7fd393577c9cde919069
size 53689

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7523ec0d6defd7074af0c804fdde64fb8d421f6cfa729bc1c4f9858bd87c42d0
size 52554
oid sha256:56440b44cf610b0baaf7401bebb8ba2f4504b5dd36cb54fe1669d5deec6d1674
size 52530

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1f48089147f7e089abfa64254143a80857ccc9f840aa60403e1aabc67e2b6d51
size 55458
oid sha256:9387ab3e62a9e10721efdea3a6abcdf29db82a04ebdb3c4cefcad2a20ab9b9b8
size 55386

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7ff682ee8363d450bb76db72ea06deea87fa47692ce319b7dff315d2a10dfb6a
size 51033
oid sha256:c0e3bd4e37bb665df997f189e5c2dc763c7f703b78384c63737675bd764fe7a8
size 51174

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e84edf8adf1a89153dd45272d36e04561d66f2ea765ad9edecfd8d750ba99f97
size 54237
oid sha256:ade57e690313eaaeddc7b21b9a2661026fca70e6af1b09aa331bba3d1b1bbaf3
size 54240

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0bc9521bd1576d47ca6f643adf43ce3638d40328207d908ec863c72503d34f24
size 55682
oid sha256:89520fd2999582229ace8fd9304644fc74ce01230a4024b90b47ce1cd61eb564
size 55678

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c2209c3cc4e7de32ed92b3b0da4616b54d19091d43d21c0e4013728450d0d3f7
size 54595
oid sha256:19420aad469fad8a218a0a8cd0f7cfbb9593d7ceb799478a7ad9b84d9c24602f
size 54598

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d7bdd0ca39534b31c9d421e28337712b1cf2aaf841a766fcc6bdaf996c756bfa
size 57524
oid sha256:e224a21a1ae913066530e8be81760795b10be388cb7fa92fe399b1e11367d7af
size 57423

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ba349f81d5c417c612cae1263504ea5b4e83dc606ec20c3942368e8992b87ad4
size 52886
oid sha256:cac4b5bdb9f225c5313f36caef190e3f698b734db79707928572c942fed85603
size 52960

19
tools/gitflow/gitflow-init.sh Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env bash
#
# Copyright (c) 2023 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
git flow init -d
git config gitflow.prefix.versiontag v

Some files were not shown because too many files have changed in this diff Show more