Browse Source

WIP pick avatar image

feature/jme/open-room-member-details-when-clicking-on-user-data
Florian Renaud 1 year ago
parent
commit
fcdc737a81
  1. 1
      features/createroom/impl/build.gradle.kts
  2. 2
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt
  3. 32
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt
  4. 2
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt
  5. 9
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt
  6. 33
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt
  7. 34
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarAction.kt
  8. 21
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListEvents.kt
  9. 26
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListState.kt
  10. 30
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListStateProvider.kt
  11. 121
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListView.kt
  12. 7
      libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt
  13. 14
      libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt

1
features/createroom/impl/build.gradle.kts

@ -48,6 +48,7 @@ dependencies {
implementation(projects.features.userlist.api) implementation(projects.features.userlist.api)
api(projects.features.createroom.api) api(projects.features.createroom.api)
implementation(libs.coil.compose) // FIXME temp implementation(libs.coil.compose) // FIXME temp
implementation(projects.libraries.mediapickers)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

2
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt

@ -16,14 +16,12 @@
package io.element.android.features.createroom.impl.configureroom package io.element.android.features.createroom.impl.configureroom
import android.net.Uri
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface ConfigureRoomEvents { sealed interface ConfigureRoomEvents {
data class RoomNameChanged(val name: String) : ConfigureRoomEvents data class RoomNameChanged(val name: String) : ConfigureRoomEvents
data class TopicChanged(val topic: String) : ConfigureRoomEvents data class TopicChanged(val topic: String) : ConfigureRoomEvents
data class AvatarUriChanged(val uri: Uri?) : ConfigureRoomEvents
data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents

32
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt

@ -26,6 +26,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.createroom.impl.CreateRoomDataStore
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListEvents
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListState
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.execute
@ -34,6 +37,8 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility import io.element.android.libraries.matrix.api.createroom.RoomVisibility
import io.element.android.libraries.mediapickers.PickerProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -41,6 +46,7 @@ import javax.inject.Inject
class ConfigureRoomPresenter @Inject constructor( class ConfigureRoomPresenter @Inject constructor(
private val dataStore: CreateRoomDataStore, private val dataStore: CreateRoomDataStore,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val mediaPickerProvider: PickerProvider,
) : Presenter<ConfigureRoomState> { ) : Presenter<ConfigureRoomState> {
@Composable @Composable
@ -52,6 +58,13 @@ class ConfigureRoomPresenter @Inject constructor(
} }
} }
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(onResult = { uri ->
if (uri != null) dataStore.setAvatarUrl(uri.toString())
})
val galleryImagePicker = mediaPickerProvider.registerGalleryImagePicker(onResult = { uri ->
if (uri != null) dataStore.setAvatarUrl(uri.toString())
})
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val createRoomAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) } val createRoomAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
@ -62,7 +75,6 @@ class ConfigureRoomPresenter @Inject constructor(
fun handleEvents(event: ConfigureRoomEvents) { fun handleEvents(event: ConfigureRoomEvents) {
when (event) { when (event) {
is ConfigureRoomEvents.AvatarUriChanged -> dataStore.setAvatarUrl(event.uri?.toString())
is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name) is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name)
is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic) is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic)
is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy) is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy)
@ -72,9 +84,27 @@ class ConfigureRoomPresenter @Inject constructor(
} }
} }
fun handleAvatarEvents(event: AvatarActionListEvents) {
when (event) {
is AvatarActionListEvents.HandleAction -> when (event.action) {
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
AvatarAction.TakePhoto -> cameraPhotoPicker.launch()
}
}
}
val avatarActionListState = AvatarActionListState(
actions = persistentListOf(
AvatarAction.ChoosePhoto,
AvatarAction.TakePhoto,
),
eventSink = ::handleAvatarEvents,
)
return ConfigureRoomState( return ConfigureRoomState(
config = createRoomConfig.value, config = createRoomConfig.value,
isCreateButtonEnabled = isCreateButtonEnabled, isCreateButtonEnabled = isCreateButtonEnabled,
avatarActionListState = avatarActionListState,
createRoomAction = createRoomAction.value, createRoomAction = createRoomAction.value,
eventSink = ::handleEvents, eventSink = ::handleEvents,
) )

2
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt

@ -17,12 +17,14 @@
package io.element.android.features.createroom.impl.configureroom package io.element.android.features.createroom.impl.configureroom
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListState
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
data class ConfigureRoomState( data class ConfigureRoomState(
val config: CreateRoomConfig, val config: CreateRoomConfig,
val isCreateButtonEnabled: Boolean, val isCreateButtonEnabled: Boolean,
val avatarActionListState: AvatarActionListState,
val createRoomAction: Async<RoomId>, val createRoomAction: Async<RoomId>,
val eventSink: (ConfigureRoomEvents) -> Unit val eventSink: (ConfigureRoomEvents) -> Unit
) )

9
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt

