mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 03:16:02 +03:00
Merge branch 'develop' into feature/stabilization
This commit is contained in:
commit
19fb3ce032
53 changed files with 1828 additions and 931 deletions
|
@ -8,13 +8,19 @@ Improvements 🙌:
|
||||||
- The initial sync is now handled by a foreground service
|
- The initial sync is now handled by a foreground service
|
||||||
- Render aliases and canonical alias change in the timeline
|
- Render aliases and canonical alias change in the timeline
|
||||||
- Fix autocompletion issues and add support for rooms and groups
|
- Fix autocompletion issues and add support for rooms and groups
|
||||||
|
- Introduce developer mode in the settings (#796)
|
||||||
|
- Improve devices list screen
|
||||||
|
- Add settings for rageshake sensibility
|
||||||
|
- Fix autocompletion issues and add support for rooms, groups, and emoji (#780)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
-
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
|
- Fix crash when opening room creation screen from the room filtering screen
|
||||||
- Fix avatar image disappearing (#777)
|
- Fix avatar image disappearing (#777)
|
||||||
- Fix read marker banner when permalink
|
- Fix read marker banner when permalink
|
||||||
|
- Hide non working settings (#751)
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
|
|
@ -28,6 +28,12 @@ fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||||
?.chunked(4)
|
?.chunked(4)
|
||||||
?.joinToString(separator = " ")
|
?.joinToString(separator = " ")
|
||||||
|
|
||||||
fun MutableList<DeviceInfo>.sortByLastSeen() {
|
/* ==========================================================================================
|
||||||
sortWith(DatedObjectComparators.descComparator)
|
* DeviceInfo
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> {
|
||||||
|
val list = toMutableList()
|
||||||
|
list.sortWith(DatedObjectComparators.descComparator)
|
||||||
|
return list
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
|
||||||
|
@ -89,6 +90,8 @@ interface CryptoService {
|
||||||
|
|
||||||
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||||
|
|
||||||
|
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||||
|
|
||||||
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||||
|
|
||||||
fun isRoomEncrypted(roomId: String): Boolean
|
fun isRoomEncrypted(roomId: String): Boolean
|
||||||
|
|
|
@ -123,6 +123,9 @@ internal abstract class CryptoModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
|
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
@ -127,6 +128,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
||||||
// Tasks
|
// Tasks
|
||||||
private val getDevicesTask: GetDevicesTask,
|
private val getDevicesTask: GetDevicesTask,
|
||||||
|
private val getDeviceInfoTask: GetDeviceInfoTask,
|
||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
|
@ -199,6 +201,14 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
||||||
|
getDeviceInfoTask
|
||||||
|
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||||
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,18 @@ internal interface CryptoApi {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the devices list
|
* Get the devices list
|
||||||
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-devices
|
* Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices")
|
||||||
fun getDevices(): Call<DevicesListResponse>
|
fun getDevices(): Call<DevicesListResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device info by id
|
||||||
|
* Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices-deviceid
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}")
|
||||||
|
fun getDeviceInfo(@Path("deviceId") deviceId: String): Call<DeviceInfo>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload device and/or one-time keys.
|
* Upload device and/or one-time keys.
|
||||||
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-upload
|
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-upload
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.tasks
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface GetDeviceInfoTask : Task<GetDeviceInfoTask.Params, DeviceInfo> {
|
||||||
|
data class Params(val deviceId: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultGetDeviceInfoTask @Inject constructor(private val cryptoApi: CryptoApi)
|
||||||
|
: GetDeviceInfoTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo {
|
||||||
|
return executeRequest {
|
||||||
|
apiCall = cryptoApi.getDeviceInfo(params.deviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".VectorApplication"
|
android:name=".VectorApplication"
|
||||||
|
|
|
@ -45,6 +45,7 @@ import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
||||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
||||||
import im.vector.riotx.features.settings.*
|
import im.vector.riotx.features.settings.*
|
||||||
|
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
||||||
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
||||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||||
import im.vector.riotx.features.signout.soft.SoftLogoutFragment
|
import im.vector.riotx.features.signout.soft.SoftLogoutFragment
|
||||||
|
@ -228,6 +229,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(VectorSettingsIgnoredUsersFragment::class)
|
@FragmentKey(VectorSettingsIgnoredUsersFragment::class)
|
||||||
fun bindVectorSettingsIgnoredUsersFragment(fragment: VectorSettingsIgnoredUsersFragment): Fragment
|
fun bindVectorSettingsIgnoredUsersFragment(fragment: VectorSettingsIgnoredUsersFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(VectorSettingsDevicesFragment::class)
|
||||||
|
fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(SASVerificationIncomingFragment::class)
|
@FragmentKey(SASVerificationIncomingFragment::class)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.core.hardware
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.Vibrator
|
||||||
|
|
||||||
|
fun vibrate(context: Context, durationMillis: Long = 100) {
|
||||||
|
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
vibrator.vibrate(VibrationEffect.createOneShot(durationMillis, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
vibrator.vibrate(durationMillis)
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,7 @@ import im.vector.riotx.features.rageshake.BugReportActivity
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
import im.vector.riotx.features.rageshake.RageShake
|
import im.vector.riotx.features.rageshake.RageShake
|
||||||
import im.vector.riotx.features.session.SessionListener
|
import im.vector.riotx.features.session.SessionListener
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import im.vector.riotx.features.themes.ActivityOtherThemes
|
import im.vector.riotx.features.themes.ActivityOtherThemes
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
import im.vector.riotx.receivers.DebugReceiver
|
import im.vector.riotx.receivers.DebugReceiver
|
||||||
|
@ -88,9 +89,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
private lateinit var configurationViewModel: ConfigurationViewModel
|
private lateinit var configurationViewModel: ConfigurationViewModel
|
||||||
private lateinit var sessionListener: SessionListener
|
private lateinit var sessionListener: SessionListener
|
||||||
protected lateinit var bugReporter: BugReporter
|
protected lateinit var bugReporter: BugReporter
|
||||||
private lateinit var rageShake: RageShake
|
lateinit var rageShake: RageShake
|
||||||
|
private set
|
||||||
protected lateinit var navigator: Navigator
|
protected lateinit var navigator: Navigator
|
||||||
private lateinit var activeSessionHolder: ActiveSessionHolder
|
private lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
|
private lateinit var vectorPreferences: VectorPreferences
|
||||||
|
|
||||||
// Filter for multiple invalid token error
|
// Filter for multiple invalid token error
|
||||||
private var mainActivityStarted = false
|
private var mainActivityStarted = false
|
||||||
|
@ -135,7 +138,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
screenComponent = DaggerScreenComponent.factory().create(getVectorComponent(), this)
|
val vectorComponent = getVectorComponent()
|
||||||
|
screenComponent = DaggerScreenComponent.factory().create(vectorComponent, this)
|
||||||
val timeForInjection = measureTimeMillis {
|
val timeForInjection = measureTimeMillis {
|
||||||
injectWith(screenComponent)
|
injectWith(screenComponent)
|
||||||
}
|
}
|
||||||
|
@ -150,6 +154,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
rageShake = screenComponent.rageShake()
|
rageShake = screenComponent.rageShake()
|
||||||
navigator = screenComponent.navigator()
|
navigator = screenComponent.navigator()
|
||||||
activeSessionHolder = screenComponent.activeSessionHolder()
|
activeSessionHolder = screenComponent.activeSessionHolder()
|
||||||
|
vectorPreferences = vectorComponent.vectorPreferences()
|
||||||
configurationViewModel.activityRestarter.observe(this, Observer {
|
configurationViewModel.activityRestarter.observe(this, Observer {
|
||||||
if (!it.hasBeenHandled) {
|
if (!it.hasBeenHandled) {
|
||||||
// Recreate the Activity because configuration has changed
|
// Recreate the Activity because configuration has changed
|
||||||
|
@ -226,7 +231,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
|
|
||||||
configurationViewModel.onActivityResumed()
|
configurationViewModel.onActivityResumed()
|
||||||
|
|
||||||
if (this !is BugReportActivity) {
|
if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
|
||||||
rageShake.start()
|
rageShake.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2018 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.core.preference
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import im.vector.riotx.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Divider for Preference screen
|
|
||||||
*/
|
|
||||||
class VectorPreferenceDivider @JvmOverloads constructor(context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0,
|
|
||||||
defStyleRes: Int = 0
|
|
||||||
) : Preference(context, attrs, defStyleAttr, defStyleRes) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
layoutResource = R.layout.vector_preference_divider
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.autocomplete.emoji
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.riotx.EmojiCompatFontProvider
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
import im.vector.riotx.features.reactions.ReactionClickListener
|
||||||
|
import im.vector.riotx.features.reactions.data.EmojiItem
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AutocompleteEmojiController @Inject constructor(
|
||||||
|
private val fontProvider: EmojiCompatFontProvider
|
||||||
|
) : TypedEpoxyController<List<EmojiItem>>() {
|
||||||
|
|
||||||
|
var emojiTypeface: Typeface? = fontProvider.typeface
|
||||||
|
|
||||||
|
private val fontProviderListener = object : EmojiCompatFontProvider.FontProviderListener {
|
||||||
|
override fun compatibilityFontUpdate(typeface: Typeface?) {
|
||||||
|
emojiTypeface = typeface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
fontProvider.addListener(fontProviderListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener: AutocompleteClickListener<String>? = null
|
||||||
|
|
||||||
|
override fun buildModels(data: List<EmojiItem>?) {
|
||||||
|
if (data.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data
|
||||||
|
.take(MAX)
|
||||||
|
.forEach { emojiItem ->
|
||||||
|
autocompleteEmojiItem {
|
||||||
|
id(emojiItem.name)
|
||||||
|
emojiItem(emojiItem)
|
||||||
|
emojiTypeFace(emojiTypeface)
|
||||||
|
onClickListener(
|
||||||
|
object : ReactionClickListener {
|
||||||
|
override fun onReactionSelected(reaction: String) {
|
||||||
|
listener?.onItemClick(reaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size > MAX) {
|
||||||
|
autocompleteMoreResultItem {
|
||||||
|
id("more_result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
|
fontProvider.removeListener(fontProviderListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAX = 50
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.autocomplete.emoji
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.features.reactions.ReactionClickListener
|
||||||
|
import im.vector.riotx.features.reactions.data.EmojiItem
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_autocomplete_emoji)
|
||||||
|
abstract class AutocompleteEmojiItem : VectorEpoxyModel<AutocompleteEmojiItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var emojiItem: EmojiItem
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var emojiTypeFace: Typeface? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var onClickListener: ReactionClickListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
holder.emojiText.text = emojiItem.emoji
|
||||||
|
holder.emojiText.typeface = emojiTypeFace ?: Typeface.DEFAULT
|
||||||
|
holder.emojiNameText.text = emojiItem.name
|
||||||
|
holder.emojiKeywordText.setTextOrHide(emojiItem.keywords.joinToString())
|
||||||
|
|
||||||
|
holder.view.setOnClickListener {
|
||||||
|
onClickListener?.onReactionSelected(emojiItem.emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val emojiText by bind<TextView>(R.id.itemAutocompleteEmoji)
|
||||||
|
val emojiNameText by bind<TextView>(R.id.itemAutocompleteEmojiName)
|
||||||
|
val emojiKeywordText by bind<TextView>(R.id.itemAutocompleteEmojiSubname)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.autocomplete.emoji
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||||
|
private val emojiDataSource: EmojiDataSource,
|
||||||
|
private val controller: AutocompleteEmojiController) :
|
||||||
|
RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> {
|
||||||
|
|
||||||
|
init {
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
|
||||||
|
// Also remove animation
|
||||||
|
recyclerView?.itemAnimator = null
|
||||||
|
return controller.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(t: String) {
|
||||||
|
dispatchClick(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuery(query: CharSequence?) {
|
||||||
|
val data = if (query.isNullOrBlank()) {
|
||||||
|
// Return common emojis
|
||||||
|
emojiDataSource.getQuickReactions()
|
||||||
|
} else {
|
||||||
|
emojiDataSource.filterWith(query.toString())
|
||||||
|
}
|
||||||
|
controller.setData(data)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.autocomplete.emoji
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_autocomplete_more_result)
|
||||||
|
abstract class AutocompleteMoreResultItem : VectorEpoxyModel<AutocompleteMoreResultItem.Holder>() {
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder()
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.detail
|
||||||
|
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.widget.EditText
|
||||||
|
import com.otaliastudios.autocomplete.Autocomplete
|
||||||
|
import com.otaliastudios.autocomplete.AutocompleteCallback
|
||||||
|
import com.otaliastudios.autocomplete.CharPolicy
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.matrix.android.api.util.toRoomAliasMatrixItem
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
|
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||||
|
import im.vector.riotx.features.autocomplete.emoji.AutocompleteEmojiPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.member.AutocompleteMemberPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
||||||
|
import im.vector.riotx.features.command.Command
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
|
||||||
|
import im.vector.riotx.features.html.PillImageSpan
|
||||||
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AutoCompleter @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val commandAutocompletePolicy: CommandAutocompletePolicy,
|
||||||
|
private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
|
||||||
|
private val autocompleteMemberPresenter: AutocompleteMemberPresenter,
|
||||||
|
private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
|
||||||
|
private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
|
||||||
|
private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter
|
||||||
|
) {
|
||||||
|
private lateinit var editText: EditText
|
||||||
|
|
||||||
|
fun enterSpecialMode() {
|
||||||
|
commandAutocompletePolicy.enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exitSpecialMode() {
|
||||||
|
commandAutocompletePolicy.enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private val glideRequests by lazy {
|
||||||
|
GlideApp.with(editText)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setup(editText: EditText, listener: AutoCompleterListener) {
|
||||||
|
this.editText = editText
|
||||||
|
|
||||||
|
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, R.attr.riotx_background))
|
||||||
|
|
||||||
|
setupCommands(backgroundDrawable, editText)
|
||||||
|
setupUsers(backgroundDrawable, editText, listener)
|
||||||
|
setupRooms(backgroundDrawable, editText, listener)
|
||||||
|
setupGroups(backgroundDrawable, editText, listener)
|
||||||
|
setupEmojis(backgroundDrawable, editText)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(state: TextComposerViewState) {
|
||||||
|
autocompleteMemberPresenter.render(state.asyncMembers)
|
||||||
|
autocompleteRoomPresenter.render(state.asyncRooms)
|
||||||
|
autocompleteGroupPresenter.render(state.asyncGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCommands(backgroundDrawable: Drawable, editText: EditText) {
|
||||||
|
Autocomplete.on<Command>(editText)
|
||||||
|
.with(commandAutocompletePolicy)
|
||||||
|
.with(autocompleteCommandPresenter)
|
||||||
|
.with(ELEVATION)
|
||||||
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<Command> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable, item: Command): Boolean {
|
||||||
|
editable.clear()
|
||||||
|
editable
|
||||||
|
.append(item.command)
|
||||||
|
.append(" ")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUsers(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteMemberPresenter.Callback) {
|
||||||
|
autocompleteMemberPresenter.callback = listener
|
||||||
|
Autocomplete.on<RoomMember>(editText)
|
||||||
|
.with(CharPolicy('@', true))
|
||||||
|
.with(autocompleteMemberPresenter)
|
||||||
|
.with(ELEVATION)
|
||||||
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<RoomMember> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable, item: RoomMember): Boolean {
|
||||||
|
insertMatrixItem(editText, editable, "@", item.toMatrixItem())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRooms(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteRoomPresenter.Callback) {
|
||||||
|
autocompleteRoomPresenter.callback = listener
|
||||||
|
Autocomplete.on<RoomSummary>(editText)
|
||||||
|
.with(CharPolicy('#', true))
|
||||||
|
.with(autocompleteRoomPresenter)
|
||||||
|
.with(ELEVATION)
|
||||||
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<RoomSummary> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable, item: RoomSummary): Boolean {
|
||||||
|
insertMatrixItem(editText, editable, "#", item.toRoomAliasMatrixItem())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupGroups(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteGroupPresenter.Callback) {
|
||||||
|
autocompleteGroupPresenter.callback = listener
|
||||||
|
Autocomplete.on<GroupSummary>(editText)
|
||||||
|
.with(CharPolicy('+', true))
|
||||||
|
.with(autocompleteGroupPresenter)
|
||||||
|
.with(ELEVATION)
|
||||||
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<GroupSummary> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable, item: GroupSummary): Boolean {
|
||||||
|
insertMatrixItem(editText, editable, "+", item.toMatrixItem())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupEmojis(backgroundDrawable: Drawable, editText: EditText) {
|
||||||
|
Autocomplete.on<String>(editText)
|
||||||
|
.with(CharPolicy(':', false))
|
||||||
|
.with(autocompleteEmojiPresenter)
|
||||||
|
.with(ELEVATION)
|
||||||
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<String> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable, item: String): Boolean {
|
||||||
|
// Detect last ":" and remove it
|
||||||
|
var startIndex = editable.lastIndexOf(":")
|
||||||
|
if (startIndex == -1) {
|
||||||
|
startIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect next word separator
|
||||||
|
var endIndex = editable.indexOf(" ", startIndex)
|
||||||
|
if (endIndex == -1) {
|
||||||
|
endIndex = editable.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the word by its completion
|
||||||
|
editable.replace(startIndex, endIndex, item)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insertMatrixItem(editText: EditText, editable: Editable, firstChar: String, matrixItem: MatrixItem) {
|
||||||
|
// Detect last firstChar and remove it
|
||||||
|
var startIndex = editable.lastIndexOf(firstChar)
|
||||||
|
if (startIndex == -1) {
|
||||||
|
startIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect next word separator
|
||||||
|
var endIndex = editable.indexOf(" ", startIndex)
|
||||||
|
if (endIndex == -1) {
|
||||||
|
endIndex = editable.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the word by its completion
|
||||||
|
val displayName = matrixItem.getBestName()
|
||||||
|
|
||||||
|
// with a trailing space
|
||||||
|
editable.replace(startIndex, endIndex, "$displayName ")
|
||||||
|
|
||||||
|
// Add the span
|
||||||
|
val span = PillImageSpan(
|
||||||
|
glideRequests,
|
||||||
|
avatarRenderer,
|
||||||
|
editText.context,
|
||||||
|
matrixItem
|
||||||
|
)
|
||||||
|
span.bind(editText)
|
||||||
|
|
||||||
|
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoCompleterListener :
|
||||||
|
AutocompleteMemberPresenter.Callback,
|
||||||
|
AutocompleteRoomPresenter.Callback,
|
||||||
|
AutocompleteGroupPresenter.Callback
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ELEVATION = 6f
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,12 +20,10 @@ import android.annotation.SuppressLint
|
||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.text.Editable
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -52,17 +50,11 @@ import com.github.piasy.biv.BigImageViewer
|
||||||
import com.github.piasy.biv.loader.ImageLoader
|
import com.github.piasy.biv.loader.ImageLoader
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.otaliastudios.autocomplete.Autocomplete
|
|
||||||
import com.otaliastudios.autocomplete.AutocompleteCallback
|
|
||||||
import com.otaliastudios.autocomplete.CharPolicy
|
|
||||||
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.content.ContentAttachmentData
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||||
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.group.model.GroupSummary
|
|
||||||
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.RoomMember
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
|
@ -70,7 +62,6 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
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
|
||||||
import im.vector.matrix.android.api.util.toRoomAliasMatrixItem
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.dialogs.withColoredButton
|
import im.vector.riotx.core.dialogs.withColoredButton
|
||||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||||
|
@ -84,11 +75,6 @@ import im.vector.riotx.core.utils.*
|
||||||
import im.vector.riotx.features.attachments.AttachmentTypeSelectorView
|
import im.vector.riotx.features.attachments.AttachmentTypeSelectorView
|
||||||
import im.vector.riotx.features.attachments.AttachmentsHelper
|
import im.vector.riotx.features.attachments.AttachmentsHelper
|
||||||
import im.vector.riotx.features.attachments.ContactAttachment
|
import im.vector.riotx.features.attachments.ContactAttachment
|
||||||
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
|
||||||
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
|
||||||
import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
|
|
||||||
import im.vector.riotx.features.autocomplete.member.AutocompleteMemberPresenter
|
|
||||||
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.getColorFromUserId
|
import im.vector.riotx.features.home.getColorFromUserId
|
||||||
|
@ -117,7 +103,6 @@ import im.vector.riotx.features.permalink.PermalinkHandler
|
||||||
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
@ -142,11 +127,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val timelineEventController: TimelineEventController,
|
private val timelineEventController: TimelineEventController,
|
||||||
private val commandAutocompletePolicy: CommandAutocompletePolicy,
|
private val autoCompleter: AutoCompleter,
|
||||||
private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
|
|
||||||
private val autocompleteMemberPresenter: AutocompleteMemberPresenter,
|
|
||||||
private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
|
|
||||||
private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
|
|
||||||
private val permalinkHandler: PermalinkHandler,
|
private val permalinkHandler: PermalinkHandler,
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
||||||
|
@ -156,9 +137,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
) :
|
) :
|
||||||
VectorBaseFragment(),
|
VectorBaseFragment(),
|
||||||
TimelineEventController.Callback,
|
TimelineEventController.Callback,
|
||||||
AutocompleteMemberPresenter.Callback,
|
AutoCompleter.AutoCompleterListener,
|
||||||
AutocompleteRoomPresenter.Callback,
|
|
||||||
AutocompleteGroupPresenter.Callback,
|
|
||||||
VectorInviteView.Callback,
|
VectorInviteView.Callback,
|
||||||
JumpToReadMarkerView.Callback,
|
JumpToReadMarkerView.Callback,
|
||||||
AttachmentTypeSelectorView.Callback,
|
AttachmentTypeSelectorView.Callback,
|
||||||
|
@ -396,7 +375,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderRegularMode(text: String) {
|
private fun renderRegularMode(text: String) {
|
||||||
commandAutocompletePolicy.enabled = true
|
autoCompleter.exitSpecialMode()
|
||||||
composerLayout.collapse()
|
composerLayout.collapse()
|
||||||
|
|
||||||
updateComposerText(text)
|
updateComposerText(text)
|
||||||
|
@ -407,7 +386,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
@DrawableRes iconRes: Int,
|
@DrawableRes iconRes: Int,
|
||||||
@StringRes descriptionRes: Int,
|
@StringRes descriptionRes: Int,
|
||||||
defaultContent: String) {
|
defaultContent: String) {
|
||||||
commandAutocompletePolicy.enabled = false
|
autoCompleter.enterSpecialMode()
|
||||||
// switch to expanded bar
|
// switch to expanded bar
|
||||||
composerLayout.composerRelatedMessageTitle.apply {
|
composerLayout.composerRelatedMessageTitle.apply {
|
||||||
text = event.getDisambiguatedDisplayName()
|
text = event.getDisambiguatedDisplayName()
|
||||||
|
@ -581,165 +560,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupComposer() {
|
private fun setupComposer() {
|
||||||
val elevation = 6f
|
autoCompleter.setup(composerLayout.composerEditText, this)
|
||||||
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(requireContext(), R.attr.riotx_background))
|
|
||||||
Autocomplete.on<Command>(composerLayout.composerEditText)
|
|
||||||
.with(commandAutocompletePolicy)
|
|
||||||
.with(autocompleteCommandPresenter)
|
|
||||||
.with(elevation)
|
|
||||||
.with(backgroundDrawable)
|
|
||||||
.with(object : AutocompleteCallback<Command> {
|
|
||||||
override fun onPopupItemClicked(editable: Editable, item: Command): Boolean {
|
|
||||||
editable.clear()
|
|
||||||
editable
|
|
||||||
.append(item.command)
|
|
||||||
.append(" ")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPopupVisibilityChanged(shown: Boolean) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
autocompleteRoomPresenter.callback = this
|
|
||||||
Autocomplete.on<RoomSummary>(composerLayout.composerEditText)
|
|
||||||
.with(CharPolicy('#', true))
|
|
||||||
.with(autocompleteRoomPresenter)
|
|
||||||
.with(elevation)
|
|
||||||
.with(backgroundDrawable)
|
|
||||||
.with(object : AutocompleteCallback<RoomSummary> {
|
|
||||||
override fun onPopupItemClicked(editable: Editable, item: RoomSummary): Boolean {
|
|
||||||
// Detect last '#' and remove it
|
|
||||||
var startIndex = editable.lastIndexOf("#")
|
|
||||||
if (startIndex == -1) {
|
|
||||||
startIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect next word separator
|
|
||||||
var endIndex = editable.indexOf(" ", startIndex)
|
|
||||||
if (endIndex == -1) {
|
|
||||||
endIndex = editable.length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the word by its completion
|
|
||||||
val matrixItem = item.toRoomAliasMatrixItem()
|
|
||||||
val displayName = matrixItem.getBestName()
|
|
||||||
|
|
||||||
// with a trailing space
|
|
||||||
editable.replace(startIndex, endIndex, "$displayName ")
|
|
||||||
|
|
||||||
// Add the span
|
|
||||||
val span = PillImageSpan(
|
|
||||||
glideRequests,
|
|
||||||
avatarRenderer,
|
|
||||||
requireContext(),
|
|
||||||
matrixItem
|
|
||||||
)
|
|
||||||
span.bind(composerLayout.composerEditText)
|
|
||||||
|
|
||||||
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPopupVisibilityChanged(shown: Boolean) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
autocompleteGroupPresenter.callback = this
|
|
||||||
Autocomplete.on<GroupSummary>(composerLayout.composerEditText)
|
|
||||||
.with(CharPolicy('+', true))
|
|
||||||
.with(autocompleteGroupPresenter)
|
|
||||||
.with(elevation)
|
|
||||||
.with(backgroundDrawable)
|
|
||||||
.with(object : AutocompleteCallback<GroupSummary> {
|
|
||||||
override fun onPopupItemClicked(editable: Editable, item: GroupSummary): Boolean {
|
|
||||||
// Detect last '+' and remove it
|
|
||||||
var startIndex = editable.lastIndexOf("+")
|
|
||||||
if (startIndex == -1) {
|
|
||||||
startIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect next word separator
|
|
||||||
var endIndex = editable.indexOf(" ", startIndex)
|
|
||||||
if (endIndex == -1) {
|
|
||||||
endIndex = editable.length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the word by its completion
|
|
||||||
val matrixItem = item.toMatrixItem()
|
|
||||||
val displayName = matrixItem.getBestName()
|
|
||||||
|
|
||||||
// with a trailing space
|
|
||||||
editable.replace(startIndex, endIndex, "$displayName ")
|
|
||||||
|
|
||||||
// Add the span
|
|
||||||
val span = PillImageSpan(
|
|
||||||
glideRequests,
|
|
||||||
avatarRenderer,
|
|
||||||
requireContext(),
|
|
||||||
matrixItem
|
|
||||||
)
|
|
||||||
span.bind(composerLayout.composerEditText)
|
|
||||||
|
|
||||||
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPopupVisibilityChanged(shown: Boolean) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
autocompleteMemberPresenter.callback = this
|
|
||||||
Autocomplete.on<RoomMember>(composerLayout.composerEditText)
|
|
||||||
.with(CharPolicy('@', true))
|
|
||||||
.with(autocompleteMemberPresenter)
|
|
||||||
.with(elevation)
|
|
||||||
.with(backgroundDrawable)
|
|
||||||
.with(object : AutocompleteCallback<RoomMember> {
|
|
||||||
override fun onPopupItemClicked(editable: Editable, item: RoomMember): Boolean {
|
|
||||||
// Detect last '@' and remove it
|
|
||||||
var startIndex = editable.lastIndexOf("@")
|
|
||||||
if (startIndex == -1) {
|
|
||||||
startIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect next word separator
|
|
||||||
var endIndex = editable.indexOf(" ", startIndex)
|
|
||||||
if (endIndex == -1) {
|
|
||||||
endIndex = editable.length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the word by its completion
|
|
||||||
val matrixItem = item.toMatrixItem()
|
|
||||||
val displayName = matrixItem.getBestName()
|
|
||||||
|
|
||||||
// with a trailing space
|
|
||||||
editable.replace(startIndex, endIndex, "$displayName ")
|
|
||||||
|
|
||||||
// Add the span
|
|
||||||
val span = PillImageSpan(
|
|
||||||
glideRequests,
|
|
||||||
avatarRenderer,
|
|
||||||
requireContext(),
|
|
||||||
matrixItem
|
|
||||||
)
|
|
||||||
span.bind(composerLayout.composerEditText)
|
|
||||||
|
|
||||||
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPopupVisibilityChanged(shown: Boolean) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
composerLayout.callback = object : TextComposerView.Callback {
|
composerLayout.callback = object : TextComposerView.Callback {
|
||||||
override fun onAddAttachment() {
|
override fun onAddAttachment() {
|
||||||
if (!::attachmentTypeSelector.isInitialized) {
|
if (!::attachmentTypeSelector.isInitialized) {
|
||||||
|
@ -836,9 +657,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderTextComposerState(state: TextComposerViewState) {
|
private fun renderTextComposerState(state: TextComposerViewState) {
|
||||||
autocompleteMemberPresenter.render(state.asyncMembers)
|
autoCompleter.render(state)
|
||||||
autocompleteRoomPresenter.render(state.asyncRooms)
|
|
||||||
autocompleteGroupPresenter.render(state.asyncGroups)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderTombstoneEventHandling(async: Async<String>) {
|
private fun renderTombstoneEventHandling(async: Async<String>) {
|
||||||
|
|
|
@ -41,6 +41,8 @@ import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventForm
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
import im.vector.riotx.features.html.VectorHtmlCompressor
|
import im.vector.riotx.features.html.VectorHtmlCompressor
|
||||||
|
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -85,7 +87,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
private val htmlCompressor: VectorHtmlCompressor,
|
private val htmlCompressor: VectorHtmlCompressor,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val noticeEventFormatter: NoticeEventFormatter,
|
private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
private val stringProvider: StringProvider
|
private val stringProvider: StringProvider,
|
||||||
|
private val vectorPreferences: VectorPreferences
|
||||||
) : VectorViewModel<MessageActionState, MessageActionsAction>(initialState) {
|
) : VectorViewModel<MessageActionState, MessageActionsAction>(initialState) {
|
||||||
|
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId
|
||||||
|
@ -98,9 +101,6 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<MessageActionsViewModel, MessageActionState> {
|
companion object : MvRxViewModelFactory<MessageActionsViewModel, MessageActionState> {
|
||||||
|
|
||||||
val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀")
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? {
|
||||||
val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
@ -143,7 +143,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
room.rx()
|
room.rx()
|
||||||
.liveAnnotationSummary(eventId)
|
.liveAnnotationSummary(eventId)
|
||||||
.map { annotations ->
|
.map { annotations ->
|
||||||
quickEmojis.map { emoji ->
|
EmojiDataSource.quickEmojis.map { emoji ->
|
||||||
ToggleState(emoji, annotations.getOrNull()?.reactionsSummary?.firstOrNull { it.key == emoji }?.addedByMe ?: false)
|
ToggleState(emoji, annotations.getOrNull()?.reactionsSummary?.firstOrNull { it.key == emoji }?.addedByMe ?: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,14 +258,15 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vectorPreferences.developerMode()) {
|
||||||
add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent()))
|
add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent()))
|
||||||
if (timelineEvent.isEncrypted()) {
|
if (timelineEvent.isEncrypted()) {
|
||||||
val decryptedContent = timelineEvent.root.toClearContentStringWithIndent()
|
val decryptedContent = timelineEvent.root.toClearContentStringWithIndent()
|
||||||
?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
||||||
add(EventSharedAction.ViewDecryptedSource(decryptedContent))
|
add(EventSharedAction.ViewDecryptedSource(decryptedContent))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
add(EventSharedAction.CopyPermalink(eventId))
|
add(EventSharedAction.CopyPermalink(eventId))
|
||||||
|
|
||||||
if (session.myUserId != timelineEvent.root.senderId) {
|
if (session.myUserId != timelineEvent.root.senderId) {
|
||||||
// not sent by me
|
// not sent by me
|
||||||
if (timelineEvent.root.getClearType() == EventType.MESSAGE) {
|
if (timelineEvent.root.getClearType() == EventType.MESSAGE) {
|
||||||
|
|
|
@ -120,8 +120,8 @@ class DefaultNavigator @Inject constructor(
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openSettings(context: Context) {
|
override fun openSettings(context: Context, directAccess: Int) {
|
||||||
val intent = VectorSettingsActivity.getIntent(context)
|
val intent = VectorSettingsActivity.getIntent(context, directAccess)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.navigation
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
|
|
||||||
interface Navigator {
|
interface Navigator {
|
||||||
|
@ -39,7 +40,7 @@ interface Navigator {
|
||||||
|
|
||||||
fun openRoomsFiltering(context: Context)
|
fun openRoomsFiltering(context: Context)
|
||||||
|
|
||||||
fun openSettings(context: Context)
|
fun openSettings(context: Context, directAccess: Int = VectorSettingsActivity.EXTRA_DIRECT_ACCESS_ROOT)
|
||||||
|
|
||||||
fun openDebug(context: Context)
|
fun openDebug(context: Context)
|
||||||
|
|
||||||
|
|
|
@ -19,33 +19,32 @@ package im.vector.riotx.features.rageshake
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.Sensor
|
import android.hardware.Sensor
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.preference.PreferenceManager
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.edit
|
|
||||||
import com.squareup.seismic.ShakeDetector
|
import com.squareup.seismic.ShakeDetector
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.hardware.vibrate
|
||||||
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RageShake @Inject constructor(private val activity: AppCompatActivity,
|
class RageShake @Inject constructor(private val activity: AppCompatActivity,
|
||||||
private val bugReporter: BugReporter) : ShakeDetector.Listener {
|
private val bugReporter: BugReporter,
|
||||||
|
private val navigator: Navigator,
|
||||||
|
private val vectorPreferences: VectorPreferences) : ShakeDetector.Listener {
|
||||||
|
|
||||||
private var shakeDetector: ShakeDetector? = null
|
private var shakeDetector: ShakeDetector? = null
|
||||||
|
|
||||||
private var dialogDisplayed = false
|
private var dialogDisplayed = false
|
||||||
|
|
||||||
|
var interceptor: (() -> Unit)? = null
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (!isEnable(activity)) {
|
val sensorManager = activity.getSystemService(AppCompatActivity.SENSOR_SERVICE) as? SensorManager ?: return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val sensorManager = activity.getSystemService(AppCompatActivity.SENSOR_SERVICE) as? SensorManager
|
|
||||||
|
|
||||||
if (sensorManager == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
shakeDetector = ShakeDetector(this).apply {
|
shakeDetector = ShakeDetector(this).apply {
|
||||||
|
setSensitivity(vectorPreferences.getRageshakeSensitivity())
|
||||||
start(sensorManager)
|
start(sensorManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,52 +53,43 @@ class RageShake @Inject constructor(private val activity: AppCompatActivity,
|
||||||
shakeDetector?.stop()
|
shakeDetector?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun setSensitivity(sensitivity: Int) {
|
||||||
* Enable the feature, and start it
|
shakeDetector?.setSensitivity(sensitivity)
|
||||||
*/
|
|
||||||
fun enable() {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity).edit {
|
|
||||||
putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable the feature, and stop it
|
|
||||||
*/
|
|
||||||
fun disable() {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity).edit {
|
|
||||||
putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hearShake() {
|
override fun hearShake() {
|
||||||
|
val i = interceptor
|
||||||
|
if (i != null) {
|
||||||
|
vibrate(activity)
|
||||||
|
i.invoke()
|
||||||
|
} else {
|
||||||
if (dialogDisplayed) {
|
if (dialogDisplayed) {
|
||||||
// Filtered!
|
// Filtered!
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vibrate(activity)
|
||||||
dialogDisplayed = true
|
dialogDisplayed = true
|
||||||
|
|
||||||
AlertDialog.Builder(activity)
|
AlertDialog.Builder(activity)
|
||||||
.setMessage(R.string.send_bug_report_alert_message)
|
.setMessage(R.string.send_bug_report_alert_message)
|
||||||
.setPositiveButton(R.string.yes) { _, _ -> openBugReportScreen() }
|
.setPositiveButton(R.string.yes) { _, _ -> openBugReportScreen() }
|
||||||
.setNeutralButton(R.string.disable) { _, _ -> disable() }
|
.setNeutralButton(R.string.settings) { _, _ -> openSettings() }
|
||||||
.setOnDismissListener { dialogDisplayed = false }
|
.setOnDismissListener { dialogDisplayed = false }
|
||||||
.setNegativeButton(R.string.no, null)
|
.setNegativeButton(R.string.no, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun openBugReportScreen() {
|
private fun openBugReportScreen() {
|
||||||
bugReporter.openBugReportScreen(activity)
|
bugReporter.openBugReportScreen(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
private fun openSettings() {
|
||||||
private const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY"
|
navigator.openSettings(activity, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Check if the feature is available
|
* Check if the feature is available
|
||||||
*/
|
*/
|
||||||
|
@ -107,12 +97,5 @@ class RageShake @Inject constructor(private val activity: AppCompatActivity,
|
||||||
return (context.getSystemService(AppCompatActivity.SENSOR_SERVICE) as? SensorManager)
|
return (context.getSystemService(AppCompatActivity.SENSOR_SERVICE) as? SensorManager)
|
||||||
?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
|
?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the feature is enable (enabled by default)
|
|
||||||
*/
|
|
||||||
private fun isEnable(context: Context): Boolean {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,26 +56,10 @@ class EmojiSearchResultViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateQuery(action: EmojiSearchAction.UpdateQuery) {
|
private fun updateQuery(action: EmojiSearchAction.UpdateQuery) {
|
||||||
val words = action.queryString.split("\\s".toRegex())
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
query = action.queryString,
|
query = action.queryString,
|
||||||
// First add emojis with name matching query, sorted by name
|
results = dataSource.filterWith(action.queryString)
|
||||||
// Then emojis with keyword matching any of the word in the query, sorted by name
|
|
||||||
results = dataSource.rawData.emojis
|
|
||||||
.values
|
|
||||||
.filter { emojiItem ->
|
|
||||||
emojiItem.name.contains(action.queryString, true)
|
|
||||||
}
|
|
||||||
.sortedBy { it.name }
|
|
||||||
+ dataSource.rawData.emojis
|
|
||||||
.values
|
|
||||||
.filter { emojiItem ->
|
|
||||||
words.fold(true, { prev, word ->
|
|
||||||
prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.sortedBy { it.name }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,4 +33,49 @@ class EmojiDataSource @Inject constructor(
|
||||||
.fromJson(input.bufferedReader().use { it.readText() })
|
.fromJson(input.bufferedReader().use { it.readText() })
|
||||||
}
|
}
|
||||||
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||||
|
|
||||||
|
private val quickReactions = mutableListOf<EmojiItem>()
|
||||||
|
|
||||||
|
fun filterWith(query: String): List<EmojiItem> {
|
||||||
|
val words = query.split("\\s".toRegex())
|
||||||
|
|
||||||
|
// First add emojis with name matching query, sorted by name
|
||||||
|
return (rawData.emojis.values
|
||||||
|
.filter { emojiItem ->
|
||||||
|
emojiItem.name.contains(query, true)
|
||||||
|
}
|
||||||
|
.sortedBy { it.name } +
|
||||||
|
// Then emojis with keyword matching any of the word in the query, sorted by name
|
||||||
|
rawData.emojis.values
|
||||||
|
.filter { emojiItem ->
|
||||||
|
words.fold(true, { prev, word ->
|
||||||
|
prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.sortedBy { it.name })
|
||||||
|
// and ensure they will not be present twice
|
||||||
|
.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getQuickReactions(): List<EmojiItem> {
|
||||||
|
if (quickReactions.isEmpty()) {
|
||||||
|
listOf(
|
||||||
|
"+1", // 👍
|
||||||
|
"-1", // 👎
|
||||||
|
"grinning", // 😄
|
||||||
|
"tada", // 🎉
|
||||||
|
"confused", // 😕
|
||||||
|
"heart", // ❤️
|
||||||
|
"rocket", // 🚀
|
||||||
|
"eyes" // 👀
|
||||||
|
)
|
||||||
|
.mapNotNullTo(quickReactions) { rawData.emojis[it] }
|
||||||
|
}
|
||||||
|
|
||||||
|
return quickReactions
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.os.Bundle
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import com.airbnb.mvrx.viewModel
|
import com.airbnb.mvrx.viewModel
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.extensions.addFragment
|
import im.vector.riotx.core.extensions.addFragment
|
||||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
@ -45,6 +46,10 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
configureToolbar(toolbar)
|
configureToolbar(toolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun initUiAndData() {
|
override fun initUiAndData() {
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
addFragment(R.id.simpleFragmentContainer, CreateRoomFragment::class.java)
|
addFragment(R.id.simpleFragmentContainer, CreateRoomFragment::class.java)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.squareup.seismic.ShakeDetector
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.features.homeserver.ServerUrlsRepository
|
import im.vector.riotx.features.homeserver.ServerUrlsRepository
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
@ -62,8 +63,6 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY"
|
const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY"
|
||||||
const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
|
const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
|
||||||
const val SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY"
|
const val SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY"
|
||||||
const val SETTINGS_DEVICES_LIST_PREFERENCE_KEY = "SETTINGS_DEVICES_LIST_PREFERENCE_KEY"
|
|
||||||
const val SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY = "SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY"
|
|
||||||
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY"
|
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY"
|
||||||
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY"
|
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY"
|
||||||
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"
|
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"
|
||||||
|
@ -149,12 +148,16 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
|
|
||||||
const val SETTINGS_LABS_ALLOW_EXTENDED_LOGS = "SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
const val SETTINGS_LABS_ALLOW_EXTENDED_LOGS = "SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
||||||
|
|
||||||
|
private const val SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
|
||||||
private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
|
private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
|
||||||
private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
||||||
|
|
||||||
// analytics
|
// analytics
|
||||||
const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY"
|
const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY"
|
||||||
|
|
||||||
|
// Rageshake
|
||||||
const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY"
|
const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY"
|
||||||
|
const val SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY = "SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY"
|
||||||
|
|
||||||
// other
|
// other
|
||||||
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
|
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
|
||||||
|
@ -247,8 +250,12 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun developerMode(): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY, false)
|
||||||
|
}
|
||||||
|
|
||||||
fun shouldShowHiddenEvents(): Boolean {
|
fun shouldShowHiddenEvents(): Boolean {
|
||||||
return defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false)
|
return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun swipeToReplyIsEnabled(): Boolean {
|
fun swipeToReplyIsEnabled(): Boolean {
|
||||||
|
@ -256,7 +263,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun labAllowedExtendedLogging(): Boolean {
|
fun labAllowedExtendedLogging(): Boolean {
|
||||||
return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false)
|
return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -730,14 +737,10 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the rage shake status.
|
* Get the rage shake sensitivity.
|
||||||
*
|
|
||||||
* @param isEnabled true to enable the rage shake
|
|
||||||
*/
|
*/
|
||||||
fun setUseRageshake(isEnabled: Boolean) {
|
fun getRageshakeSensitivity(): Int {
|
||||||
defaultPrefs.edit {
|
return defaultPrefs.getInt(SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY, ShakeDetector.SENSITIVITY_MEDIUM)
|
||||||
putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, isEnabled)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -54,8 +54,13 @@ class VectorSettingsActivity : VectorBaseActivity(),
|
||||||
|
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
// display the fragment
|
// display the fragment
|
||||||
|
when (intent.getIntExtra(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOT)) {
|
||||||
|
EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS ->
|
||||||
|
replaceFragment(R.id.vector_settings_page, VectorSettingsAdvancedSettingsFragment::class.java, null, FRAGMENT_TAG)
|
||||||
|
else ->
|
||||||
replaceFragment(R.id.vector_settings_page, VectorSettingsRootFragment::class.java, null, FRAGMENT_TAG)
|
replaceFragment(R.id.vector_settings_page, VectorSettingsRootFragment::class.java, null, FRAGMENT_TAG)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
supportFragmentManager.addOnBackStackChangedListener(this)
|
supportFragmentManager.addOnBackStackChangedListener(this)
|
||||||
}
|
}
|
||||||
|
@ -111,7 +116,13 @@ class VectorSettingsActivity : VectorBaseActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getIntent(context: Context) = Intent(context, VectorSettingsActivity::class.java)
|
fun getIntent(context: Context, directAccess: Int) = Intent(context, VectorSettingsActivity::class.java)
|
||||||
|
.apply { putExtra(EXTRA_DIRECT_ACCESS, directAccess) }
|
||||||
|
|
||||||
|
private const val EXTRA_DIRECT_ACCESS = "EXTRA_DIRECT_ACCESS"
|
||||||
|
|
||||||
|
const val EXTRA_DIRECT_ACCESS_ROOT = 0
|
||||||
|
const val EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS = 1
|
||||||
|
|
||||||
private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment"
|
private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.settings
|
||||||
|
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.SeekBarPreference
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
|
import im.vector.riotx.features.rageshake.RageShake
|
||||||
|
|
||||||
|
class VectorSettingsAdvancedSettingsFragment : VectorSettingsBaseFragment() {
|
||||||
|
|
||||||
|
override var titleRes = R.string.settings_advanced_settings
|
||||||
|
override val preferenceXmlRes = R.xml.vector_settings_advanced_settings
|
||||||
|
|
||||||
|
private var rageshake: RageShake? = null
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
rageshake = (activity as? VectorBaseActivity)?.rageShake
|
||||||
|
rageshake?.interceptor = {
|
||||||
|
(activity as? VectorBaseActivity)?.showSnackbar(getString(R.string.rageshake_detected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
rageshake?.interceptor = null
|
||||||
|
rageshake = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindPref() {
|
||||||
|
val isRageShakeAvailable = RageShake.isAvailable(requireContext())
|
||||||
|
|
||||||
|
if (isRageShakeAvailable) {
|
||||||
|
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_USE_RAGE_SHAKE_KEY)!!
|
||||||
|
.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
|
||||||
|
if (newValue as? Boolean == true) {
|
||||||
|
rageshake?.start()
|
||||||
|
} else {
|
||||||
|
rageshake?.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
findPreference<SeekBarPreference>(VectorPreferences.SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY)!!
|
||||||
|
.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
(activity as? VectorBaseActivity)?.let {
|
||||||
|
val newValueAsInt = newValue as? Int ?: return@OnPreferenceChangeListener true
|
||||||
|
|
||||||
|
rageshake?.setSensitivity(newValueAsInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
findPreference<VectorSwitchPreference>("SETTINGS_RAGE_SHAKE_CATEGORY_KEY")!!.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,12 +18,8 @@ package im.vector.riotx.features.settings
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -33,30 +29,19 @@ import androidx.preference.SwitchPreference
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
|
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
|
||||||
import im.vector.matrix.android.api.extensions.sortByLastSeen
|
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.dialogs.ExportKeysDialog
|
import im.vector.riotx.core.dialogs.ExportKeysDialog
|
||||||
import im.vector.riotx.core.intent.ExternalIntentData
|
import im.vector.riotx.core.intent.ExternalIntentData
|
||||||
import im.vector.riotx.core.intent.analyseIntent
|
import im.vector.riotx.core.intent.analyseIntent
|
||||||
import im.vector.riotx.core.intent.getFilenameFromUri
|
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.ProgressBarPreference
|
|
||||||
import im.vector.riotx.core.preference.VectorPreference
|
import im.vector.riotx.core.preference.VectorPreference
|
||||||
import im.vector.riotx.core.preference.VectorPreferenceDivider
|
|
||||||
import im.vector.riotx.core.utils.*
|
import im.vector.riotx.core.utils.*
|
||||||
import im.vector.riotx.features.crypto.keys.KeysExporter
|
import im.vector.riotx.features.crypto.keys.KeysExporter
|
||||||
import im.vector.riotx.features.crypto.keys.KeysImporter
|
import im.vector.riotx.features.crypto.keys.KeysImporter
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
import timber.log.Timber
|
|
||||||
import java.text.DateFormat
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
|
@ -66,9 +51,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
override var titleRes = R.string.settings_security_and_privacy
|
override var titleRes = R.string.settings_security_and_privacy
|
||||||
override val preferenceXmlRes = R.xml.vector_settings_security_privacy
|
override val preferenceXmlRes = R.xml.vector_settings_security_privacy
|
||||||
|
|
||||||
// used to avoid requesting to enter the password for each deletion
|
|
||||||
private var mAccountPassword: String = ""
|
|
||||||
|
|
||||||
// devices: device IDs and device names
|
// devices: device IDs and device names
|
||||||
private val mDevicesNameList: MutableList<DeviceInfo> = mutableListOf()
|
private val mDevicesNameList: MutableList<DeviceInfo> = mutableListOf()
|
||||||
|
|
||||||
|
@ -78,29 +60,14 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
private val mCryptographyCategory by lazy {
|
private val mCryptographyCategory by lazy {
|
||||||
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY)!!
|
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY)!!
|
||||||
}
|
}
|
||||||
private val mCryptographyCategoryDivider by lazy {
|
|
||||||
findPreference<VectorPreferenceDivider>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY)!!
|
|
||||||
}
|
|
||||||
// cryptography manage
|
// cryptography manage
|
||||||
private val mCryptographyManageCategory by lazy {
|
private val mCryptographyManageCategory by lazy {
|
||||||
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY)!!
|
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY)!!
|
||||||
}
|
}
|
||||||
private val mCryptographyManageCategoryDivider by lazy {
|
|
||||||
findPreference<VectorPreferenceDivider>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY)!!
|
|
||||||
}
|
|
||||||
// displayed pushers
|
// displayed pushers
|
||||||
private val mPushersSettingsDivider by lazy {
|
|
||||||
findPreference<VectorPreferenceDivider>(VectorPreferences.SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY)!!
|
|
||||||
}
|
|
||||||
private val mPushersSettingsCategory by lazy {
|
private val mPushersSettingsCategory by lazy {
|
||||||
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY)!!
|
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY)!!
|
||||||
}
|
}
|
||||||
private val mDevicesListSettingsCategory by lazy {
|
|
||||||
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_DEVICES_LIST_PREFERENCE_KEY)!!
|
|
||||||
}
|
|
||||||
private val mDevicesListSettingsCategoryDivider by lazy {
|
|
||||||
findPreference<VectorPreferenceDivider>(VectorPreferences.SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY)!!
|
|
||||||
}
|
|
||||||
private val cryptoInfoDeviceNamePreference by lazy {
|
private val cryptoInfoDeviceNamePreference by lazy {
|
||||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY)!!
|
findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY)!!
|
||||||
}
|
}
|
||||||
|
@ -129,13 +96,16 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!!
|
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
// My device name may have been updated
|
||||||
|
refreshMyDevice()
|
||||||
|
}
|
||||||
|
|
||||||
override fun bindPref() {
|
override fun bindPref() {
|
||||||
// Push target
|
// Push target
|
||||||
refreshPushersList()
|
refreshPushersList()
|
||||||
|
|
||||||
// Device list
|
|
||||||
refreshDevicesList()
|
|
||||||
|
|
||||||
// Refresh Key Management section
|
// Refresh Key Management section
|
||||||
refreshKeysManagementSection()
|
refreshKeysManagementSection()
|
||||||
|
|
||||||
|
@ -151,16 +121,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rageshake Management
|
|
||||||
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_USE_RAGE_SHAKE_KEY)!!.let {
|
|
||||||
it.isChecked = vectorPreferences.useRageshake()
|
|
||||||
|
|
||||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
|
||||||
vectorPreferences.setUseRageshake(newValue as Boolean)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
@ -353,11 +313,9 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
private fun removeCryptographyPreference() {
|
private fun removeCryptographyPreference() {
|
||||||
preferenceScreen.let {
|
preferenceScreen.let {
|
||||||
it.removePreference(mCryptographyCategory)
|
it.removePreference(mCryptographyCategory)
|
||||||
it.removePreference(mCryptographyCategoryDivider)
|
|
||||||
|
|
||||||
// Also remove keys management section
|
// Also remove keys management section
|
||||||
it.removePreference(mCryptographyManageCategory)
|
it.removePreference(mCryptographyManageCategory)
|
||||||
it.removePreference(mCryptographyManageCategoryDivider)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,7 +333,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
cryptoInfoDeviceNamePreference.summary = aMyDeviceInfo.displayName
|
cryptoInfoDeviceNamePreference.summary = aMyDeviceInfo.displayName
|
||||||
|
|
||||||
cryptoInfoDeviceNamePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
cryptoInfoDeviceNamePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
displayDeviceRenameDialog(aMyDeviceInfo)
|
// TODO device can be rename only from the device list screen for the moment
|
||||||
|
// displayDeviceRenameDialog(aMyDeviceInfo)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,340 +387,20 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
// devices list
|
// devices list
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
|
|
||||||
private fun removeDevicesPreference() {
|
private fun refreshMyDevice() {
|
||||||
preferenceScreen.let {
|
// TODO Move to a ViewModel...
|
||||||
it.removePreference(mDevicesListSettingsCategory)
|
session.sessionParams.credentials.deviceId?.let {
|
||||||
it.removePreference(mDevicesListSettingsCategoryDivider)
|
session.getDeviceInfo(it, object : MatrixCallback<DeviceInfo> {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force the refresh of the devices list.<br></br>
|
|
||||||
* The devices list is the list of the devices where the user as looged in.
|
|
||||||
* It can be any mobile device, as any browser.
|
|
||||||
*/
|
|
||||||
private fun refreshDevicesList() {
|
|
||||||
if (session.isCryptoEnabled() && !session.sessionParams.credentials.deviceId.isNullOrEmpty()) {
|
|
||||||
// display a spinner while loading the devices list
|
|
||||||
if (0 == mDevicesListSettingsCategory.preferenceCount) {
|
|
||||||
activity?.let {
|
|
||||||
val preference = ProgressBarPreference(it)
|
|
||||||
mDevicesListSettingsCategory.addPreference(preference)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
|
||||||
override fun onSuccess(data: DevicesListResponse) {
|
|
||||||
if (!isAdded) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.devices?.isEmpty() == true) {
|
|
||||||
removeDevicesPreference()
|
|
||||||
} else {
|
|
||||||
buildDevicesSettings(data.devices!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
if (!isAdded) {
|
// Ignore for this time?...
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeDevicesPreference()
|
override fun onSuccess(data: DeviceInfo) {
|
||||||
onCommonDone(failure.message)
|
mMyDeviceInfo = data
|
||||||
}
|
refreshCryptographyPreference(data)
|
||||||
})
|
|
||||||
} else {
|
|
||||||
removeDevicesPreference()
|
|
||||||
removeCryptographyPreference()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the devices portion of the settings.<br></br>
|
|
||||||
* Each row correspond to a device ID and its corresponding device name. Clicking on the row
|
|
||||||
* display a dialog containing: the device ID, the device name and the "last seen" information.
|
|
||||||
*
|
|
||||||
* @param aDeviceInfoList the list of the devices
|
|
||||||
*/
|
|
||||||
private fun buildDevicesSettings(aDeviceInfoList: List<DeviceInfo>) {
|
|
||||||
var preference: VectorPreference
|
|
||||||
var typeFaceHighlight: Int
|
|
||||||
var isNewList = true
|
|
||||||
val myDeviceId = session.sessionParams.credentials.deviceId
|
|
||||||
|
|
||||||
if (aDeviceInfoList.size == mDevicesNameList.size) {
|
|
||||||
isNewList = !mDevicesNameList.containsAll(aDeviceInfoList)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNewList) {
|
|
||||||
var prefIndex = 0
|
|
||||||
mDevicesNameList.clear()
|
|
||||||
mDevicesNameList.addAll(aDeviceInfoList)
|
|
||||||
|
|
||||||
// sort before display: most recent first
|
|
||||||
mDevicesNameList.sortByLastSeen()
|
|
||||||
|
|
||||||
// start from scratch: remove the displayed ones
|
|
||||||
mDevicesListSettingsCategory.removeAll()
|
|
||||||
|
|
||||||
for (deviceInfo in mDevicesNameList) {
|
|
||||||
// set bold to distinguish current device ID
|
|
||||||
if (null != myDeviceId && myDeviceId == deviceInfo.deviceId) {
|
|
||||||
mMyDeviceInfo = deviceInfo
|
|
||||||
typeFaceHighlight = Typeface.BOLD
|
|
||||||
} else {
|
|
||||||
typeFaceHighlight = Typeface.NORMAL
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the edit text preference
|
|
||||||
preference = VectorPreference(requireActivity()).apply {
|
|
||||||
mTypeface = typeFaceHighlight
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null == deviceInfo.deviceId && null == deviceInfo.displayName) {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
if (null != deviceInfo.deviceId) {
|
|
||||||
preference.title = deviceInfo.deviceId
|
|
||||||
}
|
|
||||||
|
|
||||||
// display name parameter can be null (new JSON API)
|
|
||||||
if (null != deviceInfo.displayName) {
|
|
||||||
preference.summary = deviceInfo.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex
|
|
||||||
prefIndex++
|
|
||||||
|
|
||||||
// onClick handler: display device details dialog
|
|
||||||
preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
displayDeviceDetailsDialog(deviceInfo)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
mDevicesListSettingsCategory.addPreference(preference)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshCryptographyPreference(mMyDeviceInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a dialog containing the device ID, the device name and the "last seen" information.<>
|
|
||||||
* This dialog allow to delete the corresponding device (see [.displayDeviceDeletionDialog])
|
|
||||||
*
|
|
||||||
* @param aDeviceInfo the device information
|
|
||||||
*/
|
|
||||||
private fun displayDeviceDetailsDialog(aDeviceInfo: DeviceInfo) {
|
|
||||||
activity?.let {
|
|
||||||
val builder = AlertDialog.Builder(it)
|
|
||||||
val inflater = it.layoutInflater
|
|
||||||
val layout = inflater.inflate(R.layout.dialog_device_details, null)
|
|
||||||
var textView = layout.findViewById<TextView>(R.id.device_id)
|
|
||||||
|
|
||||||
textView.text = aDeviceInfo.deviceId
|
|
||||||
|
|
||||||
// device name
|
|
||||||
textView = layout.findViewById(R.id.device_name)
|
|
||||||
val displayName = if (aDeviceInfo.displayName.isNullOrEmpty()) LABEL_UNAVAILABLE_DATA else aDeviceInfo.displayName
|
|
||||||
textView.text = displayName
|
|
||||||
|
|
||||||
// last seen info
|
|
||||||
textView = layout.findViewById(R.id.device_last_seen)
|
|
||||||
|
|
||||||
val lastSeenIp = aDeviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"
|
|
||||||
|
|
||||||
val lastSeenTime = aDeviceInfo.lastSeenTs?.let { ts ->
|
|
||||||
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.ROOT)
|
|
||||||
val date = Date(ts)
|
|
||||||
|
|
||||||
val time = dateFormatTime.format(date)
|
|
||||||
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
|
|
||||||
|
|
||||||
dateFormat.format(date) + ", " + time
|
|
||||||
} ?: "-"
|
|
||||||
|
|
||||||
val lastSeenInfo = getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
|
||||||
textView.text = lastSeenInfo
|
|
||||||
|
|
||||||
// title & icon
|
|
||||||
builder.setTitle(R.string.devices_details_dialog_title)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_info)
|
|
||||||
.setView(layout)
|
|
||||||
.setPositiveButton(R.string.rename) { _, _ -> displayDeviceRenameDialog(aDeviceInfo) }
|
|
||||||
|
|
||||||
// disable the deletion for our own device
|
|
||||||
if (session.getMyDevice().deviceId != aDeviceInfo.deviceId) {
|
|
||||||
builder.setNegativeButton(R.string.delete) { _, _ -> deleteDevice(aDeviceInfo) }
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setNeutralButton(R.string.cancel, null)
|
|
||||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
|
||||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
dialog.cancel()
|
|
||||||
return@OnKeyListener true
|
|
||||||
}
|
|
||||||
false
|
|
||||||
})
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display an alert dialog to rename a device
|
|
||||||
*
|
|
||||||
* @param aDeviceInfoToRename device info
|
|
||||||
*/
|
|
||||||
private fun displayDeviceRenameDialog(aDeviceInfoToRename: DeviceInfo) {
|
|
||||||
activity?.let {
|
|
||||||
val inflater = it.layoutInflater
|
|
||||||
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
|
|
||||||
|
|
||||||
val input = layout.findViewById<EditText>(R.id.edit_text)
|
|
||||||
input.setText(aDeviceInfoToRename.displayName)
|
|
||||||
|
|
||||||
AlertDialog.Builder(it)
|
|
||||||
.setTitle(R.string.devices_details_device_name)
|
|
||||||
.setView(layout)
|
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
|
||||||
displayLoadingView()
|
|
||||||
|
|
||||||
val newName = input.text.toString()
|
|
||||||
|
|
||||||
session.setDeviceName(aDeviceInfoToRename.deviceId!!, newName, object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
hideLoadingView()
|
|
||||||
|
|
||||||
// search which preference is updated
|
|
||||||
val count = mDevicesListSettingsCategory.preferenceCount
|
|
||||||
|
|
||||||
for (i in 0 until count) {
|
|
||||||
val pref = mDevicesListSettingsCategory.getPreference(i)
|
|
||||||
|
|
||||||
if (aDeviceInfoToRename.deviceId == pref.title) {
|
|
||||||
pref.summary = newName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// detect if the updated device is the current account one
|
|
||||||
if (cryptoInfoDeviceIdPreference.summary == aDeviceInfoToRename.deviceId) {
|
|
||||||
cryptoInfoDeviceNamePreference.summary = newName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also change the display name in aDeviceInfoToRename, in case of multiple renaming
|
|
||||||
aDeviceInfoToRename.displayName = newName
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
onCommonDone(failure.localizedMessage)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to delete a device.
|
|
||||||
*
|
|
||||||
* @param deviceInfo the device to delete
|
|
||||||
*/
|
|
||||||
private fun deleteDevice(deviceInfo: DeviceInfo) {
|
|
||||||
val deviceId = deviceInfo.deviceId
|
|
||||||
if (deviceId == null) {
|
|
||||||
Timber.e("## displayDeviceDeletionDialog(): sanity check failure")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
displayLoadingView()
|
|
||||||
session.deleteDevice(deviceId, object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
hideLoadingView()
|
|
||||||
// force settings update
|
|
||||||
refreshDevicesList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
var isPasswordRequestFound = false
|
|
||||||
|
|
||||||
if (failure is Failure.RegistrationFlowError) {
|
|
||||||
// We only support LoginFlowTypes.PASSWORD
|
|
||||||
// Check if we can provide the user password
|
|
||||||
failure.registrationFlowResponse.flows?.forEach { interactiveAuthenticationFlow ->
|
|
||||||
isPasswordRequestFound = isPasswordRequestFound || interactiveAuthenticationFlow.stages?.any { it == LoginFlowTypes.PASSWORD } == true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPasswordRequestFound) {
|
|
||||||
maybeShowDeleteDeviceWithPasswordDialog(deviceId, failure.registrationFlowResponse.session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPasswordRequestFound) {
|
|
||||||
// LoginFlowTypes.PASSWORD not supported, and this is the only one RiotX supports so far...
|
|
||||||
onCommonDone(failure.localizedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a dialog to ask for user password, or use a previously entered password.
|
|
||||||
*/
|
|
||||||
private fun maybeShowDeleteDeviceWithPasswordDialog(deviceId: String, authSession: String?) {
|
|
||||||
if (mAccountPassword.isNotEmpty()) {
|
|
||||||
deleteDeviceWithPassword(deviceId, authSession, mAccountPassword)
|
|
||||||
} else {
|
|
||||||
activity?.let {
|
|
||||||
val inflater = it.layoutInflater
|
|
||||||
val layout = inflater.inflate(R.layout.dialog_device_delete, null)
|
|
||||||
val passwordEditText = layout.findViewById<EditText>(R.id.delete_password)
|
|
||||||
|
|
||||||
AlertDialog.Builder(it)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setTitle(R.string.devices_delete_dialog_title)
|
|
||||||
.setView(layout)
|
|
||||||
.setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ ->
|
|
||||||
if (passwordEditText.toString().isEmpty()) {
|
|
||||||
it.toast(R.string.error_empty_field_your_password)
|
|
||||||
return@OnClickListener
|
|
||||||
}
|
|
||||||
mAccountPassword = passwordEditText.text.toString()
|
|
||||||
deleteDeviceWithPassword(deviceId, authSession, mAccountPassword)
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
|
||||||
hideLoadingView()
|
|
||||||
}
|
|
||||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
|
||||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
dialog.cancel()
|
|
||||||
hideLoadingView()
|
|
||||||
return@OnKeyListener true
|
|
||||||
}
|
|
||||||
false
|
|
||||||
})
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteDeviceWithPassword(deviceId: String, authSession: String?, accountPassword: String) {
|
|
||||||
session.deleteDeviceWithUserPassword(deviceId, authSession, accountPassword, object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
hideLoadingView()
|
|
||||||
// force settings update
|
|
||||||
refreshDevicesList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
// Password is maybe not good
|
|
||||||
onCommonDone(failure.localizedMessage)
|
|
||||||
mAccountPassword = ""
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
|
@ -860,6 +499,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
private const val DEVICES_PREFERENCE_KEY_BASE = "DEVICES_PREFERENCE_KEY_BASE"
|
private const val DEVICES_PREFERENCE_KEY_BASE = "DEVICES_PREFERENCE_KEY_BASE"
|
||||||
|
|
||||||
// TODO i18n
|
// TODO i18n
|
||||||
private const val LABEL_UNAVAILABLE_DATA = "none"
|
const val LABEL_UNAVAILABLE_DATA = "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.settings.devices
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list item for Device.
|
||||||
|
*/
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_device)
|
||||||
|
abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var deviceInfo: DeviceInfo
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var currentDevice = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var buttonsVisible = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var itemClickAction: (() -> Unit)? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var renameClickAction: (() -> Unit)? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var deleteClickAction: (() -> Unit)? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
holder.root.setOnClickListener { itemClickAction?.invoke() }
|
||||||
|
|
||||||
|
holder.displayNameText.text = deviceInfo.displayName ?: ""
|
||||||
|
holder.deviceIdText.text = deviceInfo.deviceId ?: ""
|
||||||
|
|
||||||
|
val lastSeenIp = deviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"
|
||||||
|
|
||||||
|
val lastSeenTime = deviceInfo.lastSeenTs?.let { ts ->
|
||||||
|
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.ROOT)
|
||||||
|
val date = Date(ts)
|
||||||
|
|
||||||
|
val time = dateFormatTime.format(date)
|
||||||
|
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
|
||||||
|
|
||||||
|
dateFormat.format(date) + ", " + time
|
||||||
|
} ?: "-"
|
||||||
|
|
||||||
|
holder.deviceLastSeenText.text = holder.root.context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
holder.displayNameLabelText,
|
||||||
|
holder.displayNameText,
|
||||||
|
holder.deviceIdLabelText,
|
||||||
|
holder.deviceIdText,
|
||||||
|
holder.deviceLastSeenLabelText,
|
||||||
|
holder.deviceLastSeenText
|
||||||
|
).map {
|
||||||
|
it.setTypeface(null, if (currentDevice) Typeface.BOLD else Typeface.NORMAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.buttonDelete.isVisible = !currentDevice
|
||||||
|
|
||||||
|
holder.buttons.isVisible = buttonsVisible
|
||||||
|
|
||||||
|
holder.buttonRename.setOnClickListener { renameClickAction?.invoke() }
|
||||||
|
holder.buttonDelete.setOnClickListener { deleteClickAction?.invoke() }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val root by bind<ViewGroup>(R.id.itemDeviceRoot)
|
||||||
|
val displayNameLabelText by bind<TextView>(R.id.itemDeviceDisplayNameLabel)
|
||||||
|
val displayNameText by bind<TextView>(R.id.itemDeviceDisplayName)
|
||||||
|
val deviceIdLabelText by bind<TextView>(R.id.itemDeviceIdLabel)
|
||||||
|
val deviceIdText by bind<TextView>(R.id.itemDeviceId)
|
||||||
|
val deviceLastSeenLabelText by bind<TextView>(R.id.itemDeviceLastSeenLabel)
|
||||||
|
val deviceLastSeenText by bind<TextView>(R.id.itemDeviceLastSeen)
|
||||||
|
val buttons by bind<View>(R.id.itemDeviceButtons)
|
||||||
|
val buttonDelete by bind<View>(R.id.itemDeviceDelete)
|
||||||
|
val buttonRename by bind<View>(R.id.itemDeviceRename)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.settings.devices
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.extensions.sortByLastSeen
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||||
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.ui.list.genericItemHeader
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DevicesController @Inject constructor(private val errorFormatter: ErrorFormatter,
|
||||||
|
private val stringProvider: StringProvider) : EpoxyController() {
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
private var viewState: DevicesViewState? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(viewState: DevicesViewState) {
|
||||||
|
this.viewState = viewState
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels() {
|
||||||
|
val nonNullViewState = viewState ?: return
|
||||||
|
buildDevicesModels(nonNullViewState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildDevicesModels(state: DevicesViewState) {
|
||||||
|
when (val devices = state.devices) {
|
||||||
|
is Loading,
|
||||||
|
is Uninitialized ->
|
||||||
|
loadingItem {
|
||||||
|
id("loading")
|
||||||
|
}
|
||||||
|
is Fail ->
|
||||||
|
errorWithRetryItem {
|
||||||
|
id("error")
|
||||||
|
text(errorFormatter.toHumanReadable(devices.error))
|
||||||
|
listener { callback?.retry() }
|
||||||
|
}
|
||||||
|
is Success ->
|
||||||
|
buildDevicesList(devices(), state.myDeviceId, state.currentExpandedDeviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildDevicesList(devices: List<DeviceInfo>, myDeviceId: String, currentExpandedDeviceId: String?) {
|
||||||
|
// Current device
|
||||||
|
genericItemHeader {
|
||||||
|
id("current")
|
||||||
|
text(stringProvider.getString(R.string.devices_current_device))
|
||||||
|
}
|
||||||
|
|
||||||
|
devices
|
||||||
|
.filter {
|
||||||
|
it.deviceId == myDeviceId
|
||||||
|
}
|
||||||
|
.forEachIndexed { idx, deviceInfo ->
|
||||||
|
deviceItem {
|
||||||
|
id("myDevice$idx")
|
||||||
|
deviceInfo(deviceInfo)
|
||||||
|
currentDevice(true)
|
||||||
|
buttonsVisible(deviceInfo.deviceId == currentExpandedDeviceId)
|
||||||
|
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
|
||||||
|
renameClickAction { callback?.onRenameDevice(deviceInfo) }
|
||||||
|
deleteClickAction { callback?.onDeleteDevice(deviceInfo) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other devices
|
||||||
|
if (devices.size > 1) {
|
||||||
|
genericItemHeader {
|
||||||
|
id("others")
|
||||||
|
text(stringProvider.getString(R.string.devices_other_devices))
|
||||||
|
}
|
||||||
|
|
||||||
|
devices
|
||||||
|
.filter {
|
||||||
|
it.deviceId != myDeviceId
|
||||||
|
}
|
||||||
|
// sort before display: most recent first
|
||||||
|
.sortByLastSeen()
|
||||||
|
.forEachIndexed { idx, deviceInfo ->
|
||||||
|
val isCurrentDevice = deviceInfo.deviceId == myDeviceId
|
||||||
|
deviceItem {
|
||||||
|
id("device$idx")
|
||||||
|
deviceInfo(deviceInfo)
|
||||||
|
currentDevice(isCurrentDevice)
|
||||||
|
buttonsVisible(deviceInfo.deviceId == currentExpandedDeviceId)
|
||||||
|
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
|
||||||
|
renameClickAction { callback?.onRenameDevice(deviceInfo) }
|
||||||
|
deleteClickAction { callback?.onDeleteDevice(deviceInfo) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun retry()
|
||||||
|
fun onDeviceClicked(deviceInfo: DeviceInfo)
|
||||||
|
fun onRenameDevice(deviceInfo: DeviceInfo)
|
||||||
|
fun onDeleteDevice(deviceInfo: DeviceInfo)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.settings.devices
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.airbnb.mvrx.*
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
data class DevicesViewState(
|
||||||
|
val myDeviceId: String = "",
|
||||||
|
val devices: Async<List<DeviceInfo>> = Uninitialized,
|
||||||
|
val currentExpandedDeviceId: String? = null,
|
||||||
|
val request: Async<Unit> = Uninitialized
|
||||||
|
) : MvRxState
|
||||||
|
|
||||||
|
sealed class DevicesAction : VectorViewModelAction {
|
||||||
|
object Retry : DevicesAction()
|
||||||
|
data class Delete(val deviceInfo: DeviceInfo) : DevicesAction()
|
||||||
|
data class Password(val password: String) : DevicesAction()
|
||||||
|
data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction()
|
||||||
|
data class ToggleDevice(val deviceInfo: DeviceInfo) : DevicesAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<DevicesViewState, DevicesAction>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: DevicesViewState): DevicesViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<DevicesViewModel, DevicesViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: DevicesViewState): DevicesViewModel? {
|
||||||
|
val fragment: VectorSettingsDevicesFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.devicesViewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// temp storage when we ask for the user password
|
||||||
|
private var _currentDeviceId: String? = null
|
||||||
|
private var _currentSession: String? = null
|
||||||
|
|
||||||
|
private val _requestPasswordLiveData = MutableLiveData<LiveEvent<Unit>>()
|
||||||
|
val requestPasswordLiveData: LiveData<LiveEvent<Unit>>
|
||||||
|
get() = _requestPasswordLiveData
|
||||||
|
|
||||||
|
init {
|
||||||
|
refreshDevicesList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the refresh of the devices list.
|
||||||
|
* The devices list is the list of the devices where the user is logged in.
|
||||||
|
* It can be any mobile devices, and any browsers.
|
||||||
|
*/
|
||||||
|
private fun refreshDevicesList() {
|
||||||
|
if (session.isCryptoEnabled() && !session.sessionParams.credentials.deviceId.isNullOrEmpty()) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
devices = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
||||||
|
override fun onSuccess(data: DevicesListResponse) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
myDeviceId = session.sessionParams.credentials.deviceId ?: "",
|
||||||
|
devices = Success(data.devices.orEmpty())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
devices = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Should not happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: DevicesAction) {
|
||||||
|
return when (action) {
|
||||||
|
is DevicesAction.Retry -> refreshDevicesList()
|
||||||
|
is DevicesAction.Delete -> handleDelete(action)
|
||||||
|
is DevicesAction.Password -> handlePassword(action)
|
||||||
|
is DevicesAction.Rename -> handleRename(action)
|
||||||
|
is DevicesAction.ToggleDevice -> handleToggleDevice(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleToggleDevice(action: DevicesAction.ToggleDevice) {
|
||||||
|
withState {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
currentExpandedDeviceId = if (it.currentExpandedDeviceId == action.deviceInfo.deviceId) null else action.deviceInfo.deviceId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRename(action: DevicesAction.Rename) {
|
||||||
|
session.setDeviceName(action.deviceInfo.deviceId!!, action.newName, object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Success(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// force settings update
|
||||||
|
refreshDevicesList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestErrorLiveData.postLiveEvent(failure)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to delete a device.
|
||||||
|
*/
|
||||||
|
private fun handleDelete(action: DevicesAction.Delete) {
|
||||||
|
val deviceId = action.deviceInfo.deviceId
|
||||||
|
if (deviceId == null) {
|
||||||
|
Timber.e("## handleDelete(): sanity check failure")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
session.deleteDevice(deviceId, object : MatrixCallback<Unit> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
var isPasswordRequestFound = false
|
||||||
|
|
||||||
|
if (failure is Failure.RegistrationFlowError) {
|
||||||
|
// We only support LoginFlowTypes.PASSWORD
|
||||||
|
// Check if we can provide the user password
|
||||||
|
failure.registrationFlowResponse.flows?.forEach { interactiveAuthenticationFlow ->
|
||||||
|
isPasswordRequestFound = isPasswordRequestFound || interactiveAuthenticationFlow.stages?.any { it == LoginFlowTypes.PASSWORD } == true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPasswordRequestFound) {
|
||||||
|
_currentDeviceId = deviceId
|
||||||
|
_currentSession = failure.registrationFlowResponse.session
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Success(Unit)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestPasswordLiveData.postLiveEvent(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPasswordRequestFound) {
|
||||||
|
// LoginFlowTypes.PASSWORD not supported, and this is the only one RiotX supports so far...
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestErrorLiveData.postLiveEvent(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Success(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// force settings update
|
||||||
|
refreshDevicesList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePassword(action: DevicesAction.Password) {
|
||||||
|
val currentDeviceId = _currentDeviceId
|
||||||
|
if (currentDeviceId.isNullOrBlank()) {
|
||||||
|
// Abort
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
session.deleteDeviceWithUserPassword(currentDeviceId, _currentSession, action.password, object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
_currentDeviceId = null
|
||||||
|
_currentSession = null
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Success(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// force settings update
|
||||||
|
refreshDevicesList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
_currentDeviceId = null
|
||||||
|
_currentSession = null
|
||||||
|
|
||||||
|
// Password is maybe not good
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
request = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestErrorLiveData.postLiveEvent(failure)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.settings.devices
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
|
import im.vector.riotx.core.extensions.observeEvent
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.utils.toast
|
||||||
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
|
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the list of the user's device
|
||||||
|
*/
|
||||||
|
class VectorSettingsDevicesFragment @Inject constructor(
|
||||||
|
val devicesViewModelFactory: DevicesViewModel.Factory,
|
||||||
|
private val devicesController: DevicesController
|
||||||
|
) : VectorBaseFragment(), DevicesController.Callback {
|
||||||
|
|
||||||
|
// used to avoid requesting to enter the password for each deletion
|
||||||
|
private var mAccountPassword: String = ""
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
|
|
||||||
|
private val devicesViewModel: DevicesViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
waiting_view_status_text.setText(R.string.please_wait)
|
||||||
|
waiting_view_status_text.isVisible = true
|
||||||
|
devicesController.callback = this
|
||||||
|
recyclerView.configureWith(devicesController, showDivider = true)
|
||||||
|
devicesViewModel.requestErrorLiveData.observeEvent(this) {
|
||||||
|
displayErrorDialog(it)
|
||||||
|
// Password is maybe not good, for safety measure, reset it here
|
||||||
|
mAccountPassword = ""
|
||||||
|
}
|
||||||
|
devicesViewModel.requestPasswordLiveData.observeEvent(this) {
|
||||||
|
maybeShowDeleteDeviceWithPasswordDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
devicesController.callback = null
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_devices_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displayErrorDialog(throwable: Throwable) {
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.dialog_title_error)
|
||||||
|
.setMessage(errorFormatter.toHumanReadable(throwable))
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeviceClicked(deviceInfo: DeviceInfo) {
|
||||||
|
devicesViewModel.handle(DevicesAction.ToggleDevice(deviceInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteDevice(deviceInfo: DeviceInfo) {
|
||||||
|
devicesViewModel.handle(DevicesAction.Delete(deviceInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRenameDevice(deviceInfo: DeviceInfo) {
|
||||||
|
displayDeviceRenameDialog(deviceInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun retry() {
|
||||||
|
devicesViewModel.handle(DevicesAction.Retry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an alert dialog to rename a device
|
||||||
|
*
|
||||||
|
* @param deviceInfo device info
|
||||||
|
*/
|
||||||
|
private fun displayDeviceRenameDialog(deviceInfo: DeviceInfo) {
|
||||||
|
val inflater = requireActivity().layoutInflater
|
||||||
|
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
|
||||||
|
|
||||||
|
val input = layout.findViewById<EditText>(R.id.edit_text)
|
||||||
|
input.setText(deviceInfo.displayName)
|
||||||
|
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.devices_details_device_name)
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
val newName = input.text.toString()
|
||||||
|
|
||||||
|
devicesViewModel.handle(DevicesAction.Rename(deviceInfo, newName))
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a dialog to ask for user password, or use a previously entered password.
|
||||||
|
*/
|
||||||
|
private fun maybeShowDeleteDeviceWithPasswordDialog() {
|
||||||
|
if (mAccountPassword.isNotEmpty()) {
|
||||||
|
devicesViewModel.handle(DevicesAction.Password(mAccountPassword))
|
||||||
|
} else {
|
||||||
|
val inflater = requireActivity().layoutInflater
|
||||||
|
val layout = inflater.inflate(R.layout.dialog_device_delete, null)
|
||||||
|
val passwordEditText = layout.findViewById<EditText>(R.id.delete_password)
|
||||||
|
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setTitle(R.string.devices_delete_dialog_title)
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ ->
|
||||||
|
if (passwordEditText.toString().isEmpty()) {
|
||||||
|
requireActivity().toast(R.string.error_empty_field_your_password)
|
||||||
|
return@OnClickListener
|
||||||
|
}
|
||||||
|
mAccountPassword = passwordEditText.text.toString()
|
||||||
|
devicesViewModel.handle(DevicesAction.Password(mAccountPassword))
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||||
|
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
dialog.cancel()
|
||||||
|
return@OnKeyListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(devicesViewModel) { state ->
|
||||||
|
devicesController.update(state)
|
||||||
|
|
||||||
|
handleRequestStatus(state.request)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRequestStatus(unIgnoreRequest: Async<Unit>) {
|
||||||
|
when (unIgnoreRequest) {
|
||||||
|
is Loading -> waiting_view.isVisible = true
|
||||||
|
else -> waiting_view.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView 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">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/device_container_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="?dialogPreferredPadding"
|
|
||||||
android:paddingLeft="?dialogPreferredPadding"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingEnd="?dialogPreferredPadding"
|
|
||||||
android:paddingRight="?dialogPreferredPadding">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/device_id_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:text="@string/devices_details_id_title"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/device_id"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="12sp"
|
|
||||||
tools:text="a device id" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/device_name_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:text="@string/devices_details_name_title"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/device_name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="12sp"
|
|
||||||
tools:text="a device name" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/device_last_seen_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:text="@string/devices_details_last_seen_title"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/device_last_seen"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="12sp"
|
|
||||||
tools:text="x.x.x.x @ 01/01 00:00:00" />
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
56
vector/src/main/res/layout/item_autocomplete_emoji.xml
Normal file
56
vector/src/main/res/layout/item_autocomplete_emoji.xml
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?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:background="?riotx_background"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemAutocompleteEmoji"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="20dp"
|
||||||
|
tools:ignore="SpUsage"
|
||||||
|
tools:text="@sample/reactions.json/data/reaction" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemAutocompleteEmojiName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemAutocompleteEmojiSubname"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="name"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/autocomplete_limited_results"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp" />
|
98
vector/src/main/res/layout/item_device.xml
Normal file
98
vector/src/main/res/layout/item_device.xml
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<?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:id="@+id/itemDeviceRoot"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemDeviceDisplayNameLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/devices_details_name_title"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemDeviceDisplayName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Android phone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemDeviceIdLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/devices_details_id_title"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemDeviceId"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
tools:text="XUIDERFZAA" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemDeviceLastSeenLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/devices_details_last_seen_title"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemDeviceLastSeen"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
tools:text="x.x.x.x @ 01/01 00:00:00" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/itemDeviceButtons"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/itemDeviceRename"
|
||||||
|
style="@style/VectorButtonStyleText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/rename" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/itemDeviceDelete"
|
||||||
|
style="@style/VectorButtonStyleText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="@string/delete"
|
||||||
|
android:textColor="@color/riotx_notice"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="5dp"
|
|
||||||
android:background="?vctr_shadow_bottom" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="2dp"
|
|
||||||
android:background="?vctr_preference_divider_color" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="3dp"
|
|
||||||
android:background="?vctr_shadow_top" />
|
|
||||||
</LinearLayout>
|
|
|
@ -63,9 +63,6 @@
|
||||||
<!-- room notification text color (typing, unsent...) -->
|
<!-- room notification text color (typing, unsent...) -->
|
||||||
<attr name="vctr_room_notification_text_color" format="color" />
|
<attr name="vctr_room_notification_text_color" format="color" />
|
||||||
|
|
||||||
<!-- color for dividers in settings -->
|
|
||||||
<attr name="vctr_preference_divider_color" format="color" />
|
|
||||||
|
|
||||||
<!-- icon colors -->
|
<!-- icon colors -->
|
||||||
<attr name="vctr_icon_tint_on_light_action_bar_color" format="color" />
|
<attr name="vctr_icon_tint_on_light_action_bar_color" format="color" />
|
||||||
<attr name="vctr_icon_tint_on_dark_action_bar_color" format="color" />
|
<attr name="vctr_icon_tint_on_dark_action_bar_color" format="color" />
|
||||||
|
|
|
@ -5,4 +5,19 @@
|
||||||
|
|
||||||
<string name="notification_initial_sync">Initial Sync…</string>
|
<string name="notification_initial_sync">Initial Sync…</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="settings_show_devices_list">See all my devices</string>
|
||||||
|
<string name="settings_advanced_settings">Advanced settings</string>
|
||||||
|
<string name="settings_developer_mode">Developer mode</string>
|
||||||
|
<string name="settings_developer_mode_summary">The developer mode activates hidden features and may also make the application less stable. For developers only!</string>
|
||||||
|
<string name="settings_rageshake">Rageshake</string>
|
||||||
|
<string name="settings_rageshake_detection_threshold">Detection threshold</string>
|
||||||
|
<string name="settings_rageshake_detection_threshold_summary">Shake your phone to test the detection threshold</string>
|
||||||
|
<string name="rageshake_detected">Shake detected!</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
<string name="devices_current_device">Current device</string>
|
||||||
|
<string name="devices_other_devices">Other devices</string>
|
||||||
|
|
||||||
|
<string name="autocomplete_limited_results">Showing only the first results, type more letters…</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -72,9 +72,6 @@
|
||||||
<item name="vctr_tab_home_secondary">@color/primary_color_dark_black</item>
|
<item name="vctr_tab_home_secondary">@color/primary_color_dark_black</item>
|
||||||
<item name="vctr_list_divider_color">@color/list_divider_color_black</item>
|
<item name="vctr_list_divider_color">@color/list_divider_color_black</item>
|
||||||
|
|
||||||
<!-- color for dividers in settings -->
|
|
||||||
<item name="vctr_preference_divider_color">@color/list_divider_color_black</item>
|
|
||||||
|
|
||||||
<item name="vctr_markdown_block_background_color">#FF4D4D4D</item>
|
<item name="vctr_markdown_block_background_color">#FF4D4D4D</item>
|
||||||
|
|
||||||
<item name="vctr_pill_receipt">@drawable/pill_receipt_black</item>
|
<item name="vctr_pill_receipt">@drawable/pill_receipt_black</item>
|
||||||
|
|
|
@ -139,9 +139,6 @@
|
||||||
<!--Notice (secondary)-->
|
<!--Notice (secondary)-->
|
||||||
<item name="vctr_room_notification_text_color">#FF61708b</item>
|
<item name="vctr_room_notification_text_color">#FF61708b</item>
|
||||||
|
|
||||||
<!-- color for dividers in settings -->
|
|
||||||
<item name="vctr_preference_divider_color">@color/list_divider_color_dark</item>
|
|
||||||
|
|
||||||
<!-- icon colors -->
|
<!-- icon colors -->
|
||||||
<item name="vctr_settings_icon_tint_color">@android:color/white</item>
|
<item name="vctr_settings_icon_tint_color">@android:color/white</item>
|
||||||
<item name="vctr_icon_tint_on_light_action_bar_color">@color/riotx_accent</item>
|
<item name="vctr_icon_tint_on_light_action_bar_color">@color/riotx_accent</item>
|
||||||
|
|
|
@ -139,9 +139,6 @@
|
||||||
<!--Notice (secondary)-->
|
<!--Notice (secondary)-->
|
||||||
<item name="vctr_room_notification_text_color">#FF61708b</item>
|
<item name="vctr_room_notification_text_color">#FF61708b</item>
|
||||||
|
|
||||||
<!-- color for dividers in settings -->
|
|
||||||
<item name="vctr_preference_divider_color">@color/list_divider_color_light</item>
|
|
||||||
|
|
||||||
<!-- icon colors -->
|
<!-- icon colors -->
|
||||||
<item name="vctr_settings_icon_tint_color">@android:color/black</item>
|
<item name="vctr_settings_icon_tint_color">@android:color/black</item>
|
||||||
<item name="vctr_icon_tint_on_light_action_bar_color">@color/riotx_accent</item>
|
<item name="vctr_icon_tint_on_light_action_bar_color">@color/riotx_accent</item>
|
||||||
|
|
|
@ -88,9 +88,6 @@
|
||||||
<!-- room notification text color (typing, unsent...) -->
|
<!-- room notification text color (typing, unsent...) -->
|
||||||
<item name="vctr_room_notification_text_color">#a0a29f</item>
|
<item name="vctr_room_notification_text_color">#a0a29f</item>
|
||||||
|
|
||||||
<!-- color for dividers in settings -->
|
|
||||||
<item name="vctr_preference_divider_color">#e1e1e1</item>
|
|
||||||
|
|
||||||
<!-- icon colors -->
|
<!-- icon colors -->
|
||||||
<item name="vctr_settings_icon_tint_color">@color/accent_color_status</item>
|
<item name="vctr_settings_icon_tint_color">@color/accent_color_status</item>
|
||||||
<item name="vctr_icon_tint_on_light_action_bar_color">@color/riotx_accent</item>
|
<item name="vctr_icon_tint_on_light_action_bar_color">@color/riotx_accent</item>
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_developer_mode">
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
|
||||||
|
android:summary="@string/settings_developer_mode_summary"
|
||||||
|
android:title="@string/settings_developer_mode" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:dependency="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
|
||||||
|
android:key="SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
|
||||||
|
android:title="@string/settings_labs_show_hidden_events_in_timeline" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:dependency="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
|
||||||
|
android:key="SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
||||||
|
android:summary="@string/labs_allow_extended_logging_summary"
|
||||||
|
android:title="@string/labs_allow_extended_logging" />
|
||||||
|
|
||||||
|
<!-- TODO Display unsupported events -->
|
||||||
|
|
||||||
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
|
android:key="SETTINGS_RAGE_SHAKE_CATEGORY_KEY"
|
||||||
|
android:title="@string/settings_rageshake">
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
|
android:key="SETTINGS_USE_RAGE_SHAKE_KEY"
|
||||||
|
android:title="@string/send_bug_report_rage_shake" />
|
||||||
|
|
||||||
|
<androidx.preference.SeekBarPreference
|
||||||
|
android:defaultValue="13"
|
||||||
|
android:dependency="SETTINGS_USE_RAGE_SHAKE_KEY"
|
||||||
|
android:key="SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY"
|
||||||
|
android:max="15"
|
||||||
|
android:summary="@string/settings_rageshake_detection_threshold_summary"
|
||||||
|
android:title="@string/settings_rageshake_detection_threshold"
|
||||||
|
app:min="11" />
|
||||||
|
|
||||||
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_notifications">
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/settings_notifications_targets"
|
||||||
|
app:fragment="im.vector.riotx.features.settings.push.PushGatewaysFragment" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/settings_push_rules"
|
||||||
|
app:fragment="im.vector.riotx.features.settings.push.PushRulesFragment" />
|
||||||
|
|
||||||
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
|
</androidx.preference.PreferenceScreen>
|
|
@ -45,11 +45,10 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider />
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
android:key="SETTINGS_CONTACT_PREFERENCE_KEYS"
|
android:key="SETTINGS_CONTACT_PREFERENCE_KEYS"
|
||||||
android:title="@string/settings_contact">
|
android:title="@string/settings_contact"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:key="CONTACT_BOOK_ACCESS_KEY"
|
android:key="CONTACT_BOOK_ACCESS_KEY"
|
||||||
|
@ -62,8 +61,6 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider />
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_advanced">
|
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_advanced">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
|
@ -74,11 +71,12 @@
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:key="SETTINGS_HOME_SERVER_PREFERENCE_KEY"
|
android:key="SETTINGS_HOME_SERVER_PREFERENCE_KEY"
|
||||||
android:title="@string/settings_home_server"
|
android:title="@string/settings_home_server"
|
||||||
tools:summary="@string/default_hs_server_url" />
|
tools:summary="https://homeserver.org" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:key="SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY"
|
android:key="SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY"
|
||||||
android:title="@string/settings_identity_server"
|
android:title="@string/settings_identity_server"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented"
|
||||||
tools:summary="https://identity.server.url" />
|
tools:summary="https://identity.server.url" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
|
@ -91,10 +89,7 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider />
|
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/action_sign_out">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
|
||||||
android:title="@string/action_sign_out">
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:key="SETTINGS_SIGN_OUT_KEY"
|
android:key="SETTINGS_SIGN_OUT_KEY"
|
||||||
|
@ -104,7 +99,8 @@
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
android:key="SETTINGS_DEACTIVATE_ACCOUNT_CATEGORY_KEY"
|
android:key="SETTINGS_DEACTIVATE_ACCOUNT_CATEGORY_KEY"
|
||||||
android:title="@string/settings_deactivate_account_section">
|
android:title="@string/settings_deactivate_account_section"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:key="SETTINGS_DEACTIVATE_ACCOUNT_KEY"
|
android:key="SETTINGS_DEACTIVATE_ACCOUNT_KEY"
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
|
||||||
android:key="SETTINGS_OTHERS_PREFERENCE_KEY"
|
|
||||||
android:title="@string/settings_other">
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:key="APP_INFO_LINK_PREFERENCE_KEY"
|
android:key="APP_INFO_LINK_PREFERENCE_KEY"
|
||||||
android:summary="@string/settings_app_info_link_summary"
|
android:summary="@string/settings_app_info_link_summary"
|
||||||
|
@ -46,6 +42,4 @@
|
||||||
android:key="SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY"
|
android:key="SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY"
|
||||||
android:title="@string/settings_other_third_party_notices" />
|
android:title="@string/settings_other_third_party_notices" />
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
|
@ -34,24 +34,12 @@
|
||||||
<!--android:summary="@string/settings_labs_enable_send_voice_summary"-->
|
<!--android:summary="@string/settings_labs_enable_send_voice_summary"-->
|
||||||
<!--android:title="@string/settings_labs_enable_send_voice" />-->
|
<!--android:title="@string/settings_labs_enable_send_voice" />-->
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
|
|
||||||
android:title="@string/settings_labs_show_hidden_events_in_timeline" />
|
|
||||||
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
||||||
android:title="@string/labs_swipe_to_reply_in_timeline" />
|
android:title="@string/labs_swipe_to_reply_in_timeline" />
|
||||||
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
|
||||||
android:summary="@string/labs_allow_extended_logging_summary"
|
|
||||||
android:title="@string/labs_allow_extended_logging" />
|
|
||||||
|
|
||||||
<!--</im.vector.riotx.core.preference.VectorPreferenceCategory>-->
|
<!--</im.vector.riotx.core.preference.VectorPreferenceCategory>-->
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
|
@ -36,8 +36,6 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider />
|
|
||||||
|
|
||||||
<!-- For API < 26 -->
|
<!-- For API < 26 -->
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:dialogTitle="@string/settings_notification_ringtone"
|
android:dialogTitle="@string/settings_notification_ringtone"
|
||||||
|
|
|
@ -67,27 +67,8 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider android:key="SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY" />
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
android:key="SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY"
|
android:key="SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY"
|
||||||
android:title="@string/settings_notifications_targets" />
|
android:title="@string/settings_notifications_targets" /-->
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider android:key="SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY" /-->
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_expert">
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:persistent="false"
|
|
||||||
android:title="@string/settings_notifications_targets"
|
|
||||||
app:fragment="im.vector.riotx.features.settings.push.PushGatewaysFragment" />
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:persistent="false"
|
|
||||||
android:title="@string/settings_push_rules"
|
|
||||||
app:fragment="im.vector.riotx.features.settings.push.PushRulesFragment" />
|
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
|
@ -30,13 +30,15 @@
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="SETTINGS_SHOW_URL_PREVIEW_KEY"
|
android:key="SETTINGS_SHOW_URL_PREVIEW_KEY"
|
||||||
android:summary="@string/settings_inline_url_preview_summary"
|
android:summary="@string/settings_inline_url_preview_summary"
|
||||||
android:title="@string/settings_inline_url_preview" />
|
android:title="@string/settings_inline_url_preview"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="SETTINGS_SEND_TYPING_NOTIF_KEY"
|
android:key="SETTINGS_SEND_TYPING_NOTIF_KEY"
|
||||||
android:summary="@string/settings_send_typing_notifs_summary"
|
android:summary="@string/settings_send_typing_notifs_summary"
|
||||||
android:title="@string/settings_send_typing_notifs" />
|
android:title="@string/settings_send_typing_notifs"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
|
@ -46,38 +48,45 @@
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:key="SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY"
|
android:key="SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY"
|
||||||
android:title="@string/settings_always_show_timestamps" />
|
android:title="@string/settings_always_show_timestamps"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:key="SETTINGS_12_24_TIMESTAMPS_KEY"
|
android:key="SETTINGS_12_24_TIMESTAMPS_KEY"
|
||||||
android:title="@string/settings_12_24_timestamps" />
|
android:title="@string/settings_12_24_timestamps"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="SETTINGS_SHOW_READ_RECEIPTS_KEY"
|
android:key="SETTINGS_SHOW_READ_RECEIPTS_KEY"
|
||||||
android:summary="@string/settings_show_read_receipts_summary"
|
android:summary="@string/settings_show_read_receipts_summary"
|
||||||
android:title="@string/settings_show_read_receipts" />
|
android:title="@string/settings_show_read_receipts"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY"
|
android:key="SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY"
|
||||||
android:summary="@string/settings_show_join_leave_messages_summary"
|
android:summary="@string/settings_show_join_leave_messages_summary"
|
||||||
android:title="@string/settings_show_join_leave_messages" />
|
android:title="@string/settings_show_join_leave_messages"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY"
|
android:key="SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY"
|
||||||
android:summary="@string/settings_show_avatar_display_name_changes_messages_summary"
|
android:summary="@string/settings_show_avatar_display_name_changes_messages_summary"
|
||||||
android:title="@string/settings_show_avatar_display_name_changes_messages" />
|
android:title="@string/settings_show_avatar_display_name_changes_messages"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:key="SETTINGS_VIBRATE_ON_MENTION_KEY"
|
android:key="SETTINGS_VIBRATE_ON_MENTION_KEY"
|
||||||
android:title="@string/settings_vibrate_on_mention" />
|
android:title="@string/settings_vibrate_on_mention"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:key="SETTINGS_SEND_MESSAGE_WITH_ENTER"
|
android:key="SETTINGS_SEND_MESSAGE_WITH_ENTER"
|
||||||
android:summary="@string/settings_send_message_with_enter_summary"
|
android:summary="@string/settings_send_message_with_enter_summary"
|
||||||
android:title="@string/settings_send_message_with_enter" />
|
android:title="@string/settings_send_message_with_enter"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorListPreference
|
<im.vector.riotx.core.preference.VectorListPreference
|
||||||
android:defaultValue="always"
|
android:defaultValue="always"
|
||||||
|
@ -85,15 +94,15 @@
|
||||||
android:entryValues="@array/show_info_area_values"
|
android:entryValues="@array/show_info_area_values"
|
||||||
android:key="SETTINGS_SHOW_INFO_AREA_KEY"
|
android:key="SETTINGS_SHOW_INFO_AREA_KEY"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/settings_info_area_show" />
|
android:title="@string/settings_info_area_show"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider />
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
android:key="SETTINGS_HOME_DISPLAY_KEY"
|
android:key="SETTINGS_HOME_DISPLAY_KEY"
|
||||||
android:title="@string/settings_home_display">
|
android:title="@string/settings_home_display"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
|
@ -107,9 +116,9 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider />
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
|
android:title="@string/settings_media"
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_media">
|
app:isPreferenceVisible="@bool/false_not_implemented">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:key="SETTINGS_MEDIA_SAVING_PERIOD_KEY"
|
android:key="SETTINGS_MEDIA_SAVING_PERIOD_KEY"
|
||||||
|
|
|
@ -3,58 +3,54 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:icon="@drawable/ic_settings_root_general"
|
android:icon="@drawable/ic_settings_root_general"
|
||||||
android:title="@string/settings_general_title"
|
android:title="@string/settings_general_title"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsGeneralFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsGeneralFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:enabled="@bool/false_not_implemented"
|
android:enabled="@bool/false_not_implemented"
|
||||||
android:icon="@drawable/ic_settings_root_flair"
|
android:icon="@drawable/ic_settings_root_flair"
|
||||||
android:title="@string/settings_flair"
|
android:title="@string/settings_flair"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsFlairFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsFlairFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:icon="@drawable/ic_settings_root_notification"
|
android:icon="@drawable/ic_settings_root_notification"
|
||||||
android:key="SETTINGS_NOTIFICATIONS_KEY"
|
android:key="SETTINGS_NOTIFICATIONS_KEY"
|
||||||
android:title="@string/settings_notifications"
|
android:title="@string/settings_notifications"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:icon="@drawable/ic_settings_root_preferences"
|
android:icon="@drawable/ic_settings_root_preferences"
|
||||||
android:title="@string/settings_preferences"
|
android:title="@string/settings_preferences"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsPreferencesFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsPreferencesFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:enabled="@bool/false_not_implemented"
|
android:enabled="@bool/false_not_implemented"
|
||||||
android:icon="@drawable/ic_settings_root_call"
|
android:icon="@drawable/ic_settings_root_call"
|
||||||
android:title="@string/preference_voice_and_video"
|
android:title="@string/preference_voice_and_video"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsVoiceVideoFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsVoiceVideoFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:icon="@drawable/ic_settings_root_ignored_users"
|
android:icon="@drawable/ic_settings_root_ignored_users"
|
||||||
android:title="@string/settings_ignored_users"
|
android:title="@string/settings_ignored_users"
|
||||||
app:fragment="im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment" />
|
app:fragment="im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:icon="@drawable/ic_settings_root_security_privacy"
|
android:icon="@drawable/ic_settings_root_security_privacy"
|
||||||
android:title="@string/settings_security_and_privacy"
|
android:title="@string/settings_security_and_privacy"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:icon="@drawable/ic_settings_root_labs"
|
android:icon="@drawable/ic_settings_root_labs"
|
||||||
android:title="@string/room_settings_labs_pref_title"
|
android:title="@string/room_settings_labs_pref_title"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsLabsFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsLabsFragment" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreference
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:layout_width="match_parent"
|
android:icon="@drawable/ic_settings_root_general"
|
||||||
|
android:title="@string/settings_advanced_settings"
|
||||||
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsAdvancedSettingsFragment" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
android:icon="@drawable/ic_settings_root_help_about"
|
android:icon="@drawable/ic_settings_root_help_about"
|
||||||
android:title="@string/preference_root_help_about"
|
android:title="@string/preference_root_help_about"
|
||||||
app:fragment="im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment" />
|
app:fragment="im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment" />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<!-- ************ Cryptography section ************ -->
|
<!-- ************ Cryptography section ************ -->
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
|
@ -21,11 +22,22 @@
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:key="SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY"
|
android:key="SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY"
|
||||||
android:summary="@string/encryption_never_send_to_unverified_devices_summary"
|
android:summary="@string/encryption_never_send_to_unverified_devices_summary"
|
||||||
android:title="@string/encryption_never_send_to_unverified_devices_title" />
|
android:title="@string/encryption_never_send_to_unverified_devices_title"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented" />
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider android:key="SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY" />
|
<!-- devices list entry point -->
|
||||||
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
|
android:key="SETTINGS_DEVICES_LIST_PREFERENCE_KEY"
|
||||||
|
android:title="@string/settings_devices_list">
|
||||||
|
|
||||||
|
<im.vector.riotx.core.preference.VectorPreference
|
||||||
|
android:key="SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY"
|
||||||
|
android:title="@string/settings_show_devices_list"
|
||||||
|
app:fragment="im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment" />
|
||||||
|
|
||||||
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
|
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
|
||||||
|
@ -48,18 +60,10 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY" />
|
|
||||||
|
|
||||||
<!-- devices list: device ids + device names -->
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
|
||||||
android:key="SETTINGS_DEVICES_LIST_PREFERENCE_KEY"
|
|
||||||
android:title="@string/settings_devices_list" />
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceDivider android:key="SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY" />
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||||
android:key="SETTINGS_ANALYTICS_PREFERENCE_KEY"
|
android:key="SETTINGS_ANALYTICS_PREFERENCE_KEY"
|
||||||
android:title="@string/settings_analytics">
|
android:title="@string/settings_analytics"
|
||||||
|
app:isPreferenceVisible="@bool/false_not_implemented">
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
|
@ -67,9 +71,6 @@
|
||||||
android:summary="@string/settings_opt_in_of_analytics_summary"
|
android:summary="@string/settings_opt_in_of_analytics_summary"
|
||||||
android:title="@string/settings_opt_in_of_analytics" />
|
android:title="@string/settings_opt_in_of_analytics" />
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
|
||||||
android:key="SETTINGS_USE_RAGE_SHAKE_KEY"
|
|
||||||
android:title="@string/send_bug_report_rage_shake" />
|
|
||||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in a new issue