Florian Renaud
2 years ago
committed by
GitHub
55 changed files with 1060 additions and 54 deletions
@ -0,0 +1 @@ |
|||||||
|
[Create and join rooms] Start new chat screen (UI) |
@ -0,0 +1,56 @@ |
|||||||
|
/* |
||||||
|
* 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed |
||||||
|
@Suppress("DSL_SCOPE_VIOLATION") |
||||||
|
plugins { |
||||||
|
id("io.element.android-compose-library") |
||||||
|
alias(libs.plugins.ksp) |
||||||
|
alias(libs.plugins.anvil) |
||||||
|
id("kotlin-parcelize") |
||||||
|
} |
||||||
|
|
||||||
|
android { |
||||||
|
namespace = "io.element.android.features.createroom" |
||||||
|
} |
||||||
|
|
||||||
|
anvil { |
||||||
|
generateDaggerFactories.set(true) |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
anvil(projects.anvilcodegen) |
||||||
|
implementation(projects.anvilannotations) |
||||||
|
|
||||||
|
implementation(projects.libraries.core) |
||||||
|
implementation(projects.libraries.architecture) |
||||||
|
implementation(projects.libraries.matrix.api) |
||||||
|
implementation(projects.libraries.matrixui) |
||||||
|
implementation(projects.libraries.designsystem) |
||||||
|
implementation(projects.libraries.elementresources) |
||||||
|
implementation(projects.libraries.uiStrings) |
||||||
|
|
||||||
|
testImplementation(libs.test.junit) |
||||||
|
testImplementation(libs.coroutines.test) |
||||||
|
testImplementation(libs.molecule.runtime) |
||||||
|
testImplementation(libs.test.truth) |
||||||
|
testImplementation(libs.test.turbine) |
||||||
|
testImplementation(projects.libraries.matrix.test) |
||||||
|
|
||||||
|
androidTestImplementation(libs.test.junitext) |
||||||
|
|
||||||
|
ksp(libs.showkase.processor) |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
# Add project specific ProGuard rules here. |
||||||
|
# You can control the set of applied configuration files using the |
||||||
|
# proguardFiles setting in build.gradle. |
||||||
|
# |
||||||
|
# For more details, see |
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html |
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following |
||||||
|
# and specify the fully qualified class name to the JavaScript interface |
||||||
|
# class: |
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||||
|
# public *; |
||||||
|
#} |
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for |
||||||
|
# debugging stack traces. |
||||||
|
#-keepattributes SourceFile,LineNumberTable |
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to |
||||||
|
# hide the original source file name. |
||||||
|
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,20 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!-- |
||||||
|
~ 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<manifest> |
||||||
|
|
||||||
|
</manifest> |
@ -0,0 +1,62 @@ |
|||||||
|
/* |
||||||
|
* 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.createroom |
||||||
|
|
||||||
|
import android.os.Parcelable |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import com.bumble.appyx.core.composable.Children |
||||||
|
import com.bumble.appyx.core.modality.BuildContext |
||||||
|
import com.bumble.appyx.core.node.Node |
||||||
|
import com.bumble.appyx.core.node.ParentNode |
||||||
|
import com.bumble.appyx.navmodel.backstack.BackStack |
||||||
|
import io.element.android.features.createroom.root.CreateRoomRootNode |
||||||
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler |
||||||
|
import io.element.android.libraries.architecture.createNode |
||||||
|
import kotlinx.parcelize.Parcelize |
||||||
|
|
||||||
|
class CreateRoomFlowNode( |
||||||
|
buildContext: BuildContext, |
||||||
|
private val backstack: BackStack<NavTarget> = BackStack( |
||||||
|
initialElement = NavTarget.Root, |
||||||
|
savedStateMap = buildContext.savedStateMap, |
||||||
|
), |
||||||
|
) : ParentNode<CreateRoomFlowNode.NavTarget>( |
||||||
|
navModel = backstack, |
||||||
|
buildContext = buildContext |
||||||
|
) { |
||||||
|
|
||||||
|
sealed interface NavTarget : Parcelable { |
||||||
|
@Parcelize |
||||||
|
object Root : NavTarget |
||||||
|
} |
||||||
|
|
||||||
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { |
||||||
|
return when (navTarget) { |
||||||
|
NavTarget.Root -> createNode<CreateRoomRootNode>(buildContext) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
override fun View(modifier: Modifier) { |
||||||
|
Children( |
||||||
|
navModel = backstack, |
||||||
|
modifier = modifier, |
||||||
|
transitionHandler = rememberDefaultTransitionHandler() |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* 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.createroom.root |
||||||
|
|
||||||
|
sealed interface CreateRoomRootEvents { |
||||||
|
object CreateRoom : CreateRoomRootEvents |
||||||
|
object InvitePeople : CreateRoomRootEvents |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
/* |
||||||
|
* 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.createroom.root |
||||||
|
|
||||||
|
import android.os.Parcelable |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import com.bumble.appyx.core.modality.BuildContext |
||||||
|
import com.bumble.appyx.core.node.Node |
||||||
|
import com.bumble.appyx.core.plugin.Plugin |
||||||
|
import dagger.assisted.Assisted |
||||||
|
import dagger.assisted.AssistedInject |
||||||
|
import io.element.android.anvilannotations.ContributesNode |
||||||
|
import io.element.android.libraries.di.SessionScope |
||||||
|
import kotlinx.parcelize.Parcelize |
||||||
|
|
||||||
|
@ContributesNode(SessionScope::class) |
||||||
|
class CreateRoomRootNode @AssistedInject constructor( |
||||||
|
@Assisted buildContext: BuildContext, |
||||||
|
@Assisted plugins: List<Plugin>, |
||||||
|
private val presenter: CreateRoomRootPresenter, |
||||||
|
) : Node(buildContext, plugins = plugins) { |
||||||
|
|
||||||
|
sealed interface NavTarget : Parcelable { |
||||||
|
@Parcelize |
||||||
|
object Root : NavTarget |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
override fun View(modifier: Modifier) { |
||||||
|
val state = presenter.present() |
||||||
|
CreateRoomRootScreen( |
||||||
|
state = state, |
||||||
|
modifier = modifier, |
||||||
|
onClosePressed = this::navigateUp, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/* |
||||||
|
* 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.createroom.root |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import io.element.android.libraries.architecture.Presenter |
||||||
|
import javax.inject.Inject |
||||||
|
|
||||||
|
class CreateRoomRootPresenter @Inject constructor() : Presenter<CreateRoomRootState> { |
||||||
|
|
||||||
|
@Composable |
||||||
|
override fun present(): CreateRoomRootState { |
||||||
|
|
||||||
|
fun handleEvents(event: CreateRoomRootEvents) { |
||||||
|
when (event) { |
||||||
|
CreateRoomRootEvents.CreateRoom -> Unit // Todo Handle create room action |
||||||
|
CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return CreateRoomRootState( |
||||||
|
eventSink = ::handleEvents |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,236 @@ |
|||||||
|
/* |
||||||
|
* 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.createroom.root |
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes |
||||||
|
import androidx.compose.foundation.clickable |
||||||
|
import androidx.compose.foundation.layout.Arrangement |
||||||
|
import androidx.compose.foundation.layout.Column |
||||||
|
import androidx.compose.foundation.layout.Row |
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth |
||||||
|
import androidx.compose.foundation.layout.heightIn |
||||||
|
import androidx.compose.foundation.layout.padding |
||||||
|
import androidx.compose.material.icons.Icons |
||||||
|
import androidx.compose.material.icons.filled.Close |
||||||
|
import androidx.compose.material.icons.filled.Search |
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api |
||||||
|
import androidx.compose.material3.SearchBarDefaults |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable |
||||||
|
import androidx.compose.runtime.setValue |
||||||
|
import androidx.compose.ui.Alignment |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.draw.alpha |
||||||
|
import androidx.compose.ui.graphics.Color |
||||||
|
import androidx.compose.ui.platform.LocalFocusManager |
||||||
|
import androidx.compose.ui.res.stringResource |
||||||
|
import androidx.compose.ui.text.font.FontWeight |
||||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import androidx.compose.ui.unit.sp |
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight |
||||||
|
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar |
||||||
|
import io.element.android.libraries.designsystem.theme.components.DockedSearchBar |
||||||
|
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.Scaffold |
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text |
||||||
|
import io.element.android.libraries.designsystem.R as DrawableR |
||||||
|
import io.element.android.libraries.ui.strings.R as StringR |
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||||
|
@Composable |
||||||
|
fun CreateRoomRootScreen( |
||||||
|
state: CreateRoomRootState, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
onClosePressed: () -> Unit = {}, |
||||||
|
) { |
||||||
|
var searchText by rememberSaveable { mutableStateOf("") } |
||||||
|
var isSearchActive by rememberSaveable { mutableStateOf(false) } |
||||||
|
Scaffold( |
||||||
|
modifier = modifier.fillMaxWidth(), |
||||||
|
topBar = { |
||||||
|
if (!isSearchActive) { |
||||||
|
CreateRoomRootViewTopBar(onClosePressed = onClosePressed) |
||||||
|
} |
||||||
|
} |
||||||
|
) { paddingValues -> |
||||||
|
Column( |
||||||
|
modifier = Modifier.padding(paddingValues), |
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp), |
||||||
|
) { |
||||||
|
CreateRoomSearchBar( |
||||||
|
modifier = Modifier.fillMaxWidth(), |
||||||
|
text = searchText, |
||||||
|
placeHolderTitle = stringResource(StringR.string.search_for_someone), |
||||||
|
active = isSearchActive, |
||||||
|
onActiveChanged = { isSearchActive = it }, |
||||||
|
onTextChanged = { searchText = it }, |
||||||
|
) |
||||||
|
|
||||||
|
if (!isSearchActive) { |
||||||
|
CreateRoomActionButtonsList( |
||||||
|
onNewRoomClicked = { state.eventSink(CreateRoomRootEvents.CreateRoom) }, |
||||||
|
onInvitePeopleClicked = { state.eventSink(CreateRoomRootEvents.InvitePeople) }, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||||
|
@Composable |
||||||
|
fun CreateRoomRootViewTopBar( |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
onClosePressed: () -> Unit = {}, |
||||||
|
) { |
||||||
|
CenterAlignedTopAppBar( |
||||||
|
modifier = modifier, |
||||||
|
title = { |
||||||
|
Text( |
||||||
|
text = stringResource(id = StringR.string.start_chat), |
||||||
|
fontSize = 16.sp, |
||||||
|
fontWeight = FontWeight.SemiBold, |
||||||
|
) |
||||||
|
}, |
||||||
|
actions = { |
||||||
|
IconButton(onClick = onClosePressed) { |
||||||
|
Icon(imageVector = Icons.Default.Close, contentDescription = stringResource(id = StringR.string.action_close)) |
||||||
|
} |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||||
|
@Composable |
||||||
|
fun CreateRoomSearchBar( |
||||||
|
text: String, |
||||||
|
placeHolderTitle: String, |
||||||
|
active: Boolean, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
onActiveChanged: (Boolean) -> Unit = {}, |
||||||
|
onTextChanged: (String) -> Unit = {}, |
||||||
|
) { |
||||||
|
val focusManager = LocalFocusManager.current |
||||||
|
|
||||||
|
if (!active) { |
||||||
|
onTextChanged("") |
||||||
|
focusManager.clearFocus() |
||||||
|
} |
||||||
|
|
||||||
|
DockedSearchBar( |
||||||
|
query = text, |
||||||
|
onQueryChange = onTextChanged, |
||||||
|
onSearch = { focusManager.clearFocus() }, |
||||||
|
active = active, |
||||||
|
onActiveChange = onActiveChanged, |
||||||
|
modifier = modifier |
||||||
|
.padding(horizontal = if (!active) 16.dp else 0.dp), |
||||||
|
placeholder = { |
||||||
|
Text( |
||||||
|
text = placeHolderTitle, |
||||||
|
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine) |
||||||
|
) |
||||||
|
}, |
||||||
|
leadingIcon = if (active) { |
||||||
|
{ BackButton(onClick = { onActiveChanged(false) }) } |
||||||
|
} else null, |
||||||
|
trailingIcon = { |
||||||
|
if (active) { |
||||||
|
IconButton(onClick = { onTextChanged("") }) { |
||||||
|
Icon(Icons.Default.Close, stringResource(StringR.string.a11y_clear)) |
||||||
|
} |
||||||
|
} else { |
||||||
|
Icon( |
||||||
|
imageVector = Icons.Default.Search, |
||||||
|
contentDescription = stringResource(StringR.string.search), |
||||||
|
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine) |
||||||
|
) |
||||||
|
} |
||||||
|
}, |
||||||
|
shape = if (!active) SearchBarDefaults.dockedShape else SearchBarDefaults.fullScreenShape, |
||||||
|
colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent), |
||||||
|
content = {}, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun CreateRoomActionButtonsList( |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
onNewRoomClicked: () -> Unit = {}, |
||||||
|
onInvitePeopleClicked: () -> Unit = {}, |
||||||
|
) { |
||||||
|
Column(modifier = modifier) { |
||||||
|
CreateRoomActionButton( |
||||||
|
iconRes = DrawableR.drawable.ic_groups, |
||||||
|
text = stringResource(id = StringR.string.new_room), |
||||||
|
onClick = onNewRoomClicked, |
||||||
|
) |
||||||
|
CreateRoomActionButton( |
||||||
|
iconRes = DrawableR.drawable.ic_share, |
||||||
|
text = stringResource(id = StringR.string.invite_people_menu), |
||||||
|
onClick = onInvitePeopleClicked, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun CreateRoomActionButton( |
||||||
|
@DrawableRes iconRes: Int, |
||||||
|
text: String, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
onClick: () -> Unit = {}, |
||||||
|
) { |
||||||
|
Row( |
||||||
|
modifier = modifier |
||||||
|
.fillMaxWidth() |
||||||
|
.heightIn(min = 56.dp) |
||||||
|
.clickable { onClick() } |
||||||
|
.padding(horizontal = 16.dp), |
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp), |
||||||
|
verticalAlignment = Alignment.CenterVertically, |
||||||
|
) { |
||||||
|
Icon( |
||||||
|
modifier = Modifier.alpha(0.5f), // FIXME align on Design system theme (removing alpha should be fine) |
||||||
|
resourceId = iconRes, |
||||||
|
contentDescription = null, |
||||||
|
) |
||||||
|
Text(text = text) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
fun CreateRoomRootViewLightPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) = |
||||||
|
ElementPreviewLight { ContentToPreview(state) } |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
fun CreateRoomRootViewDarkPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) = |
||||||
|
ElementPreviewDark { ContentToPreview(state) } |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun ContentToPreview(state: CreateRoomRootState) { |
||||||
|
CreateRoomRootScreen( |
||||||
|
state = state, |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
/* |
||||||
|
* 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.createroom.root |
||||||
|
|
||||||
|
// TODO add your ui models. Remove the eventSink if you don't have events. |
||||||
|
// Do not use default value, so no member get forgotten in the presenters. |
||||||
|
data class CreateRoomRootState( |
||||||
|
val eventSink: (CreateRoomRootEvents) -> Unit |
||||||
|
) |
@ -0,0 +1,31 @@ |
|||||||
|
/* |
||||||
|
* 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.createroom.root |
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider |
||||||
|
|
||||||
|
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> { |
||||||
|
override val values: Sequence<CreateRoomRootState> |
||||||
|
get() = sequenceOf( |
||||||
|
aCreateRoomRootState(), |
||||||
|
// Add other state here |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fun aCreateRoomRootState() = CreateRoomRootState( |
||||||
|
eventSink = {} |
||||||
|
) |
@ -0,0 +1,53 @@ |
|||||||
|
/* |
||||||
|
* 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class) |
||||||
|
|
||||||
|
package io.element.android.features.createroom.root |
||||||
|
|
||||||
|
import app.cash.molecule.RecompositionClock |
||||||
|
import app.cash.molecule.moleculeFlow |
||||||
|
import app.cash.turbine.test |
||||||
|
import com.google.common.truth.Truth.assertThat |
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||||
|
import kotlinx.coroutines.test.runTest |
||||||
|
import org.junit.Test |
||||||
|
|
||||||
|
class CreateRoomRootPresenterTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `present - initial state`() = runTest { |
||||||
|
val presenter = CreateRoomRootPresenter() |
||||||
|
moleculeFlow(RecompositionClock.Immediate) { |
||||||
|
presenter.present() |
||||||
|
}.test { |
||||||
|
val initialState = awaitItem() |
||||||
|
assertThat(initialState) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `present - send event`() = runTest { |
||||||
|
val presenter = CreateRoomRootPresenter() |
||||||
|
moleculeFlow(RecompositionClock.Immediate) { |
||||||
|
presenter.present() |
||||||
|
}.test { |
||||||
|
val initialState = awaitItem() |
||||||
|
initialState.eventSink(CreateRoomRootEvents.CreateRoom) // Not implemented yet |
||||||
|
initialState.eventSink(CreateRoomRootEvents.InvitePeople) // Not implemented yet |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
/* |
||||||
|
* 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
@file:OptIn(ExperimentalMaterial3Api::class) |
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.theme.components |
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.RowScope |
||||||
|
import androidx.compose.foundation.layout.WindowInsets |
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api |
||||||
|
import androidx.compose.material3.TopAppBarColors |
||||||
|
import androidx.compose.material3.TopAppBarDefaults |
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight |
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||||
|
@Composable |
||||||
|
fun CenterAlignedTopAppBar( |
||||||
|
title: @Composable () -> Unit, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
navigationIcon: @Composable () -> Unit = {}, |
||||||
|
actions: @Composable RowScope.() -> Unit = {}, |
||||||
|
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, |
||||||
|
colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(), |
||||||
|
scrollBehavior: TopAppBarScrollBehavior? = null, |
||||||
|
) { |
||||||
|
androidx.compose.material3.CenterAlignedTopAppBar( |
||||||
|
title = title, |
||||||
|
modifier = modifier, |
||||||
|
navigationIcon = navigationIcon, |
||||||
|
actions = actions, |
||||||
|
windowInsets = windowInsets, |
||||||
|
colors = colors, |
||||||
|
scrollBehavior = scrollBehavior, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
internal fun CenterAlignedTopAppBarLightPreview() = |
||||||
|
ElementPreviewLight { ContentToPreview() } |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
internal fun CenterAlignedTopAppBarDarkPreview() = |
||||||
|
ElementPreviewDark { ContentToPreview() } |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun ContentToPreview() { |
||||||
|
CenterAlignedTopAppBar(title = { Text(text = "Title") }) |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
/* |
||||||
|
* 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
@file:OptIn(ExperimentalMaterial3Api::class) |
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.theme.components |
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource |
||||||
|
import androidx.compose.foundation.layout.ColumnScope |
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api |
||||||
|
import androidx.compose.material3.SearchBarColors |
||||||
|
import androidx.compose.material3.SearchBarDefaults |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.graphics.Shape |
||||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||||
|
import androidx.compose.ui.unit.Dp |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight |
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||||
|
@Composable |
||||||
|
fun DockedSearchBar( |
||||||
|
query: String, |
||||||
|
onQueryChange: (String) -> Unit, |
||||||
|
onSearch: (String) -> Unit, |
||||||
|
active: Boolean, |
||||||
|
onActiveChange: (Boolean) -> Unit, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
enabled: Boolean = true, |
||||||
|
placeholder: @Composable (() -> Unit)? = null, |
||||||
|
leadingIcon: @Composable (() -> Unit)? = null, |
||||||
|
trailingIcon: @Composable (() -> Unit)? = null, |
||||||
|
shape: Shape = SearchBarDefaults.dockedShape, |
||||||
|
colors: SearchBarColors = SearchBarDefaults.colors(), |
||||||
|
tonalElevation: Dp = SearchBarDefaults.Elevation, |
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, |
||||||
|
content: @Composable ColumnScope.() -> Unit, |
||||||
|
) { |
||||||
|
androidx.compose.material3.DockedSearchBar( |
||||||
|
query = query, |
||||||
|
onQueryChange = onQueryChange, |
||||||
|
onSearch = onSearch, |
||||||
|
active = active, |
||||||
|
onActiveChange = onActiveChange, |
||||||
|
modifier = modifier, |
||||||
|
enabled = enabled, |
||||||
|
placeholder = placeholder, |
||||||
|
leadingIcon = leadingIcon, |
||||||
|
trailingIcon = trailingIcon, |
||||||
|
shape = shape, |
||||||
|
colors = colors, |
||||||
|
tonalElevation = tonalElevation, |
||||||
|
interactionSource = interactionSource, |
||||||
|
content = content, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
internal fun DockedSearchBarLightPreview() = ElementPreviewLight { ContentToPreview() } |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
internal fun DockedSearchBarDarkPreview() = ElementPreviewDark { ContentToPreview() } |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun ContentToPreview() { |
||||||
|
DockedSearchBar( |
||||||
|
query = "Some text", |
||||||
|
onQueryChange = {}, |
||||||
|
onSearch = {}, |
||||||
|
active = false, |
||||||
|
onActiveChange = {}, |
||||||
|
content = {}, |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* 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.libraries.designsystem.theme.components |
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke |
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource |
||||||
|
import androidx.compose.foundation.layout.Column |
||||||
|
import androidx.compose.foundation.layout.PaddingValues |
||||||
|
import androidx.compose.foundation.layout.RowScope |
||||||
|
import androidx.compose.material3.ButtonColors |
||||||
|
import androidx.compose.material3.ButtonDefaults |
||||||
|
import androidx.compose.material3.ButtonElevation |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.graphics.Shape |
||||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun TextButton( |
||||||
|
onClick: () -> Unit, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
enabled: Boolean = true, |
||||||
|
shape: Shape = ButtonDefaults.textShape, |
||||||
|
colors: ButtonColors = ButtonDefaults.textButtonColors(), |
||||||
|
elevation: ButtonElevation? = null, |
||||||
|
border: BorderStroke? = null, |
||||||
|
contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding, |
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, |
||||||
|
content: @Composable RowScope.() -> Unit |
||||||
|
) { |
||||||
|
androidx.compose.material3.TextButton( |
||||||
|
onClick = onClick, |
||||||
|
modifier = modifier, |
||||||
|
enabled = enabled, |
||||||
|
shape = shape, |
||||||
|
colors = colors, |
||||||
|
elevation = elevation, |
||||||
|
border = border, |
||||||
|
contentPadding = contentPadding, |
||||||
|
interactionSource = interactionSource, |
||||||
|
content = content, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
internal fun TextButtonLightPreview() = ElementPreviewLight { ContentToPreview() } |
||||||
|
|
||||||
|
@Preview |
||||||
|
@Composable |
||||||
|
internal fun TextButtonDarkPreview() = ElementPreviewDark { ContentToPreview() } |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun ContentToPreview() { |
||||||
|
Column { |
||||||
|
TextButton(onClick = {}, enabled = true) { |
||||||
|
Text(text = "Click me! - Enabled") |
||||||
|
} |
||||||
|
TextButton(onClick = {}, enabled = false) { |
||||||
|
Text(text = "Click me! - Disabled") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
<!-- |
||||||
|
~ 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:tint="#000000" |
||||||
|
android:width="21dp" |
||||||
|
android:height="22dp" |
||||||
|
android:viewportWidth="21" |
||||||
|
android:viewportHeight="22"> |
||||||
|
<path |
||||||
|
android:pathData="M1.5,21.7C1.1,21.7 0.75,21.55 0.45,21.25C0.15,20.95 0,20.6 0,20.2V5.2C0,4.8 0.15,4.45 0.45,4.15C0.75,3.85 1.1,3.7 1.5,3.7H11.625L10.125,5.2H1.5V20.2H16.5V11.5L18,10V20.2C18,20.6 17.85,20.95 17.55,21.25C17.25,21.55 16.9,21.7 16.5,21.7H1.5ZM13.55,3.9L14.625,4.95L7.5,12.05V14.2H9.625L16.775,7.05L17.825,8.1L10.7,15.25C10.567,15.383 10.404,15.492 10.212,15.575C10.021,15.658 9.825,15.7 9.625,15.7H6.75C6.533,15.7 6.354,15.629 6.213,15.488C6.071,15.346 6,15.167 6,14.95V12.075C6,11.875 6.042,11.679 6.125,11.488C6.208,11.296 6.317,11.133 6.45,11L13.55,3.9ZM17.825,8.1L13.55,3.9L16.05,1.4C16.333,1.117 16.688,0.975 17.112,0.975C17.538,0.975 17.892,1.125 18.175,1.425L20.275,3.55C20.558,3.85 20.7,4.204 20.7,4.613C20.7,5.021 20.55,5.367 20.25,5.65L17.825,8.1Z" |
||||||
|
android:fillColor="#ffffff"/> |
||||||
|
</vector> |
@ -0,0 +1,25 @@ |
|||||||
|
<!-- |
||||||
|
~ 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:width="24dp" |
||||||
|
android:height="24dp" |
||||||
|
android:viewportWidth="24" |
||||||
|
android:viewportHeight="24"> |
||||||
|
<path |
||||||
|
android:fillColor="@android:color/black" |
||||||
|
android:pathData="M1.365,17.788C1.12,17.788 0.915,17.705 0.749,17.54C0.583,17.374 0.5,17.168 0.5,16.923V16.569C0.5,15.894 0.844,15.346 1.531,14.927C2.219,14.508 3.126,14.298 4.251,14.298C4.454,14.298 4.647,14.305 4.829,14.318C5.011,14.332 5.187,14.355 5.358,14.389C5.168,14.681 5.028,14.995 4.94,15.332C4.852,15.668 4.808,16.024 4.808,16.4V17.788H1.365ZM7.408,17.788C7.147,17.788 6.931,17.702 6.759,17.529C6.586,17.355 6.5,17.141 6.5,16.885V16.438C6.5,15.483 7.006,14.713 8.018,14.128C9.03,13.543 10.356,13.25 11.996,13.25C13.651,13.25 14.982,13.543 15.989,14.128C16.996,14.713 17.5,15.483 17.5,16.438V16.885C17.5,17.141 17.413,17.355 17.24,17.529C17.067,17.702 16.852,17.788 16.596,17.788H7.408ZM19.192,17.788V16.4C19.192,16.024 19.145,15.668 19.05,15.332C18.955,14.995 18.819,14.681 18.642,14.389C18.813,14.355 18.989,14.332 19.17,14.318C19.352,14.305 19.545,14.298 19.75,14.298C20.875,14.298 21.781,14.508 22.469,14.927C23.156,15.346 23.5,15.894 23.5,16.569V16.923C23.5,17.168 23.417,17.374 23.251,17.54C23.085,17.705 22.88,17.788 22.635,17.788H19.192ZM12,14.75C10.954,14.75 10.059,14.892 9.315,15.176C8.572,15.46 8.159,15.799 8.077,16.192V16.288H15.923V16.192C15.831,15.788 15.415,15.447 14.677,15.168C13.938,14.889 13.046,14.75 12,14.75ZM4.25,13.327C3.777,13.327 3.375,13.159 3.044,12.824C2.713,12.489 2.548,12.086 2.548,11.615C2.548,11.145 2.713,10.742 3.044,10.407C3.375,10.071 3.777,9.904 4.25,9.904C4.723,9.904 5.127,10.071 5.461,10.407C5.794,10.742 5.961,11.145 5.961,11.615C5.961,12.086 5.794,12.489 5.459,12.824C5.124,13.159 4.721,13.327 4.25,13.327ZM19.75,13.327C19.277,13.327 18.873,13.159 18.539,12.824C18.205,12.489 18.038,12.086 18.038,11.615C18.038,11.145 18.206,10.742 18.541,10.407C18.876,10.071 19.279,9.904 19.75,9.904C20.223,9.904 20.625,10.071 20.956,10.407C21.286,10.742 21.452,11.145 21.452,11.615C21.452,12.086 21.286,12.489 20.956,12.824C20.625,13.159 20.223,13.327 19.75,13.327ZM12,12.5C11.279,12.5 10.666,12.248 10.161,11.743C9.656,11.238 9.404,10.625 9.404,9.904C9.404,9.183 9.656,8.57 10.161,8.065C10.666,7.56 11.279,7.308 12,7.308C12.721,7.308 13.334,7.56 13.839,8.065C14.344,8.57 14.596,9.183 14.596,9.904C14.596,10.625 14.344,11.238 13.839,11.743C13.334,12.248 12.721,12.5 12,12.5ZM12.002,8.808C11.691,8.808 11.431,8.913 11.22,9.122C11.009,9.332 10.904,9.592 10.904,9.902C10.904,10.212 11.009,10.473 11.218,10.684C11.428,10.895 11.688,11 11.998,11C12.308,11 12.569,10.895 12.78,10.685C12.991,10.476 13.096,10.216 13.096,9.906C13.096,9.595 12.991,9.335 12.781,9.124C12.572,8.913 12.312,8.808 12.002,8.808Z" /> |
||||||
|
</vector> |
@ -0,0 +1,25 @@ |
|||||||
|
<!-- |
||||||
|
~ 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:width="24dp" |
||||||
|
android:height="24dp" |
||||||
|
android:viewportWidth="24" |
||||||
|
android:viewportHeight="24"> |
||||||
|
<path |
||||||
|
android:fillColor="@android:color/black" |
||||||
|
android:pathData="M18.001,21.75C17.237,21.75 16.588,21.483 16.053,20.948C15.518,20.413 15.25,19.764 15.25,19C15.25,18.875 15.26,18.746 15.28,18.612C15.3,18.478 15.33,18.355 15.369,18.242L7.973,13.911C7.709,14.174 7.408,14.38 7.071,14.528C6.734,14.676 6.377,14.75 6,14.75C5.236,14.75 4.587,14.483 4.052,13.948C3.517,13.414 3.25,12.765 3.25,12.001C3.25,11.238 3.517,10.588 4.052,10.053C4.587,9.518 5.236,9.25 6,9.25C6.377,9.25 6.734,9.324 7.071,9.472C7.408,9.62 7.709,9.826 7.973,10.089L15.369,5.758C15.33,5.645 15.3,5.522 15.28,5.388C15.26,5.254 15.25,5.125 15.25,5C15.25,4.236 15.517,3.587 16.052,3.052C16.586,2.517 17.235,2.25 17.999,2.25C18.762,2.25 19.412,2.517 19.947,3.052C20.482,3.586 20.75,4.235 20.75,4.999C20.75,5.762 20.483,6.412 19.948,6.947C19.413,7.482 18.764,7.75 18,7.75C17.623,7.75 17.266,7.676 16.929,7.528C16.592,7.38 16.291,7.174 16.027,6.912L8.631,11.242C8.67,11.355 8.7,11.478 8.72,11.611C8.74,11.745 8.75,11.874 8.75,11.998C8.75,12.122 8.74,12.252 8.72,12.387C8.7,12.521 8.67,12.645 8.631,12.758L16.027,17.088C16.291,16.826 16.592,16.62 16.929,16.472C17.266,16.324 17.623,16.25 18,16.25C18.764,16.25 19.413,16.517 19.948,17.052C20.483,17.586 20.75,18.235 20.75,18.999C20.75,19.762 20.483,20.412 19.948,20.947C19.414,21.482 18.765,21.75 18.001,21.75ZM18,6.25C18.347,6.25 18.643,6.129 18.886,5.886C19.128,5.643 19.25,5.347 19.25,5C19.25,4.653 19.128,4.357 18.886,4.114C18.643,3.871 18.347,3.75 18,3.75C17.653,3.75 17.357,3.871 17.114,4.114C16.871,4.357 16.75,4.653 16.75,5C16.75,5.347 16.871,5.643 17.114,5.886C17.357,6.129 17.653,6.25 18,6.25ZM6,13.25C6.347,13.25 6.643,13.128 6.886,12.886C7.129,12.643 7.25,12.347 7.25,12C7.25,11.653 7.129,11.357 6.886,11.114C6.643,10.871 6.347,10.75 6,10.75C5.653,10.75 5.357,10.871 5.114,11.114C4.871,11.357 4.75,11.653 4.75,12C4.75,12.347 4.871,12.643 5.114,12.886C5.357,13.128 5.653,13.25 6,13.25ZM18,20.25C18.347,20.25 18.643,20.128 18.886,19.886C19.128,19.643 19.25,19.347 19.25,19C19.25,18.653 19.128,18.357 18.886,18.114C18.643,17.871 18.347,17.75 18,17.75C17.653,17.75 17.357,17.871 17.114,18.114C16.871,18.357 16.75,18.653 16.75,19C16.75,19.347 16.871,19.643 17.114,19.886C17.357,20.128 17.653,20.25 18,20.25Z" /> |
||||||
|
</vector> |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue