Merge pull request #1464 from vector-im/feature/room_settings
Room Settings: Name, Topic, Photo, Aliases, History Visibility
This commit is contained in:
commit
87a087c0b5
@ -9,6 +9,8 @@ Improvements 🙌:
|
|||||||
- "Add Matrix app" menu is now always visible (#1495)
|
- "Add Matrix app" menu is now always visible (#1495)
|
||||||
- Handle `/op`, `/deop`, and `/nick` commands (#12)
|
- Handle `/op`, `/deop`, and `/nick` commands (#12)
|
||||||
- Prioritising Recovery key over Recovery passphrase (#1463)
|
- Prioritising Recovery key over Recovery passphrase (#1463)
|
||||||
|
- Room Settings: Name, Topic, Photo, Aliases, History Visibility (#1455)
|
||||||
|
- Update user avatar (#1054)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Fix dark theme issue on login screen (#1097)
|
- Fix dark theme issue on login screen (#1097)
|
||||||
|
@ -16,12 +16,14 @@
|
|||||||
|
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import im.vector.matrix.android.api.query.QueryStringValue
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||||
@ -101,6 +103,30 @@ class RxRoom(private val room: Room) {
|
|||||||
fun invite(userId: String, reason: String? = null): Completable = completableBuilder<Unit> {
|
fun invite(userId: String, reason: String? = null): Completable = completableBuilder<Unit> {
|
||||||
room.invite(userId, reason, it)
|
room.invite(userId, reason, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateTopic(topic: String): Completable = completableBuilder<Unit> {
|
||||||
|
room.updateTopic(topic, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateName(name: String): Completable = completableBuilder<Unit> {
|
||||||
|
room.updateName(name, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addRoomAlias(alias: String): Completable = completableBuilder<Unit> {
|
||||||
|
room.addRoomAlias(alias, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCanonicalAlias(alias: String): Completable = completableBuilder<Unit> {
|
||||||
|
room.updateCanonicalAlias(alias, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder<Unit> {
|
||||||
|
room.updateHistoryReadability(readability, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder<Unit> {
|
||||||
|
room.updateAvatar(avatarUri, fileName, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.profile
|
package im.vector.matrix.android.api.session.profile
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
@ -48,6 +49,14 @@ interface ProfileService {
|
|||||||
*/
|
*/
|
||||||
fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable
|
fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the avatar for this user
|
||||||
|
* @param userId the userId to update the avatar of
|
||||||
|
* @param newAvatarUri the new avatar uri of the user
|
||||||
|
* @param fileName the fileName of selected image
|
||||||
|
*/
|
||||||
|
fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current avatarUrl for this user.
|
* Return the current avatarUrl for this user.
|
||||||
* @param userId the userId param to look for
|
* @param userId the userId param to look for
|
||||||
|
@ -27,7 +27,9 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||||||
*/
|
*/
|
||||||
data class RoomSummary constructor(
|
data class RoomSummary constructor(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
// Computed display name
|
||||||
val displayName: String = "",
|
val displayName: String = "",
|
||||||
|
val name: String = "",
|
||||||
val topic: String = "",
|
val topic: String = "",
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String = "",
|
||||||
val canonicalAlias: String? = null,
|
val canonicalAlias: String? = null,
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.powerlevels
|
package im.vector.matrix.android.api.session.room.powerlevels
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,4 +124,59 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||||||
else -> Role.Moderator.value
|
else -> Role.Moderator.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user have the necessary power level to change room name
|
||||||
|
* @param userId the id of the user to check for.
|
||||||
|
* @return true if able to change room name
|
||||||
|
*/
|
||||||
|
fun isUserAbleToChangeRoomName(userId: String): Boolean {
|
||||||
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
|
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_NAME] ?: powerLevelsContent.stateDefault
|
||||||
|
return powerLevel >= minPowerLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user have the necessary power level to change room topic
|
||||||
|
* @param userId the id of the user to check for.
|
||||||
|
* @return true if able to change room topic
|
||||||
|
*/
|
||||||
|
fun isUserAbleToChangeRoomTopic(userId: String): Boolean {
|
||||||
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
|
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_TOPIC] ?: powerLevelsContent.stateDefault
|
||||||
|
return powerLevel >= minPowerLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user have the necessary power level to change room canonical alias
|
||||||
|
* @param userId the id of the user to check for.
|
||||||
|
* @return true if able to change room canonical alias
|
||||||
|
*/
|
||||||
|
fun isUserAbleToChangeRoomCanonicalAlias(userId: String): Boolean {
|
||||||
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
|
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_CANONICAL_ALIAS] ?: powerLevelsContent.stateDefault
|
||||||
|
return powerLevel >= minPowerLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user have the necessary power level to change room history readability
|
||||||
|
* @param userId the id of the user to check for.
|
||||||
|
* @return true if able to change room history readability
|
||||||
|
*/
|
||||||
|
fun isUserAbleToChangeRoomHistoryReadability(userId: String): Boolean {
|
||||||
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
|
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_HISTORY_VISIBILITY] ?: powerLevelsContent.stateDefault
|
||||||
|
return powerLevel >= minPowerLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user have the necessary power level to change room avatar
|
||||||
|
* @param userId the id of the user to check for.
|
||||||
|
* @return true if able to change room avatar
|
||||||
|
*/
|
||||||
|
fun isUserAbleToChangeRoomAvatar(userId: String): Boolean {
|
||||||
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
|
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_AVATAR] ?: powerLevelsContent.stateDefault
|
||||||
|
return powerLevel >= minPowerLevel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.state
|
package im.vector.matrix.android.api.session.room.state
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.query.QueryStringValue
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
@ -31,6 +33,31 @@ interface StateService {
|
|||||||
*/
|
*/
|
||||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable
|
fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the name of the room
|
||||||
|
*/
|
||||||
|
fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new alias to the room.
|
||||||
|
*/
|
||||||
|
fun addRoomAlias(roomAlias: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the canonical alias of the room
|
||||||
|
*/
|
||||||
|
fun updateCanonicalAlias(alias: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the history readability of the room
|
||||||
|
*/
|
||||||
|
fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the avatar of the room
|
||||||
|
*/
|
||||||
|
fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
|
fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
||||||
|
@ -35,6 +35,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||||||
return RoomSummary(
|
return RoomSummary(
|
||||||
roomId = roomSummaryEntity.roomId,
|
roomId = roomSummaryEntity.roomId,
|
||||||
displayName = roomSummaryEntity.displayName ?: "",
|
displayName = roomSummaryEntity.displayName ?: "",
|
||||||
|
name = roomSummaryEntity.name ?: "",
|
||||||
topic = roomSummaryEntity.topic ?: "",
|
topic = roomSummaryEntity.topic ?: "",
|
||||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||||
isDirect = roomSummaryEntity.isDirect,
|
isDirect = roomSummaryEntity.isDirect,
|
||||||
|
@ -28,6 +28,7 @@ internal open class RoomSummaryEntity(
|
|||||||
@PrimaryKey var roomId: String = "",
|
@PrimaryKey var roomId: String = "",
|
||||||
var displayName: String? = "",
|
var displayName: String? = "",
|
||||||
var avatarUrl: String? = "",
|
var avatarUrl: String? = "",
|
||||||
|
var name: String? = "",
|
||||||
var topic: String? = "",
|
var topic: String? = "",
|
||||||
var latestPreviewableEvent: TimelineEventEntity? = null,
|
var latestPreviewableEvent: TimelineEventEntity? = null,
|
||||||
var heroes: RealmList<String> = RealmList(),
|
var heroes: RealmList<String> = RealmList(),
|
||||||
|
@ -16,12 +16,16 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.content
|
package im.vector.matrix.android.internal.session.content
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.internal.di.Authenticated
|
import im.vector.matrix.android.internal.di.Authenticated
|
||||||
import im.vector.matrix.android.internal.network.ProgressRequestBody
|
import im.vector.matrix.android.internal.network.ProgressRequestBody
|
||||||
import im.vector.matrix.android.internal.network.awaitResponse
|
import im.vector.matrix.android.internal.network.awaitResponse
|
||||||
import im.vector.matrix.android.internal.network.toFailure
|
import im.vector.matrix.android.internal.network.toFailure
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -31,12 +35,14 @@ import okhttp3.RequestBody.Companion.asRequestBody
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class FileUploader @Inject constructor(@Authenticated
|
internal class FileUploader @Inject constructor(@Authenticated
|
||||||
private val okHttpClient: OkHttpClient,
|
private val okHttpClient: OkHttpClient,
|
||||||
private val eventBus: EventBus,
|
private val eventBus: EventBus,
|
||||||
|
private val context: Context,
|
||||||
contentUrlResolver: ContentUrlResolver,
|
contentUrlResolver: ContentUrlResolver,
|
||||||
moshi: Moshi) {
|
moshi: Moshi) {
|
||||||
|
|
||||||
@ -59,6 +65,19 @@ internal class FileUploader @Inject constructor(@Authenticated
|
|||||||
return upload(uploadBody, filename, progressListener)
|
return upload(uploadBody, filename, progressListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun uploadFromUri(uri: Uri,
|
||||||
|
filename: String?,
|
||||||
|
mimeType: String?,
|
||||||
|
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
||||||
|
val inputStream = withContext(Dispatchers.IO) {
|
||||||
|
context.contentResolver.openInputStream(uri)
|
||||||
|
} ?: throw FileNotFoundException()
|
||||||
|
|
||||||
|
inputStream.use {
|
||||||
|
return uploadByteArray(it.readBytes(), filename, mimeType, progressListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
|
private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
|
||||||
val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException()
|
val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException()
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.profile
|
package im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
@ -27,16 +28,22 @@ import im.vector.matrix.android.api.util.JsonDict
|
|||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.internal.database.model.UserThreePidEntity
|
import im.vector.matrix.android.internal.database.model.UserThreePidEntity
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
|
import im.vector.matrix.android.internal.session.content.FileUploader
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor,
|
internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val refreshUserThreePidsTask: RefreshUserThreePidsTask,
|
private val refreshUserThreePidsTask: RefreshUserThreePidsTask,
|
||||||
private val getProfileInfoTask: GetProfileInfoTask,
|
private val getProfileInfoTask: GetProfileInfoTask,
|
||||||
private val setDisplayNameTask: SetDisplayNameTask) : ProfileService {
|
private val setDisplayNameTask: SetDisplayNameTask,
|
||||||
|
private val setAvatarUrlTask: SetAvatarUrlTask,
|
||||||
|
private val fileUploader: FileUploader) : ProfileService {
|
||||||
|
|
||||||
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||||
val params = GetProfileInfoTask.Params(userId)
|
val params = GetProfileInfoTask.Params(userId)
|
||||||
@ -64,6 +71,17 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) {
|
||||||
|
val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg")
|
||||||
|
setAvatarUrlTask
|
||||||
|
.configureWith(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) {
|
||||||
|
callback = matrixCallback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||||
val params = GetProfileInfoTask.Params(userId)
|
val params = GetProfileInfoTask.Params(userId)
|
||||||
return getProfileInfoTask
|
return getProfileInfoTask
|
||||||
|
@ -49,6 +49,12 @@ internal interface ProfileAPI {
|
|||||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname")
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname")
|
||||||
fun setDisplayName(@Path("userId") userId: String, @Body body: SetDisplayNameBody): Call<Unit>
|
fun setDisplayName(@Path("userId") userId: String, @Body body: SetDisplayNameBody): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change user avatar url.
|
||||||
|
*/
|
||||||
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/avatar_url")
|
||||||
|
fun setAvatarUrl(@Path("userId") userId: String, @Body body: SetAvatarUrlBody): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind a threePid
|
* Bind a threePid
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind
|
||||||
|
@ -54,4 +54,7 @@ internal abstract class ProfileModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetDisplayNameTask(task: DefaultSetDisplayNameTask): SetDisplayNameTask
|
abstract fun bindSetDisplayNameTask(task: DefaultSetDisplayNameTask): SetDisplayNameTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSetAvatarUrlTask(task: DefaultSetAvatarUrlTask): SetAvatarUrlTask
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SetAvatarUrlBody(
|
||||||
|
/**
|
||||||
|
* The new avatar url for this user.
|
||||||
|
*/
|
||||||
|
@Json(name = "avatar_url")
|
||||||
|
val avatarUrl: String
|
||||||
|
)
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal abstract class SetAvatarUrlTask : Task<SetAvatarUrlTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val userId: String,
|
||||||
|
val newAvatarUrl: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSetAvatarUrlTask @Inject constructor(
|
||||||
|
private val profileAPI: ProfileAPI,
|
||||||
|
private val eventBus: EventBus) : SetAvatarUrlTask() {
|
||||||
|
|
||||||
|
override suspend fun execute(params: Params) {
|
||||||
|
return executeRequest(eventBus) {
|
||||||
|
val body = SetAvatarUrlBody(
|
||||||
|
avatarUrl = params.newAvatarUrl
|
||||||
|
)
|
||||||
|
apiCall = profileAPI.setAvatarUrl(params.userId, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms
|
|||||||
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.AddRoomAliasBody
|
||||||
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
|
||||||
import im.vector.matrix.android.internal.session.room.membership.admin.UserIdAndReason
|
import im.vector.matrix.android.internal.session.room.membership.admin.UserIdAndReason
|
||||||
@ -311,6 +312,14 @@ internal interface RoomAPI {
|
|||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
|
||||||
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
|
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add alias to the room.
|
||||||
|
* @param roomAlias the room alias.
|
||||||
|
*/
|
||||||
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
|
||||||
|
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
|
||||||
|
@Body body: AddRoomAliasBody): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inform that the user is starting to type or has stopped typing
|
* Inform that the user is starting to type or has stopped typing
|
||||||
*/
|
*/
|
||||||
|
@ -24,6 +24,8 @@ import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
|||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.internal.session.DefaultFileService
|
import im.vector.matrix.android.internal.session.DefaultFileService
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.AddRoomAliasTask
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.DefaultAddRoomAliasTask
|
||||||
import im.vector.matrix.android.internal.session.room.alias.DefaultGetRoomIdByAliasTask
|
import im.vector.matrix.android.internal.session.room.alias.DefaultGetRoomIdByAliasTask
|
||||||
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
@ -190,6 +192,9 @@ internal abstract class RoomModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
|
abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask
|
abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask
|
||||||
|
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 im.vector.matrix.android.internal.session.room.alias
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class AddRoomAliasBody(
|
||||||
|
/**
|
||||||
|
* Required. The room id which the alias will be added to.
|
||||||
|
*/
|
||||||
|
@Json(name = "room_id") val roomId: String
|
||||||
|
)
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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 im.vector.matrix.android.internal.session.room.alias
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val roomId: String,
|
||||||
|
val roomAlias: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultAddRoomAliasTask @Inject constructor(
|
||||||
|
private val roomAPI: RoomAPI,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : AddRoomAliasTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: AddRoomAliasTask.Params) {
|
||||||
|
executeRequest<Unit>(eventBus) {
|
||||||
|
apiCall = roomAPI.addRoomAlias(
|
||||||
|
roomAlias = params.roomAlias,
|
||||||
|
body = AddRoomAliasBody(
|
||||||
|
roomId = params.roomId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.state
|
package im.vector.matrix.android.internal.session.room.state
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
@ -23,17 +24,25 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||||||
import im.vector.matrix.android.api.query.QueryStringValue
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
import im.vector.matrix.android.api.session.room.state.StateService
|
import im.vector.matrix.android.api.session.room.state.StateService
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import im.vector.matrix.android.internal.session.content.FileUploader
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.AddRoomAliasTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
|
||||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val stateEventDataSource: StateEventDataSource,
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val sendStateTask: SendStateTask
|
private val sendStateTask: SendStateTask,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val fileUploader: FileUploader,
|
||||||
|
private val addRoomAliasTask: AddRoomAliasTask
|
||||||
) : StateService {
|
) : StateService {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
@ -84,4 +93,51 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
|||||||
stateKey = null
|
stateKey = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return sendStateEvent(
|
||||||
|
eventType = EventType.STATE_ROOM_NAME,
|
||||||
|
body = mapOf("name" to name),
|
||||||
|
callback = callback,
|
||||||
|
stateKey = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addRoomAlias(roomAlias: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return addRoomAliasTask
|
||||||
|
.configureWith(AddRoomAliasTask.Params(roomId, roomAlias)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateCanonicalAlias(alias: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return sendStateEvent(
|
||||||
|
eventType = EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
|
body = mapOf("alias" to alias),
|
||||||
|
callback = callback,
|
||||||
|
stateKey = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return sendStateEvent(
|
||||||
|
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
|
body = mapOf("history_visibility" to readability),
|
||||||
|
callback = callback,
|
||||||
|
stateKey = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")
|
||||||
|
sendStateEvent(
|
||||||
|
eventType = EventType.STATE_ROOM_AVATAR,
|
||||||
|
body = mapOf("url" to response.contentUri),
|
||||||
|
callback = callback,
|
||||||
|
stateKey = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomNameContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate
|
import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate
|
||||||
@ -107,6 +108,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
|
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
|
||||||
filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true)
|
filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true)
|
||||||
|
|
||||||
|
val lastNameEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
|
||||||
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
||||||
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||||
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
|
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
|
||||||
@ -122,6 +124,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
|
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId).toString()
|
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId).toString()
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
|
||||||
|
roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
|
||||||
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
|
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
|
||||||
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
|
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
|
||||||
roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel<RoomCanonicalAliasContent>()
|
roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel<RoomCanonicalAliasContent>()
|
||||||
|
@ -25,10 +25,12 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.core.widget.ImageViewCompat
|
import androidx.core.widget.ImageViewCompat
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_profile_action)
|
@EpoxyModelClass(layout = R.layout.item_profile_action)
|
||||||
@ -51,6 +53,12 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var accessoryRes: Int = 0
|
var accessoryRes: Int = 0
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var accessoryMatrixItem: MatrixItem? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var avatarRenderer: AvatarRenderer? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var editable: Boolean = true
|
var editable: Boolean = true
|
||||||
|
|
||||||
@ -93,6 +101,13 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||||||
holder.secondaryAccessory.isVisible = false
|
holder.secondaryAccessory.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (accessoryMatrixItem != null) {
|
||||||
|
avatarRenderer?.render(accessoryMatrixItem!!, holder.secondaryAccessory)
|
||||||
|
holder.secondaryAccessory.isVisible = true
|
||||||
|
} else {
|
||||||
|
holder.secondaryAccessory.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
if (editableRes != 0 && editable) {
|
if (editableRes != 0 && editable) {
|
||||||
val tintColorSecondary = if (destructive) {
|
val tintColorSecondary = if (destructive) {
|
||||||
tintColor
|
tintColor
|
||||||
|
@ -19,8 +19,10 @@ package im.vector.riotx.core.epoxy.profiles
|
|||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.core.epoxy.ClickListener
|
import im.vector.riotx.core.epoxy.ClickListener
|
||||||
import im.vector.riotx.core.epoxy.dividerItem
|
import im.vector.riotx.core.epoxy.dividerItem
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
fun EpoxyController.buildProfileSection(title: String) {
|
fun EpoxyController.buildProfileSection(title: String) {
|
||||||
profileSectionItem {
|
profileSectionItem {
|
||||||
@ -41,7 +43,9 @@ fun EpoxyController.buildProfileAction(
|
|||||||
destructive: Boolean = false,
|
destructive: Boolean = false,
|
||||||
divider: Boolean = true,
|
divider: Boolean = true,
|
||||||
action: ClickListener? = null,
|
action: ClickListener? = null,
|
||||||
@DrawableRes accessory: Int = 0
|
@DrawableRes accessory: Int = 0,
|
||||||
|
accessoryMatrixItem: MatrixItem? = null,
|
||||||
|
avatarRenderer: AvatarRenderer? = null
|
||||||
) {
|
) {
|
||||||
profileActionItem {
|
profileActionItem {
|
||||||
iconRes(icon)
|
iconRes(icon)
|
||||||
@ -53,6 +57,8 @@ fun EpoxyController.buildProfileAction(
|
|||||||
destructive(destructive)
|
destructive(destructive)
|
||||||
title(title)
|
title(title)
|
||||||
accessoryRes(accessory)
|
accessoryRes(accessory)
|
||||||
|
accessoryMatrixItem(accessoryMatrixItem)
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
listener { _ ->
|
listener { _ ->
|
||||||
action?.invoke()
|
action?.invoke()
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ package im.vector.riotx.features.attachments.preview
|
|||||||
import android.app.Activity.RESULT_CANCELED
|
import android.app.Activity.RESULT_CANCELED
|
||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
@ -38,7 +37,6 @@ import com.airbnb.mvrx.args
|
|||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.yalantis.ucrop.UCrop
|
import com.yalantis.ucrop.UCrop
|
||||||
import com.yalantis.ucrop.UCropActivity
|
|
||||||
import im.vector.matrix.android.api.extensions.orFalse
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
@ -52,6 +50,7 @@ import im.vector.riotx.core.utils.SnapOnScrollListener
|
|||||||
import im.vector.riotx.core.utils.allGranted
|
import im.vector.riotx.core.utils.allGranted
|
||||||
import im.vector.riotx.core.utils.attachSnapHelperWithListener
|
import im.vector.riotx.core.utils.attachSnapHelperWithListener
|
||||||
import im.vector.riotx.core.utils.checkPermissions
|
import im.vector.riotx.core.utils.checkPermissions
|
||||||
|
import im.vector.riotx.features.media.createUCropWithDefaultSettings
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
|
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -203,32 +202,7 @@ class AttachmentsPreviewFragment @Inject constructor(
|
|||||||
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
|
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
|
||||||
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
|
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
|
||||||
val uri = currentAttachment.queryUri
|
val uri = currentAttachment.queryUri
|
||||||
UCrop.of(uri, destinationFile.toUri())
|
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), currentAttachment.name)
|
||||||
.withOptions(
|
|
||||||
UCrop.Options()
|
|
||||||
.apply {
|
|
||||||
setAllowedGestures(
|
|
||||||
/* tabScale = */ UCropActivity.SCALE,
|
|
||||||
/* tabRotate = */ UCropActivity.ALL,
|
|
||||||
/* tabAspectRatio = */ UCropActivity.SCALE
|
|
||||||
)
|
|
||||||
setToolbarTitle(currentAttachment.name)
|
|
||||||
// Disable freestyle crop, usability was not easy
|
|
||||||
// setFreeStyleCropEnabled(true)
|
|
||||||
// Color used for toolbar icon and text
|
|
||||||
setToolbarColor(colorProvider.getColorFromAttribute(R.attr.riotx_background))
|
|
||||||
setToolbarWidgetColor(colorProvider.getColorFromAttribute(R.attr.vctr_toolbar_primary_text_color))
|
|
||||||
// Background
|
|
||||||
setRootViewBackgroundColor(colorProvider.getColorFromAttribute(R.attr.riotx_background))
|
|
||||||
// Status bar color (pb in dark mode, icon of the status bar are dark)
|
|
||||||
setStatusBarColor(colorProvider.getColorFromAttribute(R.attr.riotx_header_panel_background))
|
|
||||||
// Known issue: there is still orange color used by the lib
|
|
||||||
// https://github.com/Yalantis/uCrop/issues/602
|
|
||||||
setActiveControlsWidgetColor(colorProvider.getColor(R.color.riotx_accent))
|
|
||||||
// Hide the logo (does not work)
|
|
||||||
setLogoColor(Color.TRANSPARENT)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.start(requireContext(), this)
|
.start(requireContext(), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 im.vector.riotx.features.form
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.widget.AppCompatButton
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.core.platform.SimpleTextWatcher
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_form_text_input_with_button)
|
||||||
|
abstract class FormEditTextWithButtonItem : VectorEpoxyModel<FormEditTextWithButtonItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var hint: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var value: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var enabled: Boolean = true
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var buttonText: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var onTextChange: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var onButtonClicked: ((View) -> Unit)? = null
|
||||||
|
|
||||||
|
private val onTextChangeListener = object : SimpleTextWatcher() {
|
||||||
|
override fun afterTextChanged(s: Editable) {
|
||||||
|
onTextChange?.invoke(s.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
holder.textInputLayout.isEnabled = enabled
|
||||||
|
holder.textInputLayout.hint = hint
|
||||||
|
|
||||||
|
// Update only if text is different
|
||||||
|
if (holder.textInputEditText.text.toString() != value) {
|
||||||
|
holder.textInputEditText.setText(value)
|
||||||
|
}
|
||||||
|
holder.textInputEditText.isEnabled = enabled
|
||||||
|
|
||||||
|
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
||||||
|
|
||||||
|
holder.textInputButton.text = buttonText
|
||||||
|
|
||||||
|
holder.textInputButton.setOnClickListener(onButtonClicked)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldSaveViewState(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(holder: Holder) {
|
||||||
|
super.unbind(holder)
|
||||||
|
holder.textInputEditText.removeTextChangedListener(onTextChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val textInputLayout by bind<TextInputLayout>(R.id.formTextInputTextInputLayout)
|
||||||
|
val textInputEditText by bind<TextInputEditText>(R.id.formTextInputTextInputEditText)
|
||||||
|
val textInputButton by bind<AppCompatButton>(R.id.formTextInputButton)
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomGuestAccessContent
|
import im.vector.matrix.android.api.session.room.model.RoomGuestAccessContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomJoinRules
|
import im.vector.matrix.android.api.session.room.model.RoomJoinRules
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent
|
import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent
|
||||||
@ -47,6 +46,7 @@ import timber.log.Timber
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NoticeEventFormatter @Inject constructor(private val sessionHolder: ActiveSessionHolder,
|
class NoticeEventFormatter @Inject constructor(private val sessionHolder: ActiveSessionHolder,
|
||||||
|
private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter,
|
||||||
private val sp: StringProvider) {
|
private val sp: StringProvider) {
|
||||||
|
|
||||||
private fun Event.isSentByCurrentUser() = senderId != null && senderId == sessionHolder.getSafeActiveSession()?.myUserId
|
private fun Event.isSentByCurrentUser() = senderId != null && senderId == sessionHolder.getSafeActiveSession()?.myUserId
|
||||||
@ -223,12 +223,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
|||||||
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
|
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
|
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
|
||||||
|
|
||||||
val formattedVisibility = when (historyVisibility) {
|
val formattedVisibility = roomHistoryVisibilityFormatter.format(historyVisibility)
|
||||||
RoomHistoryVisibility.SHARED -> sp.getString(R.string.notice_room_visibility_shared)
|
|
||||||
RoomHistoryVisibility.INVITED -> sp.getString(R.string.notice_room_visibility_invited)
|
|
||||||
RoomHistoryVisibility.JOINED -> sp.getString(R.string.notice_room_visibility_joined)
|
|
||||||
RoomHistoryVisibility.WORLD_READABLE -> sp.getString(R.string.notice_room_visibility_world_readable)
|
|
||||||
}
|
|
||||||
return if (event.isSentByCurrentUser()) {
|
return if (event.isSentByCurrentUser()) {
|
||||||
sp.getString(R.string.notice_made_future_room_visibility_by_you, formattedVisibility)
|
sp.getString(R.string.notice_made_future_room_visibility_by_you, formattedVisibility)
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 im.vector.riotx.features.home.room.detail.timeline.format
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomHistoryVisibilityFormatter @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun format(roomHistoryVisibility: RoomHistoryVisibility): String {
|
||||||
|
return when (roomHistoryVisibility) {
|
||||||
|
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
|
||||||
|
RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited)
|
||||||
|
RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined)
|
||||||
|
RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,19 +16,41 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.media
|
package im.vector.riotx.features.media
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import com.yalantis.ucrop.UCrop
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
|
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||||
|
import im.vector.riotx.core.utils.allGranted
|
||||||
|
import im.vector.riotx.core.utils.checkPermissions
|
||||||
|
import im.vector.riotx.multipicker.MultiPicker
|
||||||
|
import im.vector.riotx.multipicker.entity.MultiPickerImageType
|
||||||
import kotlinx.android.synthetic.main.activity_big_image_viewer.*
|
import kotlinx.android.synthetic.main.activity_big_image_viewer.*
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class BigImageViewerActivity : VectorBaseActivity() {
|
class BigImageViewerActivity : VectorBaseActivity() {
|
||||||
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
@Inject lateinit var stringProvider: StringProvider
|
||||||
|
|
||||||
|
private var uri: Uri? = null
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.vector_big_avatar_viewer
|
||||||
|
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
@ -45,7 +67,7 @@ class BigImageViewerActivity : VectorBaseActivity() {
|
|||||||
setDisplayHomeAsUpEnabled(true)
|
setDisplayHomeAsUpEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val uri = sessionHolder.getSafeActiveSession()
|
uri = sessionHolder.getSafeActiveSession()
|
||||||
?.contentUrlResolver()
|
?.contentUrlResolver()
|
||||||
?.resolveFullSize(intent.getStringExtra(EXTRA_IMAGE_URL))
|
?.resolveFullSize(intent.getStringExtra(EXTRA_IMAGE_URL))
|
||||||
?.toUri()
|
?.toUri()
|
||||||
@ -57,14 +79,110 @@ class BigImageViewerActivity : VectorBaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menu.findItem(R.id.bigAvatarEditAction).isVisible = shouldShowEditAction()
|
||||||
|
return super.onPrepareOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.bigAvatarEditAction) {
|
||||||
|
showAvatarSelector()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldShowEditAction(): Boolean {
|
||||||
|
return uri != null && intent.getBooleanExtra(EXTRA_CAN_EDIT_IMAGE, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showAvatarSelector() {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setItems(arrayOf(
|
||||||
|
stringProvider.getString(R.string.attachment_type_camera),
|
||||||
|
stringProvider.getString(R.string.attachment_type_gallery)
|
||||||
|
)) { dialog, which ->
|
||||||
|
dialog.cancel()
|
||||||
|
onAvatarTypeSelected(isCamera = (which == 0))
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var avatarCameraUri: Uri? = null
|
||||||
|
private fun onAvatarTypeSelected(isCamera: Boolean) {
|
||||||
|
if (isCamera) {
|
||||||
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||||
|
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRoomAvatarSelected(image: MultiPickerImageType) {
|
||||||
|
val destinationFile = File(cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
|
||||||
|
val uri = image.contentUri
|
||||||
|
createUCropWithDefaultSettings(this, uri, destinationFile.toUri(), image.displayName)
|
||||||
|
.apply { withAspectRatio(1f, 1f) }
|
||||||
|
.start(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
when (requestCode) {
|
||||||
|
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
|
||||||
|
avatarCameraUri?.let { uri ->
|
||||||
|
MultiPicker.get(MultiPicker.CAMERA)
|
||||||
|
.getTakenPhoto(this, requestCode, resultCode, uri)
|
||||||
|
?.let {
|
||||||
|
onRoomAvatarSelected(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
|
||||||
|
MultiPicker
|
||||||
|
.get(MultiPicker.IMAGE)
|
||||||
|
.getSelectedFiles(this, requestCode, resultCode, data)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
// TODO. UCrop library cannot read from Gallery. For now, we will set avatar as it is.
|
||||||
|
// onRoomAvatarSelected(it)
|
||||||
|
onAvatarCropped(it.contentUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
if (allGranted(grantResults)) {
|
||||||
|
when (requestCode) {
|
||||||
|
PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -> onAvatarTypeSelected(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAvatarCropped(uri: Uri?) {
|
||||||
|
if (uri != null) {
|
||||||
|
setResult(Activity.RESULT_OK, Intent().setData(uri))
|
||||||
|
this@BigImageViewerActivity.finish()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_TITLE = "EXTRA_TITLE"
|
private const val EXTRA_TITLE = "EXTRA_TITLE"
|
||||||
private const val EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL"
|
private const val EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL"
|
||||||
|
private const val EXTRA_CAN_EDIT_IMAGE = "EXTRA_CAN_EDIT_IMAGE"
|
||||||
|
const val REQUEST_CODE = 1000
|
||||||
|
|
||||||
fun newIntent(context: Context, title: String?, imageUrl: String): Intent {
|
fun newIntent(context: Context, title: String?, imageUrl: String, canEditImage: Boolean = false): Intent {
|
||||||
return Intent(context, BigImageViewerActivity::class.java).apply {
|
return Intent(context, BigImageViewerActivity::class.java).apply {
|
||||||
putExtra(EXTRA_TITLE, title)
|
putExtra(EXTRA_TITLE, title)
|
||||||
putExtra(EXTRA_IMAGE_URL, imageUrl)
|
putExtra(EXTRA_IMAGE_URL, imageUrl)
|
||||||
|
putExtra(EXTRA_CAN_EDIT_IMAGE, canEditImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 im.vector.riotx.features.media
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.yalantis.ucrop.UCrop
|
||||||
|
import com.yalantis.ucrop.UCropActivity
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
|
||||||
|
fun createUCropWithDefaultSettings(context: Context, source: Uri, destination: Uri, toolbarTitle: String?): UCrop {
|
||||||
|
return UCrop.of(source, destination)
|
||||||
|
.withOptions(
|
||||||
|
UCrop.Options()
|
||||||
|
.apply {
|
||||||
|
setAllowedGestures(
|
||||||
|
/* tabScale = */ UCropActivity.SCALE,
|
||||||
|
/* tabRotate = */ UCropActivity.ALL,
|
||||||
|
/* tabAspectRatio = */ UCropActivity.SCALE
|
||||||
|
)
|
||||||
|
setToolbarTitle(toolbarTitle)
|
||||||
|
// Disable freestyle crop, usability was not easy
|
||||||
|
// setFreeStyleCropEnabled(true)
|
||||||
|
// Color used for toolbar icon and text
|
||||||
|
setToolbarColor(ThemeUtils.getColor(context, R.attr.riotx_background))
|
||||||
|
setToolbarWidgetColor(ThemeUtils.getColor(context, R.attr.vctr_toolbar_primary_text_color))
|
||||||
|
// Background
|
||||||
|
setRootViewBackgroundColor(ThemeUtils.getColor(context, R.attr.riotx_background))
|
||||||
|
// Status bar color (pb in dark mode, icon of the status bar are dark)
|
||||||
|
setStatusBarColor(ThemeUtils.getColor(context, R.attr.riotx_header_panel_background))
|
||||||
|
// Known issue: there is still orange color used by the lib
|
||||||
|
// https://github.com/Yalantis/uCrop/issues/602
|
||||||
|
setActiveControlsWidgetColor(ContextCompat.getColor(context, R.color.riotx_accent))
|
||||||
|
// Hide the logo (does not work)
|
||||||
|
setLogoColor(Color.TRANSPARENT)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -17,11 +17,13 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.roomprofile
|
package im.vector.riotx.features.roomprofile
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class RoomProfileAction: VectorViewModelAction {
|
sealed class RoomProfileAction: VectorViewModelAction {
|
||||||
object LeaveRoom: RoomProfileAction()
|
object LeaveRoom: RoomProfileAction()
|
||||||
data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction()
|
data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction()
|
||||||
|
data class ChangeRoomAvatar(val uri: Uri, val fileName: String?) : RoomProfileAction()
|
||||||
object ShareRoomProfile : RoomProfileAction()
|
object ShareRoomProfile : RoomProfileAction()
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,23 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.roomprofile
|
package im.vector.riotx.features.roomprofile
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.yalantis.ucrop.UCrop
|
||||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
@ -36,7 +44,12 @@ import im.vector.riotx.core.extensions.cleanup
|
|||||||
import im.vector.riotx.core.extensions.configureWith
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.extensions.exhaustive
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.core.intent.getFilenameFromUri
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
|
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||||
|
import im.vector.riotx.core.utils.allGranted
|
||||||
|
import im.vector.riotx.core.utils.checkPermissions
|
||||||
import im.vector.riotx.core.utils.copyToClipboard
|
import im.vector.riotx.core.utils.copyToClipboard
|
||||||
import im.vector.riotx.core.utils.startSharePlainTextIntent
|
import im.vector.riotx.core.utils.startSharePlainTextIntent
|
||||||
import im.vector.riotx.features.crypto.util.toImageRes
|
import im.vector.riotx.features.crypto.util.toImageRes
|
||||||
@ -45,10 +58,15 @@ import im.vector.riotx.features.home.room.list.actions.RoomListActionsArgs
|
|||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||||
|
import im.vector.riotx.features.media.BigImageViewerActivity
|
||||||
|
import im.vector.riotx.features.media.createUCropWithDefaultSettings
|
||||||
|
import im.vector.riotx.multipicker.MultiPicker
|
||||||
|
import im.vector.riotx.multipicker.entity.MultiPickerImageType
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_matrix_profile.*
|
import kotlinx.android.synthetic.main.fragment_matrix_profile.*
|
||||||
import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
|
import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@ -96,6 +114,7 @@ class RoomProfileFragment @Inject constructor(
|
|||||||
is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
|
is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
|
||||||
is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
|
is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
|
||||||
is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink)
|
is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink)
|
||||||
|
RoomProfileViewEvents.OnChangeAvatarSuccess -> dismissLoadingDialog()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
roomListQuickActionsSharedActionViewModel
|
roomListQuickActionsSharedActionViewModel
|
||||||
@ -221,7 +240,89 @@ class RoomProfileFragment @Inject constructor(
|
|||||||
startSharePlainTextIntent(fragment = this, chooserTitle = null, text = permalink)
|
startSharePlainTextIntent(fragment = this, chooserTitle = null, text = permalink)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) {
|
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) = withState(roomProfileViewModel) {
|
||||||
navigator.openBigImageViewer(requireActivity(), view, matrixItem)
|
if (matrixItem.avatarUrl?.isNotEmpty() == true) {
|
||||||
|
val intent = BigImageViewerActivity.newIntent(requireContext(), matrixItem.getBestName(), matrixItem.avatarUrl!!, it.canChangeAvatar)
|
||||||
|
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity!!, view, ViewCompat.getTransitionName(view) ?: "")
|
||||||
|
startActivityForResult(intent, BigImageViewerActivity.REQUEST_CODE, options.toBundle())
|
||||||
|
} else if (it.canChangeAvatar) {
|
||||||
|
showAvatarSelector()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showAvatarSelector() {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setItems(arrayOf(
|
||||||
|
getString(R.string.attachment_type_camera),
|
||||||
|
getString(R.string.attachment_type_gallery)
|
||||||
|
)) { dialog, which ->
|
||||||
|
dialog.cancel()
|
||||||
|
onAvatarTypeSelected(isCamera = (which == 0))
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var avatarCameraUri: Uri? = null
|
||||||
|
private fun onAvatarTypeSelected(isCamera: Boolean) {
|
||||||
|
if (isCamera) {
|
||||||
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||||
|
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRoomAvatarSelected(image: MultiPickerImageType) {
|
||||||
|
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
|
||||||
|
val uri = image.contentUri
|
||||||
|
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
|
||||||
|
.apply { withAspectRatio(1f, 1f) }
|
||||||
|
.start(requireContext(), this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
when (requestCode) {
|
||||||
|
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
|
||||||
|
avatarCameraUri?.let { uri ->
|
||||||
|
MultiPicker.get(MultiPicker.CAMERA)
|
||||||
|
.getTakenPhoto(requireContext(), requestCode, resultCode, uri)
|
||||||
|
?.let {
|
||||||
|
onRoomAvatarSelected(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
|
||||||
|
MultiPicker
|
||||||
|
.get(MultiPicker.IMAGE)
|
||||||
|
.getSelectedFiles(requireContext(), requestCode, resultCode, data)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
// TODO. UCrop library cannot read from Gallery. For now, we will set avatar as it is.
|
||||||
|
// onRoomAvatarSelected(it)
|
||||||
|
onAvatarCropped(it.contentUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
|
||||||
|
BigImageViewerActivity.REQUEST_CODE -> data?.let { onAvatarCropped(it.data) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
if (allGranted(grantResults)) {
|
||||||
|
when (requestCode) {
|
||||||
|
PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -> onAvatarTypeSelected(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAvatarCropped(uri: Uri?) {
|
||||||
|
if (uri != null) {
|
||||||
|
roomProfileViewModel.handle(RoomProfileAction.ChangeRoomAvatar(uri, getFilenameFromUri(context, uri)))
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,5 +26,6 @@ sealed class RoomProfileViewEvents : VectorViewEvents {
|
|||||||
data class Failure(val throwable: Throwable) : RoomProfileViewEvents()
|
data class Failure(val throwable: Throwable) : RoomProfileViewEvents()
|
||||||
|
|
||||||
object OnLeaveRoomSuccess : RoomProfileViewEvents()
|
object OnLeaveRoomSuccess : RoomProfileViewEvents()
|
||||||
|
object OnChangeAvatarSuccess : RoomProfileViewEvents()
|
||||||
data class ShareRoomProfile(val permalink: String) : RoomProfileViewEvents()
|
data class ShareRoomProfile(val permalink: String) : RoomProfileViewEvents()
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,14 @@ import com.squareup.inject.assisted.AssistedInject
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
class RoomProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomProfileViewState,
|
class RoomProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomProfileViewState,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
@ -62,12 +65,22 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
|||||||
.execute {
|
.execute {
|
||||||
copy(roomSummary = it)
|
copy(roomSummary = it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable()
|
||||||
|
|
||||||
|
powerLevelsContentLive
|
||||||
|
.subscribe {
|
||||||
|
val powerLevelsHelper = PowerLevelsHelper(it)
|
||||||
|
setState { copy(canChangeAvatar = powerLevelsHelper.isUserAbleToChangeRoomAvatar(session.myUserId)) }
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: RoomProfileAction) = when (action) {
|
override fun handle(action: RoomProfileAction) = when (action) {
|
||||||
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
|
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
|
||||||
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||||
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
|
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
|
||||||
|
is RoomProfileAction.ChangeRoomAvatar -> handleChangeAvatar(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) {
|
private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) {
|
||||||
@ -96,4 +109,18 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
|||||||
_viewEvents.post(RoomProfileViewEvents.ShareRoomProfile(permalink))
|
_viewEvents.post(RoomProfileViewEvents.ShareRoomProfile(permalink))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleChangeAvatar(action: RoomProfileAction.ChangeRoomAvatar) {
|
||||||
|
_viewEvents.post(RoomProfileViewEvents.Loading())
|
||||||
|
room.rx().updateAvatar(action.uri, action.fileName ?: UUID.randomUUID().toString())
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
_viewEvents.post(RoomProfileViewEvents.OnChangeAvatarSuccess)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_viewEvents.post(RoomProfileViewEvents.Failure(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|||||||
|
|
||||||
data class RoomProfileViewState(
|
data class RoomProfileViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val roomSummary: Async<RoomSummary> = Uninitialized
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
|
val canChangeAvatar: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.roomprofile.settings
|
package im.vector.riotx.features.roomprofile.settings
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class RoomSettingsAction : VectorViewModelAction {
|
sealed class RoomSettingsAction : VectorViewModelAction {
|
||||||
data class SetRoomName(val newName: String) : RoomSettingsAction()
|
data class SetRoomName(val newName: String) : RoomSettingsAction()
|
||||||
data class SetRoomTopic(val newTopic: String) : RoomSettingsAction()
|
data class SetRoomTopic(val newTopic: String) : RoomSettingsAction()
|
||||||
data class SetRoomAvatar(val newAvatarUrl: String) : RoomSettingsAction()
|
data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction()
|
||||||
|
data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction()
|
||||||
object EnableEncryption : RoomSettingsAction()
|
object EnableEncryption : RoomSettingsAction()
|
||||||
|
object Save : RoomSettingsAction()
|
||||||
}
|
}
|
||||||
|
@ -17,21 +17,30 @@
|
|||||||
package im.vector.riotx.features.roomprofile.settings
|
package im.vector.riotx.features.roomprofile.settings
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
||||||
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.features.form.formEditTextItem
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO Add other feature here (waiting for design)
|
|
||||||
class RoomSettingsController @Inject constructor(
|
class RoomSettingsController @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter,
|
||||||
colorProvider: ColorProvider
|
colorProvider: ColorProvider
|
||||||
) : TypedEpoxyController<RoomSettingsViewState>() {
|
) : TypedEpoxyController<RoomSettingsViewState>() {
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
fun onEnableEncryptionClicked()
|
fun onEnableEncryptionClicked()
|
||||||
|
fun onNameChanged(name: String)
|
||||||
|
fun onTopicChanged(topic: String)
|
||||||
|
fun onHistoryVisibilityClicked()
|
||||||
|
fun onAliasChanged(alias: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||||
@ -45,10 +54,56 @@ class RoomSettingsController @Inject constructor(
|
|||||||
override fun buildModels(data: RoomSettingsViewState?) {
|
override fun buildModels(data: RoomSettingsViewState?) {
|
||||||
val roomSummary = data?.roomSummary?.invoke() ?: return
|
val roomSummary = data?.roomSummary?.invoke() ?: return
|
||||||
|
|
||||||
|
val historyVisibility = data.historyVisibilityEvent?.let { formatRoomHistoryVisibilityEvent(it) } ?: ""
|
||||||
|
val newHistoryVisibility = data.newHistoryVisibility?.let { roomHistoryVisibilityFormatter.format(it) }
|
||||||
|
|
||||||
buildProfileSection(
|
buildProfileSection(
|
||||||
stringProvider.getString(R.string.settings)
|
stringProvider.getString(R.string.settings)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
formEditTextItem {
|
||||||
|
id("name")
|
||||||
|
enabled(data.actionPermissions.canChangeName)
|
||||||
|
value(data.newName ?: roomSummary.displayName)
|
||||||
|
hint(stringProvider.getString(R.string.room_settings_name_hint))
|
||||||
|
|
||||||
|
onTextChange { text ->
|
||||||
|
callback?.onNameChanged(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formEditTextItem {
|
||||||
|
id("topic")
|
||||||
|
enabled(data.actionPermissions.canChangeTopic)
|
||||||
|
value(data.newTopic ?: roomSummary.topic)
|
||||||
|
hint(stringProvider.getString(R.string.room_settings_topic_hint))
|
||||||
|
|
||||||
|
onTextChange { text ->
|
||||||
|
callback?.onTopicChanged(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formEditTextItem {
|
||||||
|
id("alias")
|
||||||
|
enabled(data.actionPermissions.canChangeCanonicalAlias)
|
||||||
|
value(data.newCanonicalAlias ?: roomSummary.canonicalAlias)
|
||||||
|
hint(stringProvider.getString(R.string.room_settings_addresses_add_new_address))
|
||||||
|
|
||||||
|
onTextChange { text ->
|
||||||
|
callback?.onAliasChanged(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildProfileAction(
|
||||||
|
id = "historyReadability",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title),
|
||||||
|
subtitle = newHistoryVisibility ?: historyVisibility,
|
||||||
|
dividerColor = dividerColor,
|
||||||
|
divider = false,
|
||||||
|
editable = data.actionPermissions.canChangeHistoryReadability,
|
||||||
|
action = { if (data.actionPermissions.canChangeHistoryReadability) callback?.onHistoryVisibilityClicked() }
|
||||||
|
)
|
||||||
|
|
||||||
if (roomSummary.isEncrypted) {
|
if (roomSummary.isEncrypted) {
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "encryption",
|
id = "encryption",
|
||||||
@ -69,4 +124,9 @@ class RoomSettingsController @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatRoomHistoryVisibilityEvent(event: Event): String? {
|
||||||
|
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
|
||||||
|
return roomHistoryVisibilityFormatter.format(historyVisibility)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,19 +17,26 @@
|
|||||||
package im.vector.riotx.features.roomprofile.settings
|
package im.vector.riotx.features.roomprofile.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.cleanup
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
import im.vector.riotx.core.extensions.configureWith
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.extensions.exhaustive
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.utils.toast
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
||||||
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
|
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
|
||||||
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
||||||
@ -38,6 +45,7 @@ import javax.inject.Inject
|
|||||||
class RoomSettingsFragment @Inject constructor(
|
class RoomSettingsFragment @Inject constructor(
|
||||||
val viewModelFactory: RoomSettingsViewModel.Factory,
|
val viewModelFactory: RoomSettingsViewModel.Factory,
|
||||||
private val controller: RoomSettingsController,
|
private val controller: RoomSettingsController,
|
||||||
|
private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter,
|
||||||
private val avatarRenderer: AvatarRenderer
|
private val avatarRenderer: AvatarRenderer
|
||||||
) : VectorBaseFragment(), RoomSettingsController.Callback {
|
) : VectorBaseFragment(), RoomSettingsController.Callback {
|
||||||
|
|
||||||
@ -46,6 +54,8 @@ class RoomSettingsFragment @Inject constructor(
|
|||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
|
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.vector_room_settings
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
controller.callback = this
|
controller.callback = this
|
||||||
@ -57,20 +67,50 @@ class RoomSettingsFragment @Inject constructor(
|
|||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is RoomSettingsViewEvents.Failure -> showFailure(it.throwable)
|
is RoomSettingsViewEvents.Failure -> showFailure(it.throwable)
|
||||||
|
is RoomSettingsViewEvents.Success -> showSuccess()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showSuccess() {
|
||||||
|
activity?.toast(R.string.room_settings_save_success)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
recyclerView.cleanup()
|
recyclerView.cleanup()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
withState(viewModel) { state ->
|
||||||
|
menu.findItem(R.id.roomSettingsSaveAction).isVisible = state.showSaveAction
|
||||||
|
}
|
||||||
|
super.onPrepareOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.roomSettingsSaveAction) {
|
||||||
|
viewModel.handle(RoomSettingsAction.Save)
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { viewState ->
|
override fun invalidate() = withState(viewModel) { viewState ->
|
||||||
controller.setData(viewState)
|
controller.setData(viewState)
|
||||||
renderRoomSummary(viewState)
|
renderRoomSummary(viewState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderRoomSummary(state: RoomSettingsViewState) {
|
||||||
|
waiting_view.isVisible = state.isLoading
|
||||||
|
|
||||||
|
state.roomSummary()?.let {
|
||||||
|
roomSettingsToolbarTitleView.text = it.displayName
|
||||||
|
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onEnableEncryptionClicked() {
|
override fun onEnableEncryptionClicked() {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.room_settings_enable_encryption_dialog_title)
|
.setTitle(R.string.room_settings_enable_encryption_dialog_title)
|
||||||
@ -82,12 +122,43 @@ class RoomSettingsFragment @Inject constructor(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderRoomSummary(state: RoomSettingsViewState) {
|
override fun onNameChanged(name: String) {
|
||||||
waiting_view.isVisible = state.isLoading
|
viewModel.handle(RoomSettingsAction.SetRoomName(name))
|
||||||
|
}
|
||||||
|
|
||||||
state.roomSummary()?.let {
|
override fun onTopicChanged(topic: String) {
|
||||||
roomSettingsToolbarTitleView.text = it.displayName
|
viewModel.handle(RoomSettingsAction.SetRoomTopic(topic))
|
||||||
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
|
}
|
||||||
|
|
||||||
|
override fun onHistoryVisibilityClicked() = withState(viewModel) { state ->
|
||||||
|
val historyVisibilities = arrayOf(
|
||||||
|
RoomHistoryVisibility.SHARED,
|
||||||
|
RoomHistoryVisibility.INVITED,
|
||||||
|
RoomHistoryVisibility.JOINED,
|
||||||
|
RoomHistoryVisibility.WORLD_READABLE
|
||||||
|
)
|
||||||
|
val currentHistoryVisibility =
|
||||||
|
state.newHistoryVisibility ?: state.historyVisibilityEvent?.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
|
||||||
|
val currentHistoryVisibilityIndex = historyVisibilities.indexOf(currentHistoryVisibility)
|
||||||
|
|
||||||
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
|
setTitle(R.string.room_settings_room_read_history_rules_pref_title)
|
||||||
|
setSingleChoiceItems(
|
||||||
|
historyVisibilities
|
||||||
|
.map { roomHistoryVisibilityFormatter.format(it) }
|
||||||
|
.toTypedArray(),
|
||||||
|
currentHistoryVisibilityIndex) { dialog, which ->
|
||||||
|
if (which != currentHistoryVisibilityIndex) {
|
||||||
|
viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(historyVisibilities[which]))
|
||||||
|
}
|
||||||
|
dialog.cancel()
|
||||||
|
}
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
|
return@withState
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAliasChanged(alias: String) {
|
||||||
|
viewModel.handle(RoomSettingsAction.SetRoomCanonicalAlias(alias))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,4 +24,5 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
|||||||
*/
|
*/
|
||||||
sealed class RoomSettingsViewEvents : VectorViewEvents {
|
sealed class RoomSettingsViewEvents : VectorViewEvents {
|
||||||
data class Failure(val throwable: Throwable) : RoomSettingsViewEvents()
|
data class Failure(val throwable: Throwable) : RoomSettingsViewEvents()
|
||||||
|
object Success : RoomSettingsViewEvents()
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,15 @@ import com.squareup.inject.assisted.Assisted
|
|||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Observable
|
||||||
|
|
||||||
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
|
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
|
||||||
private val session: Session)
|
private val session: Session)
|
||||||
@ -49,41 +55,130 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
observeRoomSummary()
|
observeRoomSummary()
|
||||||
|
observeState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeState() {
|
||||||
|
selectSubscribe(
|
||||||
|
RoomSettingsViewState::newName,
|
||||||
|
RoomSettingsViewState::newCanonicalAlias,
|
||||||
|
RoomSettingsViewState::newTopic,
|
||||||
|
RoomSettingsViewState::newHistoryVisibility,
|
||||||
|
RoomSettingsViewState::roomSummary) { newName,
|
||||||
|
newCanonicalAlias,
|
||||||
|
newTopic,
|
||||||
|
newHistoryVisibility,
|
||||||
|
asyncSummary ->
|
||||||
|
val summary = asyncSummary()
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
showSaveAction = summary?.name != newName
|
||||||
|
|| summary?.topic != newTopic
|
||||||
|
|| summary?.canonicalAlias != newCanonicalAlias?.takeIf { it.isNotEmpty() }
|
||||||
|
|| newHistoryVisibility != null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx().liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
copy(roomSummary = async)
|
val roomSummary = async.invoke()
|
||||||
|
copy(
|
||||||
|
historyVisibilityEvent = room.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY),
|
||||||
|
roomSummary = async,
|
||||||
|
newName = roomSummary?.name,
|
||||||
|
newTopic = roomSummary?.topic,
|
||||||
|
newCanonicalAlias = roomSummary?.canonicalAlias
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable()
|
||||||
|
|
||||||
|
powerLevelsContentLive
|
||||||
|
.subscribe {
|
||||||
|
val powerLevelsHelper = PowerLevelsHelper(it)
|
||||||
|
val permissions = RoomSettingsViewState.ActionPermissions(
|
||||||
|
canChangeName = powerLevelsHelper.isUserAbleToChangeRoomName(session.myUserId),
|
||||||
|
canChangeTopic = powerLevelsHelper.isUserAbleToChangeRoomTopic(session.myUserId),
|
||||||
|
canChangeCanonicalAlias = powerLevelsHelper.isUserAbleToChangeRoomCanonicalAlias(session.myUserId),
|
||||||
|
canChangeHistoryReadability = powerLevelsHelper.isUserAbleToChangeRoomHistoryReadability(session.myUserId)
|
||||||
|
)
|
||||||
|
setState { copy(actionPermissions = permissions) }
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: RoomSettingsAction) {
|
override fun handle(action: RoomSettingsAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomSettingsAction.EnableEncryption -> handleEnableEncryption()
|
is RoomSettingsAction.EnableEncryption -> handleEnableEncryption()
|
||||||
|
is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) }
|
||||||
|
is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) }
|
||||||
|
is RoomSettingsAction.SetRoomHistoryVisibility -> setState { copy(newHistoryVisibility = action.visibility) }
|
||||||
|
is RoomSettingsAction.SetRoomCanonicalAlias -> setState { copy(newCanonicalAlias = action.newCanonicalAlias) }
|
||||||
|
is RoomSettingsAction.Save -> saveSettings()
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveSettings() = withState { state ->
|
||||||
|
postLoading(true)
|
||||||
|
|
||||||
|
val operationList = mutableListOf<Completable>()
|
||||||
|
|
||||||
|
val summary = state.roomSummary.invoke()
|
||||||
|
|
||||||
|
if (summary?.name != state.newName) {
|
||||||
|
operationList.add(room.rx().updateName(state.newName ?: ""))
|
||||||
}
|
}
|
||||||
|
if (summary?.topic != state.newTopic) {
|
||||||
|
operationList.add(room.rx().updateTopic(state.newTopic ?: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.newCanonicalAlias != null && summary?.canonicalAlias != state.newCanonicalAlias.takeIf { it.isNotEmpty() }) {
|
||||||
|
operationList.add(room.rx().addRoomAlias(state.newCanonicalAlias))
|
||||||
|
operationList.add(room.rx().updateCanonicalAlias(state.newCanonicalAlias))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.newHistoryVisibility != null) {
|
||||||
|
operationList.add(room.rx().updateHistoryReadability(state.newHistoryVisibility))
|
||||||
|
}
|
||||||
|
|
||||||
|
Observable
|
||||||
|
.fromIterable(operationList)
|
||||||
|
.concatMapCompletable { it }
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
postLoading(false)
|
||||||
|
setState { copy(newHistoryVisibility = null) }
|
||||||
|
_viewEvents.post(RoomSettingsViewEvents.Success)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
postLoading(false)
|
||||||
|
_viewEvents.post(RoomSettingsViewEvents.Failure(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnableEncryption() {
|
private fun handleEnableEncryption() {
|
||||||
setState {
|
postLoading(true)
|
||||||
copy(isLoading = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
room.enableEncryption(callback = object : MatrixCallback<Unit> {
|
room.enableEncryption(callback = object : MatrixCallback<Unit> {
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
setState {
|
postLoading(false)
|
||||||
copy(isLoading = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
_viewEvents.post(RoomSettingsViewEvents.Failure(failure))
|
_viewEvents.post(RoomSettingsViewEvents.Failure(failure))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
setState {
|
postLoading(false)
|
||||||
copy(isLoading = false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun postLoading(isLoading: Boolean) {
|
||||||
|
setState {
|
||||||
|
copy(isLoading = isLoading)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,30 @@ package im.vector.riotx.features.roomprofile.settings
|
|||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
||||||
|
|
||||||
data class RoomSettingsViewState(
|
data class RoomSettingsViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
val historyVisibilityEvent: Event? = null,
|
||||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val isLoading: Boolean = false
|
val isLoading: Boolean = false,
|
||||||
|
val newName: String? = null,
|
||||||
|
val newTopic: String? = null,
|
||||||
|
val newHistoryVisibility: RoomHistoryVisibility? = null,
|
||||||
|
val newCanonicalAlias: String? = null,
|
||||||
|
val showSaveAction: Boolean = false,
|
||||||
|
val actionPermissions: ActionPermissions = ActionPermissions()
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
|
||||||
|
data class ActionPermissions(
|
||||||
|
val canChangeName: Boolean = false,
|
||||||
|
val canChangeTopic: Boolean = false,
|
||||||
|
val canChangeCanonicalAlias: Boolean = false,
|
||||||
|
val canChangeHistoryReadability: Boolean = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,16 @@ package im.vector.riotx.features.settings
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
@ -36,6 +39,7 @@ import com.bumptech.glide.Glide
|
|||||||
import com.bumptech.glide.load.engine.cache.DiskCache
|
import com.bumptech.glide.load.engine.cache.DiskCache
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.yalantis.ucrop.UCrop
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||||
import im.vector.matrix.android.api.failure.isInvalidPassword
|
import im.vector.matrix.android.api.failure.isInvalidPassword
|
||||||
@ -44,25 +48,32 @@ import im.vector.matrix.android.api.session.integrationmanager.IntegrationManage
|
|||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.showPassword
|
import im.vector.riotx.core.extensions.showPassword
|
||||||
|
import im.vector.riotx.core.intent.getFilenameFromUri
|
||||||
import im.vector.riotx.core.platform.SimpleTextWatcher
|
import im.vector.riotx.core.platform.SimpleTextWatcher
|
||||||
import im.vector.riotx.core.preference.UserAvatarPreference
|
import im.vector.riotx.core.preference.UserAvatarPreference
|
||||||
import im.vector.riotx.core.preference.VectorPreference
|
import im.vector.riotx.core.preference.VectorPreference
|
||||||
import im.vector.riotx.core.preference.VectorSwitchPreference
|
import im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
|
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||||
import im.vector.riotx.core.utils.TextUtils
|
import im.vector.riotx.core.utils.TextUtils
|
||||||
import im.vector.riotx.core.utils.allGranted
|
import im.vector.riotx.core.utils.allGranted
|
||||||
|
import im.vector.riotx.core.utils.checkPermissions
|
||||||
import im.vector.riotx.core.utils.copyToClipboard
|
import im.vector.riotx.core.utils.copyToClipboard
|
||||||
import im.vector.riotx.core.utils.getSizeOfFiles
|
import im.vector.riotx.core.utils.getSizeOfFiles
|
||||||
import im.vector.riotx.core.utils.toast
|
import im.vector.riotx.core.utils.toast
|
||||||
import im.vector.riotx.features.MainActivity
|
import im.vector.riotx.features.MainActivity
|
||||||
import im.vector.riotx.features.MainActivityArgs
|
import im.vector.riotx.features.MainActivityArgs
|
||||||
|
import im.vector.riotx.features.media.createUCropWithDefaultSettings
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
import im.vector.riotx.features.workers.signout.SignOutUiWorker
|
import im.vector.riotx.features.workers.signout.SignOutUiWorker
|
||||||
|
import im.vector.riotx.multipicker.MultiPicker
|
||||||
|
import im.vector.riotx.multipicker.entity.MultiPickerImageType
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
||||||
|
|
||||||
@ -72,6 +83,8 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
|||||||
private var mDisplayedEmails = ArrayList<String>()
|
private var mDisplayedEmails = ArrayList<String>()
|
||||||
private var mDisplayedPhoneNumber = ArrayList<String>()
|
private var mDisplayedPhoneNumber = ArrayList<String>()
|
||||||
|
|
||||||
|
private var avatarCameraUri: Uri? = null
|
||||||
|
|
||||||
private val mUserSettingsCategory by lazy {
|
private val mUserSettingsCategory by lazy {
|
||||||
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_USER_SETTINGS_PREFERENCE_KEY)!!
|
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_USER_SETTINGS_PREFERENCE_KEY)!!
|
||||||
}
|
}
|
||||||
@ -281,7 +294,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
|||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
if (allGranted(grantResults)) {
|
if (allGranted(grantResults)) {
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
||||||
changeAvatar()
|
onAvatarTypeSelected(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,8 +304,27 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
|||||||
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
|
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
|
||||||
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
|
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
|
||||||
|
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
|
||||||
|
avatarCameraUri?.let { uri ->
|
||||||
|
MultiPicker.get(MultiPicker.CAMERA)
|
||||||
|
.getTakenPhoto(requireContext(), requestCode, resultCode, uri)
|
||||||
|
?.let {
|
||||||
|
onAvatarSelected(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
|
||||||
|
MultiPicker
|
||||||
|
.get(MultiPicker.IMAGE)
|
||||||
|
.getSelectedFiles(requireContext(), requestCode, resultCode, data)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
// TODO. UCrop library cannot read from Gallery. For now, we will set avatar as it is.
|
||||||
|
onAvatarCropped(it.contentUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
|
||||||
/* TODO
|
/* TODO
|
||||||
VectorUtils.TAKE_IMAGE -> {
|
VectorUtils.TAKE_IMAGE -> {
|
||||||
val thumbnailUri = VectorUtils.getThumbnailUriFromIntent(activity, data, session.mediaCache)
|
val thumbnailUri = VectorUtils.getThumbnailUriFromIntent(activity, data, session.mediaCache)
|
||||||
@ -370,21 +402,59 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
|||||||
* Update the avatar.
|
* Update the avatar.
|
||||||
*/
|
*/
|
||||||
private fun onUpdateAvatarClick() {
|
private fun onUpdateAvatarClick() {
|
||||||
notImplemented()
|
AlertDialog
|
||||||
|
.Builder(requireContext())
|
||||||
/* TODO
|
.setItems(arrayOf(
|
||||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
getString(R.string.attachment_type_camera),
|
||||||
changeAvatar()
|
getString(R.string.attachment_type_gallery)
|
||||||
}
|
)) { dialog, which ->
|
||||||
*/
|
dialog.cancel()
|
||||||
|
onAvatarTypeSelected(isCamera = (which == 0))
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeAvatar() {
|
private fun onAvatarTypeSelected(isCamera: Boolean) {
|
||||||
/* TODO
|
if (isCamera) {
|
||||||
val intent = Intent(activity, VectorMediaPickerActivity::class.java)
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||||
intent.putExtra(VectorMediaPickerActivity.EXTRA_AVATAR_MODE, true)
|
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
|
||||||
startActivityForResult(intent, VectorUtils.TAKE_IMAGE)
|
}
|
||||||
*/
|
} else {
|
||||||
|
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAvatarSelected(image: MultiPickerImageType) {
|
||||||
|
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
|
||||||
|
val uri = image.contentUri
|
||||||
|
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
|
||||||
|
.apply { withAspectRatio(1f, 1f) }
|
||||||
|
.start(requireContext(), this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAvatarCropped(uri: Uri?) {
|
||||||
|
if (uri != null) {
|
||||||
|
uploadAvatar(uri)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uploadAvatar(uri: Uri) {
|
||||||
|
displayLoadingView()
|
||||||
|
|
||||||
|
session.updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString(), object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
if (!isAdded) return
|
||||||
|
|
||||||
|
mUserAvatarPreference.refreshAvatar()
|
||||||
|
onCommonDone(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
if (!isAdded) return
|
||||||
|
onCommonDone(failure.localizedMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
@ -505,9 +575,9 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
|||||||
} */
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
// Email management
|
// Email management
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the emails list
|
* Refresh the emails list
|
||||||
@ -632,47 +702,47 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
|||||||
*
|
*
|
||||||
* @param pid the used pid.
|
* @param pid the used pid.
|
||||||
*/
|
*/
|
||||||
/* TODO
|
/* TODO
|
||||||
private fun showEmailValidationDialog(pid: ThreePid) {
|
private fun showEmailValidationDialog(pid: ThreePid) {
|
||||||
activity?.let {
|
activity?.let {
|
||||||
AlertDialog.Builder(it)
|
AlertDialog.Builder(it)
|
||||||
.setTitle(R.string.account_email_validation_title)
|
.setTitle(R.string.account_email_validation_title)
|
||||||
.setMessage(R.string.account_email_validation_message)
|
.setMessage(R.string.account_email_validation_message)
|
||||||
.setPositiveButton(R.string._continue) { _, _ ->
|
.setPositiveButton(R.string._continue) { _, _ ->
|
||||||
session.myUser.add3Pid(pid, true, object : MatrixCallback<Unit> {
|
session.myUser.add3Pid(pid, true, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(info: Void?) {
|
override fun onSuccess(info: Void?) {
|
||||||
|
it.runOnUiThread {
|
||||||
|
hideLoadingView()
|
||||||
|
refreshEmailsList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNetworkError(e: Exception) {
|
||||||
|
onCommonDone(e.localizedMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMatrixError(e: MatrixError) {
|
||||||
|
if (TextUtils.equals(e.errcode, MatrixError.THREEPID_AUTH_FAILED)) {
|
||||||
it.runOnUiThread {
|
it.runOnUiThread {
|
||||||
hideLoadingView()
|
hideLoadingView()
|
||||||
refreshEmailsList()
|
it.toast(R.string.account_email_validation_error)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
override fun onNetworkError(e: Exception) {
|
|
||||||
onCommonDone(e.localizedMessage)
|
onCommonDone(e.localizedMessage)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMatrixError(e: MatrixError) {
|
override fun onUnexpectedError(e: Exception) {
|
||||||
if (TextUtils.equals(e.errcode, MatrixError.THREEPID_AUTH_FAILED)) {
|
onCommonDone(e.localizedMessage)
|
||||||
it.runOnUiThread {
|
}
|
||||||
hideLoadingView()
|
})
|
||||||
it.toast(R.string.account_email_validation_error)
|
}
|
||||||
}
|
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||||
} else {
|
hideLoadingView()
|
||||||
onCommonDone(e.localizedMessage)
|
}
|
||||||
}
|
.show()
|
||||||
}
|
}
|
||||||
|
} */
|
||||||
override fun onUnexpectedError(e: Exception) {
|
|
||||||
onCommonDone(e.localizedMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
|
||||||
hideLoadingView()
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a dialog which asks confirmation for the deletion of a 3pid
|
* Display a dialog which asks confirmation for the deletion of a 3pid
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:minHeight="@dimen/item_form_min_height">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/formTextInputTextInputLayout"
|
||||||
|
style="@style/VectorTextInputLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/formTextInputDivider"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/formTextInputButton"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/formTextInputTextInputEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:hint="@string/create_room_name_hint" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/formTextInputButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:background="?attr/colorAccent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/formTextInputTextInputLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/formTextInputTextInputLayout"
|
||||||
|
tools:text="Add" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/formTextInputDivider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="?riotx_header_panel_border_mobile"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
68
vector/src/main/res/layout/view_avatar_selector.xml
Normal file
68
vector/src/main/res/layout/view_avatar_selector.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@drawable/bg_attachment_type_selector"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="2">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/avatarCameraButton"
|
||||||
|
style="@style/AttachmentTypeSelectorButton"
|
||||||
|
android:contentDescription="@string/attachment_type_camera"
|
||||||
|
android:src="@drawable/ic_attachment_camera_white_24dp"
|
||||||
|
tools:background="@color/riotx_accent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/AttachmentTypeSelectorLabel"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:text="@string/attachment_type_camera" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/avatarGalleryButton"
|
||||||
|
style="@style/AttachmentTypeSelectorButton"
|
||||||
|
android:contentDescription="@string/attachment_type_gallery"
|
||||||
|
android:src="@drawable/ic_attachment_gallery_white_24dp"
|
||||||
|
tools:background="@color/riotx_accent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/AttachmentTypeSelectorLabel"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:text="@string/attachment_type_gallery" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
10
vector/src/main/res/menu/vector_big_avatar_viewer.xml
Normal file
10
vector/src/main/res/menu/vector_big_avatar_viewer.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/bigAvatarEditAction"
|
||||||
|
android:title="@string/edit"
|
||||||
|
android:icon="@drawable/ic_edit"
|
||||||
|
app:iconTint="?attr/colorAccent"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
</menu>
|
9
vector/src/main/res/menu/vector_room_settings.xml
Normal file
9
vector/src/main/res/menu/vector_room_settings.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/roomSettingsSaveAction"
|
||||||
|
android:title="@string/save"
|
||||||
|
app:iconTint="?attr/colorAccent"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
</menu>
|
@ -2496,4 +2496,9 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
|
|||||||
<string name="save_your_security_key_title">Save your Security Key</string>
|
<string name="save_your_security_key_title">Save your Security Key</string>
|
||||||
<string name="save_your_security_key_notice">Store your Security Key somewhere safe, like a password manager or a safe.</string>
|
<string name="save_your_security_key_notice">Store your Security Key somewhere safe, like a password manager or a safe.</string>
|
||||||
|
|
||||||
</resources>
|
<!-- Room Settings -->
|
||||||
|
<string name="room_settings_name_hint">Room Name</string>
|
||||||
|
<string name="room_settings_topic_hint">Topic</string>
|
||||||
|
<string name="room_settings_save_success">You changed room settings successfully</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user