@ -18,8 +18,11 @@ package io.element.android.features.createroom.impl.configureroom
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListState
import io.element.android.features.userlist.api.aListOfSelectedUsers import io.element.android.features.userlist.api.aListOfSelectedUsers
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import kotlinx.collections.immutable.persistentListOf
open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomState> { open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomState> {
override val values: Sequence<ConfigureRoomState> override val values: Sequence<ConfigureRoomState>
@ -40,6 +43,10 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
fun aConfigureRoomState() = ConfigureRoomState( fun aConfigureRoomState() = ConfigureRoomState(
config = CreateRoomConfig(), config = CreateRoomConfig(),
isCreateButtonEnabled = false, isCreateButtonEnabled = false,
avatarActionListState = AvatarActionListState(
actions = persistentListOf(AvatarAction.TakePhoto, AvatarAction.ChoosePhoto),
eventSink = {},
),
createRoomAction = Async.Uninitialized, createRoomAction = Async.Uninitialized,
eventSink = {} eventSink = { },
) )

33
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt

@ -14,12 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
@file:OptIn(ExperimentalMaterial3Api::class) @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
package io.element.android.features.createroom.impl.configureroom package io.element.android.features.createroom.impl.configureroom
import android.net.Uri import android.net.Uri
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -27,12 +26,15 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -44,6 +46,8 @@ import io.element.android.features.createroom.impl.R
import io.element.android.features.createroom.impl.components.Avatar import io.element.android.features.createroom.impl.components.Avatar
import io.element.android.features.createroom.impl.components.LabelledTextField import io.element.android.features.createroom.impl.components.LabelledTextField
import io.element.android.features.createroom.impl.components.RoomPrivacyOption import io.element.android.features.createroom.impl.components.RoomPrivacyOption
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListEvents
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListView
import io.element.android.features.userlist.api.components.SelectedUsersList import io.element.android.features.userlist.api.components.SelectedUsersList
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.ProgressDialog
@ -56,6 +60,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.launch
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
@ -65,13 +70,23 @@ fun ConfigureRoomView(
onBackPressed: () -> Unit = {}, onBackPressed: () -> Unit = {},
onRoomCreated: (RoomId) -> Unit = {}, onRoomCreated: (RoomId) -> Unit = {},
) { ) {
val coroutineScope = rememberCoroutineScope()
val itemActionsBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
)
if (state.createRoomAction is Async.Success) { if (state.createRoomAction is Async.Success) {
LaunchedEffect(state.createRoomAction) { LaunchedEffect(state.createRoomAction) {
onRoomCreated(state.createRoomAction.state) onRoomCreated(state.createRoomAction.state)
} }
} }
val context = LocalContext.current fun onAvatarClicked() {
coroutineScope.launch {
itemActionsBottomSheetState.show()
}
}
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
@ -92,7 +107,7 @@ fun ConfigureRoomView(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
avatarUri = state.config.avatarUrl?.toUri(), avatarUri = state.config.avatarUrl?.toUri(),
roomName = state.config.roomName.orEmpty(), roomName = state.config.roomName.orEmpty(),
onAvatarClick = { Toast.makeText(context, "not implemented yet", Toast.LENGTH_SHORT).show() }, onAvatarClick = ::onAvatarClicked,
onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) }, onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
) )
RoomTopic( RoomTopic(
@ -114,10 +129,17 @@ fun ConfigureRoomView(
} }
} }
AvatarActionListView(
state = state.avatarActionListState,
modalBottomSheetState = itemActionsBottomSheetState,
onActionSelected = { state.avatarActionListState.eventSink(AvatarActionListEvents.HandleAction(it)) }
)
when (state.createRoomAction) { when (state.createRoomAction) {
is Async.Loading -> { is Async.Loading -> {
ProgressDialog(text = stringResource(StringR.string.common_creating_room)) ProgressDialog(text = stringResource(StringR.string.common_creating_room))
} }
is Async.Failure -> { is Async.Failure -> {
RetryDialog( RetryDialog(
content = stringResource(R.string.screen_create_room_error_creating_room), content = stringResource(R.string.screen_create_room_error_creating_room),
@ -125,6 +147,7 @@ fun ConfigureRoomView(
onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) }, onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) },
) )
} }
else -> Unit else -> Unit
} }
} }

34
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarAction.kt

@ -0,0 +1,34 @@
/*
* 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.impl.configureroom.avatar
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material.icons.filled.PhotoLibrary
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.vector.ImageVector
import io.element.android.libraries.ui.strings.R
@Immutable
sealed class AvatarAction(
@StringRes val titleResId: Int,
val icon: ImageVector,
) {
object TakePhoto : AvatarAction(titleResId = R.string.action_take_photo, icon = Icons.Default.PhotoCamera)
object ChoosePhoto : AvatarAction(titleResId = R.string.action_choose_photo, icon = Icons.Default.PhotoLibrary)
}

21
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListEvents.kt

@ -0,0 +1,21 @@
/*
* 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.impl.configureroom.avatar
sealed interface AvatarActionListEvents {
data class HandleAction(val action: AvatarAction) : AvatarActionListEvents
}

26
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListState.kt

@ -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.
*/
package io.element.android.features.createroom.impl.configureroom.avatar
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
data class AvatarActionListState(
val actions: ImmutableList<AvatarAction>,
val eventSink: (AvatarActionListEvents) -> Unit,
)

