Refactor Account Data

Auto stash before rebase of "develop"
This commit is contained in:
Valere 2020-02-10 10:51:58 +01:00 committed by Valere
parent a250a895fe
commit bf06b57bad
27 changed files with 690 additions and 20 deletions

View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import io.reactivex.Observable
import io.reactivex.Single
@ -121,6 +122,13 @@ class RxSession(private val session: Session) {
session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional()
}
}
fun liveAccountData(filter: List<String>): Observable<List<UserAccountData>> {
return session.getLiveAccountData(filter).asObservable()
.startWithCallable {
session.getAccountData(filter)
}
}
}
fun Session.rx(): RxSession {

View File

@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.failure.GlobalError
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver
@ -57,7 +58,8 @@ interface Session :
PushersService,
InitialSyncProgressService,
HomeServerCapabilitiesService,
SecureStorageService {
SecureStorageService,
AccountDataService {
/**
* The params associated to the session

View File

@ -0,0 +1,35 @@
/*
* 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.api.session.accountdata
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
interface AccountDataService {
fun getAccountData(type: String): UserAccountData?
fun getLiveAccountData(type: String): LiveData<Optional<UserAccountData>>
fun getAccountData(filterType: List<String>): List<UserAccountData>
fun getLiveAccountData(filterType: List<String>): LiveData<List<UserAccountData>>
fun updateAccountData(type: String, data: Any, callback: MatrixCallback<Unit>? = null)
}

View File

@ -66,6 +66,9 @@ object EventType {
const val ROOM_KEY_REQUEST = "m.room_key_request"
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
const val REQUEST_SECRET = "m.secret.request"
const val SEND_SECRET = "m.secret.send"
// Interactive key verification
const val KEY_VERIFICATION_START = "m.key.verification.start"
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"

View File

@ -0,0 +1,73 @@
/*
* 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.api.session.securestorage
import im.vector.matrix.android.api.MatrixCallback
/**
* Some features may require clients to store encrypted data on the server so that it can be shared securely between clients.
* Clients may also wish to securely send such data directly to each other.
* For example, key backups (MSC1219) can store the decryption key for the backups on the server, or cross-signing (MSC1756) can store the signing keys.
*
* https://github.com/matrix-org/matrix-doc/pull/1946
*
*/
interface SharedSecretStorageService {
/**
* Add a key for encrypting secrets.
*
* @param algorithm the algorithm used by the key.
* @param opts the options for the algorithm. The properties used
* depend on the algorithm given.
* @param keyId the ID of the key
*
* @return {string} the ID of the key
*/
fun addKey(algorithm: String, opts: Map<String, Any>, keyId: String, callback: MatrixCallback<String>)
/**
* Check whether we have a key with a given ID.
*
* @param keyId The ID of the key to check
* @return Whether we have the key.
*/
fun hasKey(keyId: String): Boolean
/**
* Store an encrypted secret on the server
*
* @param name The name of the secret
* @param secret The secret contents.
* @param keys The IDs of the keys to use to encrypt the secret or null to use the default key.
*/
fun storeSecret(name: String, secretBase64: String, keys: List<String>?, callback: MatrixCallback<Unit>)
/**
* Get an encrypted secret from the shared storage
*
* @param name The name of the secret
* @param keyId The id of the key that should be used to decrypt
* @param privateKey the passphrase/secret
*
* @return The decrypted value
*/
fun getSecret(name: String, keyId: String, privateKey: String) : String
}

View File

@ -0,0 +1,39 @@
/*
* 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.crypto.secrets
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
internal class DefaultSharedSecureStorage : SharedSecretStorageService {
override fun addKey(algorithm: String, opts: Map<String, Any>, keyId: String, callback: MatrixCallback<String>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun hasKey(keyId: String): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun storeSecret(name: String, secretBase64: String, keys: List<String>?, callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getSecret(name: String, keyId: String, privateKey: String): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -53,6 +53,7 @@ import io.realm.annotations.RealmModule
DraftEntity::class,
HomeServerCapabilitiesEntity::class,
RoomMemberSummaryEntity::class,
CurrentStateEventEntity::class
CurrentStateEventEntity::class,
UserAccountDataEntity::class
])
internal class SessionRealmModule

View File

@ -0,0 +1,33 @@
/*
* 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.database.model
import io.realm.RealmObject
import io.realm.annotations.Index
/**
* Clients can store custom config data for their account on their homeserver.
* This account data will be synced between different devices and can persist across installations on a particular device.
* Users may only view the account data for their own accountThe account_data may be either global or scoped to a particular rooms.
*/
internal open class UserAccountDataEntity(
@Index var type: String? = null,
var contentStr: String? = null
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,29 @@
/*
* 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.network.parsing
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import java.lang.reflect.Type
class AccountDataJsonAdapterFactory<UserAccountData> : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.failure.GlobalError
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.InitialSyncProgressService
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver
@ -91,6 +92,7 @@ internal class DefaultSession @Inject constructor(
private val contentUploadProgressTracker: ContentUploadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<AccountDataService>,
private val shieldTrustUpdater: ShieldTrustUpdater)
: Session,
RoomService by roomService.get(),
@ -106,7 +108,8 @@ internal class DefaultSession @Inject constructor(
InitialSyncProgressService by initialSyncProgressService.get(),
SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
ProfileService by profileService.get() {
ProfileService by profileService.get(),
AccountDataService by accountDataService.get() {
private var isOpen = false

View File

@ -32,6 +32,7 @@ import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.api.crypto.MXCryptoConfig
import im.vector.matrix.android.api.session.InitialSyncProgressService
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver
@ -61,6 +62,7 @@ import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLive
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver
import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStorageService
import im.vector.matrix.android.internal.session.user.accountdata.DefaultAccountDataService
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
@ -263,4 +265,7 @@ internal abstract class SessionModule {
@Binds
abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
@Binds
abstract fun bindAccountDataServiceService(accountDataService: DefaultAccountDataService): AccountDataService
}

View File

@ -19,9 +19,12 @@ package im.vector.matrix.android.internal.session.sync
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.pushrules.RuleSetKey
import im.vector.matrix.android.api.session.events.model.Content
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.RoomMemberContent
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
@ -29,15 +32,18 @@ import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
import im.vector.matrix.android.internal.database.query.getDirectRooms
import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataSync
@ -45,6 +51,7 @@ import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHel
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import io.realm.Realm
import io.realm.RealmList
import io.realm.kotlin.where
import timber.log.Timber
import javax.inject.Inject
@ -56,21 +63,23 @@ internal class UserAccountDataSyncHandler @Inject constructor(
fun handle(realm: Realm, accountData: UserAccountDataSync?) {
accountData?.list?.forEach {
when (it) {
is UserAccountDataDirectMessages -> handleDirectChatRooms(realm, it)
is UserAccountDataPushRules -> handlePushRules(realm, it)
is UserAccountDataIgnoredUsers -> handleIgnoredUsers(realm, it)
is UserAccountDataBreadcrumbs -> handleBreadcrumbs(realm, it)
is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}")
else -> error("Missing code here!")
}
}
// Generic handling, just save in base
handleGenericAccountData(realm, it.type, it.content)
// TODO Store all account data, app can be interested of it
// accountData?.list?.forEach {
// it.toString()
// MoshiProvider.providesMoshi()
// }
// Didn't want to break too much thing, so i re-serialize to jsonString before reparsing
// TODO would be better to have a mapper?
val toJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJson(it)
val model = toJson?.let { json ->
MoshiProvider.providesMoshi().adapter(UserAccountData::class.java).fromJson(json)
}
// Specific parsing
when (model) {
is UserAccountDataDirectMessages -> handleDirectChatRooms(realm, model)
is UserAccountDataPushRules -> handlePushRules(realm, model)
is UserAccountDataIgnoredUsers -> handleIgnoredUsers(realm, model)
is UserAccountDataBreadcrumbs -> handleBreadcrumbs(realm, model)
}
}
}
// If we get some direct chat invites, we synchronize the user account data including those.
@ -200,4 +209,18 @@ internal class UserAccountDataSyncHandler @Inject constructor(
?.breadcrumbsIndex = index
}
}
private fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
val existing = realm.where<UserAccountDataEntity>().equalTo(UserAccountDataEntityFields.TYPE, type)
.findFirst()
if (existing != null) {
// Update current value
existing.contentStr = ContentMapper.map(content)
} else {
realm.createObject(UserAccountDataEntity::class.java).let { accountDataEntity ->
accountDataEntity.type = type
accountDataEntity.contentStr = ContentMapper.map(content)
}
}
}
}

View File

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
import com.squareup.moshi.Json
internal abstract class UserAccountData {
abstract class UserAccountData {
@Json(name = "type") abstract val type: String

View File

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class UserAccountDataFallback(
data class UserAccountDataFallback(
@Json(name = "type") override val type: String,
@Json(name = "content") val content: Map<String, Any>
) : UserAccountData()

View File

@ -18,8 +18,9 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Event
@JsonClass(generateAdapter = true)
internal data class UserAccountDataSync(
@Json(name = "events") val list: List<UserAccountData> = emptyList()
@Json(name = "events") val list: List<Event> = emptyList()
)

View File

@ -0,0 +1,101 @@
/*
* 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.user.accountdata
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import javax.inject.Inject
internal class DefaultAccountDataService @Inject constructor(
private val monarchy: Monarchy,
@SessionId private val sessionId: String,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val taskExecutor: TaskExecutor
) : AccountDataService {
private val moshi = MoshiProvider.providesMoshi()
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
override fun getAccountData(type: String): UserAccountData? {
return getAccountData(listOf(type)).firstOrNull()
}
override fun getLiveAccountData(type: String): LiveData<Optional<UserAccountData>> {
return Transformations.map(getLiveAccountData(listOf(type))) {
it.firstOrNull()?.toOptional()
}
}
override fun getAccountData(filterType: List<String>): List<UserAccountData> {
return monarchy.fetchAllCopiedSync { realm ->
realm.where(UserAccountDataEntity::class.java)
.apply {
if (filterType.isNotEmpty()) {
`in`(UserAccountDataEntityFields.TYPE, filterType.toTypedArray())
}
}
}?.mapNotNull { entity ->
entity.type?.let { type ->
UserAccountDataFallback(
type = type,
content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap()
)
}
} ?: emptyList()
}
override fun getLiveAccountData(filterType: List<String>): LiveData<List<UserAccountData>> {
return monarchy.findAllMappedWithChanges({ realm ->
realm.where(UserAccountDataEntity::class.java)
.apply {
if (filterType.isNotEmpty()) {
`in`(UserAccountDataEntityFields.TYPE, filterType.toTypedArray())
}
}
}, { entity ->
UserAccountDataFallback(
type = entity.type ?: "",
content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap()
)
})
}
override fun updateAccountData(type: String, data: Any, callback: MatrixCallback<Unit>?) {
updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams(
type = type,
any = data
)) {
this.retryCount = 5
callback?.let { this.callback = it }
}
.executeBy(taskExecutor)
}
}

View File

@ -49,6 +49,14 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
return breadcrumbsContent
}
}
data class AnyParams(override val type: String,
private val any: Any
) : Params {
override fun getData(): Any {
return any
}
}
}
internal class DefaultUpdateUserAccountDataTask @Inject constructor(

View File

@ -354,6 +354,9 @@ dependencies {
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
// Json viewer
implementation "com.yuyh.json:jsonviewer:1.0.6"
// gplay flavor only
// Warning: due to the exclude, Android Studio does not propose to upgrade. Uncomment next line to be proposed to upgrade
// implementation 'com.google.firebase:firebase-messaging:20.0.0'

View File

@ -374,6 +374,11 @@ SOFTWARE.
<br/>
Copyright (c) 2014 Dushyanth Maguluru
</li>
<li>
<b>JsonViewer</b>
<br/>
Copyright 2017 smuyyh, All right reserved.
</li>
</ul>
<pre>
Apache License

View File

@ -72,6 +72,7 @@ import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
import im.vector.riotx.features.settings.crosssigning.CrossSigningSettingsFragment
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
import im.vector.riotx.features.settings.devtools.AccountDataFragment
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
import im.vector.riotx.features.settings.push.PushGatewaysFragment
import im.vector.riotx.features.signout.soft.SoftLogoutFragment
@ -348,4 +349,9 @@ interface FragmentModule {
@IntoMap
@FragmentKey(CrossSigningSettingsFragment::class)
fun bindCrossSigningSettingsFragment(fragment: CrossSigningSettingsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(AccountDataFragment::class)
fun bindAccountDataFragment(fragment: AccountDataFragment): Fragment
}

View File

@ -0,0 +1,79 @@
/*
* 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.settings.devtools
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.ui.list.genericFooterItem
import im.vector.riotx.core.ui.list.genericItemWithValue
import im.vector.riotx.core.utils.DebouncedClickListener
import javax.inject.Inject
class AccountDataEpoxyController @Inject constructor(
private val stringProvider: StringProvider
) : TypedEpoxyController<AccountDataViewState>() {
interface InteractionListener {
fun didTap(data: UserAccountData)
}
var interactionListener: InteractionListener? = null
override fun buildModels(data: AccountDataViewState?) {
if (data == null) return
when (data.accountData) {
is Loading -> {
loadingItem {
id("loading")
loadingText(stringProvider.getString(R.string.loading))
}
}
is Fail -> {
genericFooterItem {
id("fail")
text(data.accountData.error.localizedMessage)
}
}
is Success -> {
val dataList = data.accountData.invoke()
if (dataList.isEmpty()) {
genericFooterItem {
id("noResults")
text(stringProvider.getString(R.string.no_result_placeholder))
}
} else {
dataList.forEach { accountData ->
genericItemWithValue {
id(accountData.type)
title(accountData.type)
itemClickAction(DebouncedClickListener(View.OnClickListener {
interactionListener?.didTap(accountData)
}))
}
}
}
}
}
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.settings.devtools
import android.os.Bundle
import android.view.View
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
import im.vector.riotx.R
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import javax.inject.Inject
class AccountDataFragment @Inject constructor(
val viewModelFactory: AccountDataViewModel.Factory,
private val epoxyController: AccountDataEpoxyController
) : VectorBaseFragment(), AccountDataEpoxyController.InteractionListener {
override fun getLayoutResId() = R.layout.fragment_generic_recycler
private val viewModel: AccountDataViewModel by fragmentViewModel(AccountDataViewModel::class)
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_account_data)
}
override fun invalidate() = withState(viewModel) { state ->
epoxyController.setData(state)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(epoxyController, showDivider = true)
epoxyController.interactionListener = this
}
override fun didTap(data: UserAccountData) {
val fb = data as? UserAccountDataFallback ?: return
val jsonString = MoshiProvider.providesMoshi()
.adapter(UserAccountDataFallback::class.java)
.toJson(fb)
JsonViewerBottomSheetDialog.newInstance(jsonString)
.show(childFragmentManager, "JSON_VIEWER")
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.settings.devtools
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.rx.rx
import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel
data class AccountDataViewState(
val accountData: Async<List<UserAccountData>> = Uninitialized
) : MvRxState
class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState,
private val session: Session)
: VectorViewModel<AccountDataViewState, EmptyAction, EmptyViewEvents>(initialState) {
init {
session.rx().liveAccountData(emptyList())
.execute {
copy(accountData = it)
}
}
override fun handle(action: EmptyAction) {}
@AssistedInject.Factory
interface Factory {
fun create(initialState: AccountDataViewState): AccountDataViewModel
}
companion object : MvRxViewModelFactory<AccountDataViewModel, AccountDataViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: AccountDataViewState): AccountDataViewModel? {
val fragment: AccountDataFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.viewModelFactory.create(state)
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.settings.devtools
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
import butterknife.BindView
import com.airbnb.mvrx.MvRx
import com.yuyh.jsonviewer.library.JsonRecyclerView
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.riotx.features.themes.ThemeUtils
class JsonViewerBottomSheetDialog : VectorBaseBottomSheetDialogFragment() {
override fun getLayoutResId() = R.layout.fragment_jsonviewer
@BindView(R.id.rv_json)
lateinit var jsonRecyclerView: JsonRecyclerView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
jsonRecyclerView.setKeyColor(ThemeUtils.getColor(requireContext(), R.attr.colorAccent))
jsonRecyclerView.setValueTextColor(ContextCompat.getColor(requireContext(), R.color.riotx_notice_secondary))
jsonRecyclerView.setValueNumberColor(ContextCompat.getColor(requireContext(), R.color.riotx_notice_secondary))
jsonRecyclerView.setValueUrlColor(ThemeUtils.getColor(requireContext(), android.R.attr.textColorLink))
jsonRecyclerView.setValueNullColor(ContextCompat.getColor(requireContext(), R.color.riotx_notice_secondary))
jsonRecyclerView.setBracesColor(ThemeUtils.getColor(requireContext(), R.attr.riotx_text_primary))
val jsonString = arguments?.getString(MvRx.KEY_ARG)
jsonRecyclerView.bindJson(jsonString)
}
companion object {
fun newInstance(jsonString: String): JsonViewerBottomSheetDialog {
return JsonViewerBottomSheetDialog().apply {
setArguments(Bundle().apply { putString(MvRx.KEY_ARG, jsonString) })
}
}
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:fillViewport="true"
android:orientation="vertical">
<com.yuyh.jsonviewer.library.JsonRecyclerView
android:id="@+id/rv_json"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="ScrollViewSize" />
</HorizontalScrollView>

View File

@ -6,6 +6,8 @@
<!-- Sections has been created to avoid merge conflict. Let's see if it's better -->
<!-- BEGIN Strings added by Valere -->
<string name="settings_dev_tools">Dev Tools</string>
<string name="settings_account_data">Account Data</string>
<plurals name="poll_info">
<item quantity="zero">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -63,6 +63,14 @@
android:persistent="false"
android:title="@string/settings_push_rules"
app:fragment="im.vector.riotx.features.settings.push.PushRulesFragment" />
</im.vector.riotx.core.preference.VectorPreferenceCategory>
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_dev_tools">
<im.vector.riotx.core.preference.VectorPreference
android:persistent="false"
android:title="@string/settings_account_data"
app:fragment="im.vector.riotx.features.settings.devtools.AccountDataFragment" />
</im.vector.riotx.core.preference.VectorPreferenceCategory>