Merge branch 'develop' into feature/fga/navigation First step: makes it compile by disabling the new screens (need to migrate to nodes)

This commit is contained in:
ganfra 2023-01-06 19:22:52 +01:00
commit 9e211b5e04
312 changed files with 8608 additions and 261 deletions

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2022 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/>

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2022 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.x.matrix.ui
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.designsystem.components.avatar.AvatarSize
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.media.MediaResolver
import io.element.android.x.matrix.room.MatrixRoom
import io.element.android.x.matrix.room.RoomSummary
import io.element.android.x.matrix.ui.model.MatrixUser
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
class MatrixItemHelper(
private val client: MatrixClient
) {
/**
* TODO Make username and avatar live...
*/
@OptIn(FlowPreview::class)
fun getCurrentUserData(avatarSize: AvatarSize): Flow<MatrixUser> {
return suspend {
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
val userDisplayName = client.loadUserDisplayName().getOrNull()
val avatarData =
loadAvatarData(
userDisplayName ?: client.userId().value,
userAvatarUrl,
avatarSize
)
MatrixUser(
id = client.userId(),
username = userDisplayName,
avatarUrl = userAvatarUrl,
avatarData = avatarData,
)
}.asFlow()
}
suspend fun loadAvatarData(room: MatrixRoom, size: AvatarSize): AvatarData {
return loadAvatarData(
name = room.bestName,
url = room.avatarUrl,
size = size
)
}
suspend fun loadAvatarData(roomSummary: RoomSummary.Filled, size: AvatarSize): AvatarData {
return loadAvatarData(
name = roomSummary.details.name,
url = roomSummary.details.avatarURLString,
size = size
)
}
suspend fun loadAvatarData(
name: String,
url: String?,
size: AvatarSize
): AvatarData {
val model = client.mediaResolver()
.resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value))
return AvatarData(name, model, size)
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 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.x.matrix.ui
import coil.ComponentRegistry
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.ui.media.MediaFetcher
import io.element.android.x.matrix.ui.media.MediaKeyer
import javax.inject.Inject
class MatrixUi @Inject constructor() {
fun registerCoilComponents(
builder: ComponentRegistry.Builder,
activeClientProvider: () -> MatrixClient?
) {
builder.add(MediaKeyer())
builder.add(MediaFetcher.Factory(activeClientProvider))
}
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2022 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.x.matrix.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.x.designsystem.ElementXTheme
import io.element.android.x.designsystem.components.avatar.Avatar
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.designsystem.components.avatar.AvatarSize
import io.element.android.x.matrix.core.UserId
import io.element.android.x.matrix.ui.model.MatrixUser
import io.element.android.x.matrix.ui.model.getBestName
@Composable
fun MatrixUserHeader(
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
) {
Column(
modifier = modifier
.clickable(onClick = onClick)
.fillMaxWidth()
.padding(all = 16.dp)
.height(IntrinsicSize.Min),
horizontalAlignment = Alignment.CenterHorizontally
) {
Avatar(
matrixUser.avatarData.copy(size = AvatarSize.HUGE),
)
Spacer(modifier = Modifier.height(16.dp))
// Name
Text(
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
text = matrixUser.getBestName(),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Id
if (matrixUser.username.isNullOrEmpty().not()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = matrixUser.id.value,
color = MaterialTheme.colorScheme.secondary,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Preview
@Composable
fun MatrixUserHeaderPreview() {
ElementXTheme {
MatrixUserHeader(
MatrixUser(
id = UserId("@alice:server.org"),
username = "Alice",
avatarUrl = null,
avatarData = AvatarData("Alice")
)
)
}
}
@Preview
@Composable
fun MatrixUserHeaderNoUsernamePreview() {
ElementXTheme {
MatrixUserHeader(
MatrixUser(
id = UserId("@alice:server.org"),
username = null,
avatarUrl = null,
avatarData = AvatarData("Alice")
)
)
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2022 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.x.matrix.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.x.designsystem.ElementXTheme
import io.element.android.x.designsystem.components.avatar.Avatar
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.matrix.core.UserId
import io.element.android.x.matrix.ui.model.MatrixUser
import io.element.android.x.matrix.ui.model.getBestName
@Composable
fun MatrixUserRow(
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
) {
Row(
modifier = modifier
.clickable(onClick = onClick)
.fillMaxWidth()
.padding(horizontal = 16.dp)
.height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically
) {
Avatar(
matrixUser.avatarData,
)
Column(
modifier = Modifier
.padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp)
.alignByBaseline()
.weight(1f)
) {
// Name
Text(
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
text = matrixUser.getBestName(),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Id
if (matrixUser.username.isNullOrEmpty().not()) {
Text(
text = matrixUser.id.value,
color = MaterialTheme.colorScheme.secondary,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
}
@Preview
@Composable
fun MatrixUserRowPreview() {
ElementXTheme {
MatrixUserRow(
MatrixUser(
id = UserId("@alice:server.org"),
username = "Alice",
avatarUrl = null,
avatarData = AvatarData("Alice")
)
)
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2022 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.x.matrix.ui.media
import coil.ImageLoader
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.Options
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.media.MediaResolver
import java.nio.ByteBuffer
internal class MediaFetcher(
private val mediaResolver: MediaResolver?,
private val meta: MediaResolver.Meta,
private val options: Options,
private val imageLoader: ImageLoader
) : Fetcher {
override suspend fun fetch(): FetchResult? {
val byteArray = mediaResolver?.resolve(meta) ?: return null
val byteBuffer = ByteBuffer.wrap(byteArray)
return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
}
class Factory(private val activeClientProvider: () -> MatrixClient?) :
Fetcher.Factory<MediaResolver.Meta> {
override fun create(
data: MediaResolver.Meta,
options: Options,
imageLoader: ImageLoader
): Fetcher {
val activeClient = activeClientProvider()
return MediaFetcher(
mediaResolver = activeClient?.mediaResolver(),
meta = data,
options = options,
imageLoader = imageLoader
)
}
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 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.x.matrix.ui.media
import coil.key.Keyer
import coil.request.Options
import io.element.android.x.matrix.media.MediaResolver
internal class MediaKeyer : Keyer<MediaResolver.Meta> {
override fun key(data: MediaResolver.Meta, options: Options): String? {
return "${data.source.url()}_${data.kind}"
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 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.x.matrix.ui.model
import androidx.compose.runtime.Stable
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.matrix.core.UserId
@Stable
data class MatrixUser(
val id: UserId,
val username: String? = null,
val avatarUrl: String? = null,
val avatarData: AvatarData = AvatarData(),
)
fun MatrixUser.getBestName(): String {
return username?.takeIf { it.isNotEmpty() } ?: id.value
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2022 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.x.matrix.ui.viewmodels.user
import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.x.anvilannotations.ContributesViewModel
import io.element.android.x.architecture.viewmodel.daggerMavericksViewModelFactory
import io.element.android.x.designsystem.components.avatar.AvatarSize
import io.element.android.x.di.SessionScope
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.ui.MatrixItemHelper
@ContributesViewModel(SessionScope::class)
class UserViewModel @AssistedInject constructor(
client: MatrixClient,
@Assisted initialState: UserViewState
) : MavericksViewModel<UserViewState>(initialState) {
companion object : MavericksViewModelFactory<UserViewModel, UserViewState> by daggerMavericksViewModelFactory()
private val matrixUserHelper = MatrixItemHelper(client)
init {
handleInit()
}
private fun handleInit() {
matrixUserHelper.getCurrentUserData(avatarSize = AvatarSize.SMALL).execute {
copy(user = it)
}
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2022 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.x.matrix.ui.viewmodels.user
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import io.element.android.x.matrix.ui.model.MatrixUser
data class UserViewState(
val user: Async<MatrixUser> = Uninitialized,
) : MavericksState