30
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListStateProvider.kt

@ -0,0 +1,30 @@
/*
* 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.impl.configureroom.avatar
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import kotlinx.collections.immutable.persistentListOf
open class ActionListStateProvider : PreviewParameterProvider<AvatarActionListState> {
override val values: Sequence<AvatarActionListState>
get() = sequenceOf(anActionListState())
}
fun anActionListState() = AvatarActionListState(
actions = persistentListOf(AvatarAction.TakePhoto, AvatarAction.ChoosePhoto),
eventSink = {}
)

121
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/avatar/AvatarActionListView.kt

@ -0,0 +1,121 @@
/*
* 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(ExperimentalMaterialApi::class, ExperimentalMaterialApi::class)
package io.element.android.features.createroom.impl.configureroom.avatar
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ListItem
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout
import kotlinx.coroutines.launch
@Composable
fun AvatarActionListView(
state: AvatarActionListState,
modalBottomSheetState: ModalBottomSheetState,
modifier: Modifier = Modifier,
onActionSelected: (action: AvatarAction) -> Unit = {},
) {
val coroutineScope = rememberCoroutineScope()
fun onItemActionClicked(itemAction: AvatarAction) {
onActionSelected(itemAction)
coroutineScope.launch {
modalBottomSheetState.hide()
}
}
ModalBottomSheetLayout(
modifier = modifier,
sheetState = modalBottomSheetState,
sheetContent = {
SheetContent(
state = state,
onActionClicked = ::onItemActionClicked,
modifier = Modifier
.navigationBarsPadding()
.imePadding()
)
}
)
}
@Composable
private fun SheetContent(
state: AvatarActionListState,
modifier: Modifier = Modifier,
onActionClicked: (AvatarAction) -> Unit = { },
) {
val actions = state.actions
LazyColumn(
modifier = modifier.fillMaxWidth()
) {
items(
items = actions,
) { action ->
ListItem(
modifier = Modifier.clickable { onActionClicked(action) },
text = {
Text(text = stringResource(action.titleResId))
},
icon = {
Icon(
imageVector = action.icon,
contentDescription = stringResource(action.titleResId),
)
}
)
}
}
}
@Preview
@Composable
fun SheetContentLightPreview(@PreviewParameter(ActionListStateProvider::class) state: AvatarActionListState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun SheetContentDarkPreview(@PreviewParameter(ActionListStateProvider::class) state: AvatarActionListState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable
private fun ContentToPreview(state: AvatarActionListState) {
AvatarActionListView(
state = state,
modalBottomSheetState = ModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded
),
)
}

7
libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt

@ -26,6 +26,13 @@ sealed interface PickerType<Input, Output> {
fun getContract(): ActivityResultContract<Input, Output> fun getContract(): ActivityResultContract<Input, Output>
fun getDefaultRequest(): Input fun getDefaultRequest(): Input
object Image : PickerType<PickVisualMediaRequest, Uri?> {
override fun getContract() = ActivityResultContracts.PickVisualMedia()
override fun getDefaultRequest(): PickVisualMediaRequest {
return PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
}
}
object ImageAndVideo : PickerType<PickVisualMediaRequest, Uri?> { object ImageAndVideo : PickerType<PickVisualMediaRequest, Uri?> {
override fun getContract() = ActivityResultContracts.PickVisualMedia() override fun getContract() = ActivityResultContracts.PickVisualMedia()
override fun getDefaultRequest(): PickVisualMediaRequest { override fun getDefaultRequest(): PickVisualMediaRequest {

14
libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/PickerProviderImpl.kt

@ -59,6 +59,20 @@ class PickerProviderImpl constructor(private val isInTest: Boolean) : PickerProv
} }
} }
/**
* Remembers and returns a [PickerLauncher] for a gallery picture.
* [onResult] will be called with either the selected file's [Uri] or `null` if nothing was selected.
*/
@Composable
fun registerGalleryImagePicker(onResult: (Uri?) -> Unit): PickerLauncher<PickVisualMediaRequest, Uri?> {
// Tests and UI preview can't handle Contexts, so we might as well disable the whole picker
return if (LocalInspectionMode.current || isInTest) {
NoOpPickerLauncher { onResult(null) }
} else {
rememberPickerLauncher(type = PickerType.Image) { uri -> onResult(uri) }
}
}
/** /**
* Remembers and returns a [PickerLauncher] for a gallery item, either a picture or a video. * Remembers and returns a [PickerLauncher] for a gallery item, either a picture or a video.
* [onResult] will be called with either the selected file's [Uri] or `null` if nothing was selected. * [onResult] will be called with either the selected file's [Uri] or `null` if nothing was selected.

Loading…
Cancel
Save