forked from GitHub-Mirror/riotX-android
Crypto
This commit is contained in:
committed by
Benoit Marty
parent
6aae943e77
commit
1436667e7d
@ -174,6 +174,12 @@ dependencies {
|
||||
implementation "ru.noties.markwon:core:$markwon_version"
|
||||
implementation "ru.noties.markwon:html:$markwon_version"
|
||||
|
||||
// Passphrase strength helper
|
||||
implementation 'com.nulab-inc:zxcvbn:1.2.5'
|
||||
|
||||
//Alerter
|
||||
implementation 'com.tapadoo.android:alerter:3.0.2'
|
||||
|
||||
implementation 'com.otaliastudios:autocomplete:1.1.0'
|
||||
|
||||
// Butterknife
|
||||
|
@ -27,7 +27,7 @@ class TestTokenRegistration(val fragment: Fragment) : TroubleshootTest(R.string.
|
||||
override fun perform() {
|
||||
/*
|
||||
TODO
|
||||
Matrix.getInstance(VectorApp.getInstance().baseContext).pushManager.forceSessionsRegistration(object : ApiCallback<Void> {
|
||||
Matrix.getInstance(VectorApp.getInstance().baseContext).pushManager.forceSessionsRegistration(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_success)
|
||||
status = TestStatus.SUCCESS
|
||||
|
@ -38,6 +38,18 @@
|
||||
android:label="@string/title_activity_settings"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity android:name=".features.media.VideoMediaViewerActivity" />
|
||||
<activity
|
||||
android:name=".features.crypto.verification.SASVerificationActivity"
|
||||
android:label="@string/title_activity_verify_device" />
|
||||
<activity
|
||||
android:name="im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreActivity"
|
||||
android:label="@string/title_activity_keys_backup_setup" />
|
||||
<activity
|
||||
android:name="im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity"
|
||||
android:label="@string/title_activity_keys_backup_restore" />
|
||||
<activity
|
||||
android:name="im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity"
|
||||
android:label="@string/encryption_message_recovery" />
|
||||
|
||||
<activity
|
||||
android:name="im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity"
|
||||
|
@ -28,8 +28,10 @@ import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotredesign.core.di.AppModule
|
||||
import im.vector.riotredesign.features.home.HomeModule
|
||||
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||
import im.vector.riotredesign.features.rageshake.VectorFileLogger
|
||||
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
|
||||
import org.koin.android.logger.AndroidLogger
|
||||
import org.koin.log.EmptyLogger
|
||||
import org.koin.standalone.StandAloneContext.startKoin
|
||||
import timber.log.Timber
|
||||
@ -37,9 +39,13 @@ import timber.log.Timber
|
||||
|
||||
class VectorApplication : Application() {
|
||||
|
||||
lateinit var appContext: Context
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
appContext = this
|
||||
|
||||
VectorUncaughtExceptionHandler.activate(this)
|
||||
|
||||
// Log
|
||||
@ -56,9 +62,13 @@ class VectorApplication : Application() {
|
||||
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
val appModule = AppModule(applicationContext).definition
|
||||
val homeModule = HomeModule().definition
|
||||
startKoin(listOf(appModule, homeModule), logger = EmptyLogger())
|
||||
startKoin(
|
||||
list = listOf(appModule, homeModule),
|
||||
logger = if (BuildConfig.DEBUG) AndroidLogger() else EmptyLogger())
|
||||
|
||||
Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)
|
||||
|
||||
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
|
@ -22,6 +22,7 @@ import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotredesign.core.resources.ColorProvider
|
||||
import im.vector.riotredesign.core.resources.LocaleProvider
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
import im.vector.riotredesign.features.home.group.SelectedGroupStore
|
||||
import im.vector.riotredesign.features.home.room.VisibleRoomStore
|
||||
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
|
||||
@ -73,6 +74,10 @@ class AppModule(private val context: Context) {
|
||||
Matrix.getInstance().currentSession!!
|
||||
}
|
||||
|
||||
single {
|
||||
IncomingVerificationRequestHandler(get(), get(), get())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.riotredesign.core.platform
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.BindView
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.hideKeyboard
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
/**
|
||||
* Simple activity with a toolbar, a waiting overlay, and a fragment container and a mxSession.
|
||||
*/
|
||||
abstract class SimpleFragmentActivity : VectorBaseActivity() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.activity
|
||||
|
||||
@BindView(R.id.waiting_view_status_circular_progress)
|
||||
lateinit var waitingCircularProgress: View
|
||||
|
||||
@BindView(R.id.waiting_view_status_text)
|
||||
lateinit var waitingStatusText: TextView
|
||||
|
||||
@BindView(R.id.waiting_view_status_horizontal_progress)
|
||||
lateinit var waitingHorizontalProgress: ProgressBar
|
||||
|
||||
protected val mSession = get<Session>()
|
||||
|
||||
override fun initUiAndData() {
|
||||
configureToolbar()
|
||||
waitingView = findViewById(R.id.waiting_view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a progress indicator with a message to the user.
|
||||
* Blocks user interactions.
|
||||
*/
|
||||
fun updateWaitingView(data: WaitingViewData?) {
|
||||
data?.let {
|
||||
waitingStatusText.text = data.message
|
||||
|
||||
if (data.progress != null && data.progressTotal != null) {
|
||||
waitingHorizontalProgress.isIndeterminate = false
|
||||
waitingHorizontalProgress.progress = data.progress
|
||||
waitingHorizontalProgress.max = data.progressTotal
|
||||
waitingHorizontalProgress.isVisible = true
|
||||
waitingCircularProgress.isVisible = false
|
||||
} else if (data.isIndeterminate) {
|
||||
waitingHorizontalProgress.isIndeterminate = true
|
||||
waitingHorizontalProgress.isVisible = true
|
||||
waitingCircularProgress.isVisible = false
|
||||
} else {
|
||||
waitingHorizontalProgress.isVisible = false
|
||||
waitingCircularProgress.isVisible = true
|
||||
}
|
||||
|
||||
showWaitingView()
|
||||
} ?: run {
|
||||
hideWaitingView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showWaitingView() {
|
||||
hideKeyboard()
|
||||
waitingStatusText.isGone = waitingStatusText.text.isNullOrBlank()
|
||||
super.showWaitingView()
|
||||
}
|
||||
|
||||
override fun hideWaitingView() {
|
||||
waitingStatusText.text = null
|
||||
waitingStatusText.isGone = true
|
||||
waitingHorizontalProgress.progress = 0
|
||||
waitingHorizontalProgress.isVisible = false
|
||||
super.hideWaitingView()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (waitingView!!.isVisible) {
|
||||
// ignore
|
||||
return
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import android.view.View
|
||||
import androidx.annotation.*
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import butterknife.Unbinder
|
||||
@ -254,6 +255,39 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================================
|
||||
// Handle loading view (also called waiting view or spinner view)
|
||||
//==============================================================================================
|
||||
|
||||
var waitingView: View? = null
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
// Ensure this view is clickable to catch UI events
|
||||
value?.isClickable = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the waiting view is currently displayed
|
||||
*
|
||||
* @return true if the waiting view is displayed
|
||||
*/
|
||||
fun isWaitingViewVisible() = waitingView?.isVisible == true
|
||||
|
||||
/**
|
||||
* Show the waiting view
|
||||
*/
|
||||
open fun showWaitingView() {
|
||||
waitingView?.isVisible = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the waiting view
|
||||
*/
|
||||
open fun hideWaitingView() {
|
||||
waitingView?.isVisible = false
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* OPEN METHODS
|
||||
* ========================================================================================== */
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.riotredesign.core.platform
|
||||
|
||||
/**
|
||||
* Model to display a Waiting View
|
||||
*/
|
||||
data class WaitingViewData(
|
||||
val message: String,
|
||||
val progress: Int? = null,
|
||||
val progressTotal: Int? = null,
|
||||
val isIndeterminate: Boolean = false
|
||||
)
|
@ -133,7 +133,7 @@ open class VectorPreference : Preference {
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Timber.e(LOG_TAG, "onBindView " + e.message, e)
|
||||
Timber.e("onBindView " + e.message, e)
|
||||
}
|
||||
|
||||
super.onBindViewHolder(holder)
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.riotredesign.core.resources
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.webkit.MimeTypeMap
|
||||
import im.vector.riotredesign.core.utils.getFileExtension
|
||||
import timber.log.Timber
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Mime types
|
||||
*/
|
||||
const val MIME_TYPE_JPEG = "image/jpeg"
|
||||
const val MIME_TYPE_JPG = "image/jpg"
|
||||
const val MIME_TYPE_IMAGE_ALL = "image/*"
|
||||
const val MIME_TYPE_ALL_CONTENT = "*/*"
|
||||
|
||||
data class Resource(
|
||||
var mContentStream: InputStream? = null,
|
||||
var mMimeType: String? = null
|
||||
) {
|
||||
/**
|
||||
* Close the content stream.
|
||||
*/
|
||||
fun close() {
|
||||
try {
|
||||
mMimeType = null
|
||||
|
||||
mContentStream?.close()
|
||||
mContentStream = null
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Resource.close failed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the opened resource is a jpeg one.
|
||||
*
|
||||
* @return true if the opened resource is a jpeg one.
|
||||
*/
|
||||
fun isJpegResource(): Boolean {
|
||||
return MIME_TYPE_JPEG == mMimeType || MIME_TYPE_JPG == mMimeType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource stream and metadata about it given its URI returned from onActivityResult.
|
||||
*
|
||||
* @param context the context.
|
||||
* @param uri the URI
|
||||
* @param mimetype the mimetype
|
||||
* @return a [Resource] encapsulating the opened resource stream and associated metadata
|
||||
* or `null` if opening the resource stream failed.
|
||||
*/
|
||||
fun openResource(context: Context, uri: Uri, mimetype: String?): Resource? {
|
||||
var mimetype = mimetype
|
||||
try {
|
||||
// if the mime type is not provided, try to find it out
|
||||
if (TextUtils.isEmpty(mimetype)) {
|
||||
mimetype = context.contentResolver.getType(uri)
|
||||
|
||||
// try to find the mimetype from the filename
|
||||
if (null == mimetype) {
|
||||
val extension = getFileExtension(uri.toString())
|
||||
if (extension != null) {
|
||||
mimetype = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Resource(
|
||||
context.contentResolver.openInputStream(uri),
|
||||
mimetype)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to open resource input stream")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.riotredesign.core.ui.list
|
||||
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
/**
|
||||
* View Holder for generic list items.
|
||||
* Displays an item with a title, and optional description.
|
||||
* Can display an accessory on the right, that can be an image or an indeterminate progress.
|
||||
* If provided with an action, will display a button at the bottom of the list item.
|
||||
*/
|
||||
class GenericItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
companion object {
|
||||
@LayoutRes
|
||||
const val resId = R.layout.item_generic_list
|
||||
}
|
||||
|
||||
@BindView(R.id.item_generic_title_text)
|
||||
lateinit var titleText: TextView
|
||||
|
||||
@BindView(R.id.item_generic_description_text)
|
||||
lateinit var descriptionText: TextView
|
||||
|
||||
@BindView(R.id.item_generic_accessory_image)
|
||||
lateinit var accessoryImage: ImageView
|
||||
|
||||
@BindView(R.id.item_generic_progress_bar)
|
||||
lateinit var progressBar: ProgressBar
|
||||
|
||||
@BindView(R.id.item_generic_action_button)
|
||||
lateinit var actionButton: Button
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
}
|
||||
|
||||
fun bind(item: GenericRecyclerViewItem) {
|
||||
titleText.text = item.title
|
||||
|
||||
when (item.style) {
|
||||
GenericRecyclerViewItem.STYLE.BIG_TEXT -> titleText.textSize = 18f
|
||||
GenericRecyclerViewItem.STYLE.NORMAL_TEXT -> titleText.textSize = 14f
|
||||
}
|
||||
|
||||
item.description?.let {
|
||||
descriptionText.isVisible = true
|
||||
descriptionText.text = it
|
||||
} ?: run { descriptionText.isVisible = false }
|
||||
|
||||
if (item.hasIndeterminateProcess) {
|
||||
progressBar.isVisible = true
|
||||
accessoryImage.isVisible = false
|
||||
} else {
|
||||
progressBar.isVisible = false
|
||||
if (item.endIconResourceId != -1) {
|
||||
accessoryImage.setImageResource(item.endIconResourceId)
|
||||
accessoryImage.isVisible = true
|
||||
} else {
|
||||
accessoryImage.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
val buttonAction = item.buttonAction
|
||||
|
||||
if (buttonAction == null) {
|
||||
actionButton.isVisible = false
|
||||
} else {
|
||||
actionButton.text = buttonAction.title
|
||||
actionButton.setOnClickListener {
|
||||
buttonAction.perform?.run()
|
||||
}
|
||||
actionButton.isVisible = true
|
||||
}
|
||||
|
||||
itemView?.setOnClickListener {
|
||||
item.itemClickAction?.perform?.run()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.riotredesign.core.ui.list
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
|
||||
/**
|
||||
* A generic list item.
|
||||
* Displays an item with a title, and optional description.
|
||||
* Can display an accessory on the right, that can be an image or an indeterminate progress.
|
||||
* If provided with an action, will display a button at the bottom of the list item.
|
||||
*/
|
||||
class GenericRecyclerViewItem(val title: String,
|
||||
var description: String? = null,
|
||||
val style: STYLE = STYLE.NORMAL_TEXT) {
|
||||
|
||||
enum class STYLE {
|
||||
BIG_TEXT,
|
||||
NORMAL_TEXT
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
var endIconResourceId: Int = -1
|
||||
|
||||
var hasIndeterminateProcess = false
|
||||
|
||||
var buttonAction: Action? = null
|
||||
|
||||
var itemClickAction: Action? = null
|
||||
|
||||
class Action(var title: String) {
|
||||
var perform: Runnable? = null
|
||||
}
|
||||
}
|
293
vector/src/main/java/im/vector/riotredesign/core/ui/views/KeysBackupBanner.kt
Executable file
293
vector/src/main/java/im/vector/riotredesign/core/ui/views/KeysBackupBanner.kt
Executable file
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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.riotredesign.core.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AbsListView
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import butterknife.OnClick
|
||||
import im.vector.riotredesign.R
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* The view used in VectorHomeActivity to show some information about the keys backup state
|
||||
* It does have a unique render method
|
||||
*/
|
||||
class KeysBackupBanner @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener {
|
||||
|
||||
@BindView(R.id.view_keys_backup_banner_text_1)
|
||||
lateinit var textView1: TextView
|
||||
|
||||
@BindView(R.id.view_keys_backup_banner_text_2)
|
||||
lateinit var textView2: TextView
|
||||
|
||||
@BindView(R.id.view_keys_backup_banner_close_group)
|
||||
lateinit var close: View
|
||||
|
||||
@BindView(R.id.view_keys_backup_banner_loading)
|
||||
lateinit var loading: View
|
||||
|
||||
var delegate: Delegate? = null
|
||||
private var state: State = State.Initial
|
||||
|
||||
private var scrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
val pendingV = pendingVisibility
|
||||
|
||||
if (pendingV != null) {
|
||||
pendingVisibility = null
|
||||
visibility = pendingV
|
||||
}
|
||||
}
|
||||
|
||||
private var pendingVisibility: Int? = null
|
||||
|
||||
init {
|
||||
setupView()
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods is responsible for rendering the view according to the newState
|
||||
*
|
||||
* @param newState the newState representing the view
|
||||
*/
|
||||
fun render(newState: State, force: Boolean = false) {
|
||||
if (newState == state && !force) {
|
||||
Timber.d("State unchanged")
|
||||
return
|
||||
}
|
||||
Timber.d("Rendering $newState")
|
||||
|
||||
state = newState
|
||||
|
||||
hideAll()
|
||||
|
||||
when (newState) {
|
||||
State.Initial -> renderInitial()
|
||||
State.Hidden -> renderHidden()
|
||||
is State.Setup -> renderSetup(newState.numberOfKeys)
|
||||
is State.Recover -> renderRecover(newState.version)
|
||||
is State.Update -> renderUpdate(newState.version)
|
||||
State.BackingUp -> renderBackingUp()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setVisibility(visibility: Int) {
|
||||
if (scrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
|
||||
// Wait for scroll state to be idle
|
||||
pendingVisibility = visibility
|
||||
return
|
||||
}
|
||||
|
||||
if (visibility != getVisibility()) {
|
||||
// Schedule animation
|
||||
val parent = parent as ViewGroup
|
||||
TransitionManager.beginDelayedTransition(parent)
|
||||
}
|
||||
|
||||
super.setVisibility(visibility)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
when (state) {
|
||||
is State.Setup -> {
|
||||
delegate?.setupKeysBackup()
|
||||
}
|
||||
is State.Recover -> {
|
||||
delegate?.recoverKeysBackup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.view_keys_backup_banner_close)
|
||||
internal fun onCloseClicked() {
|
||||
state.let {
|
||||
when (it) {
|
||||
is State.Setup -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, true)
|
||||
}
|
||||
}
|
||||
is State.Recover -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, it.version)
|
||||
}
|
||||
}
|
||||
is State.Update -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, it.version)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Should not happen, close button is not displayed in other cases
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force refresh
|
||||
render(state, true)
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************************************************************************
|
||||
|
||||
private fun setupView() {
|
||||
inflate(context, R.layout.view_keys_backup_banner, this)
|
||||
ButterKnife.bind(this)
|
||||
|
||||
setOnClickListener(this)
|
||||
}
|
||||
|
||||
private fun renderInitial() {
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
private fun renderHidden() {
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
private fun renderSetup(nbOfKeys: Int) {
|
||||
if (nbOfKeys == 0
|
||||
|| PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)) {
|
||||
// Do not display the setup banner if there is no keys to backup, or if the user has already closed it
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
|
||||
textView1.setText(R.string.keys_backup_banner_setup_line1)
|
||||
textView2.isVisible = true
|
||||
textView2.setText(R.string.keys_backup_banner_setup_line2)
|
||||
close.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderRecover(version: String) {
|
||||
if (version == PreferenceManager.getDefaultSharedPreferences(context).getString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
|
||||
textView1.setText(R.string.keys_backup_banner_recover_line1)
|
||||
textView2.isVisible = true
|
||||
textView2.setText(R.string.keys_backup_banner_recover_line2)
|
||||
close.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderUpdate(version: String) {
|
||||
if (version == PreferenceManager.getDefaultSharedPreferences(context).getString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
|
||||
textView1.setText(R.string.keys_backup_banner_update_line1)
|
||||
textView2.isVisible = true
|
||||
textView2.setText(R.string.keys_backup_banner_update_line2)
|
||||
close.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderBackingUp() {
|
||||
isVisible = true
|
||||
|
||||
textView1.setText(R.string.keys_backup_banner_in_progress)
|
||||
loading.isVisible = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide all views that are not visible in all state
|
||||
*/
|
||||
private fun hideAll() {
|
||||
textView2.isVisible = false
|
||||
close.isVisible = false
|
||||
loading.isVisible = false
|
||||
}
|
||||
|
||||
/**
|
||||
* The state representing the view
|
||||
* It can take one state at a time
|
||||
*/
|
||||
sealed class State {
|
||||
// Not yet rendered
|
||||
object Initial : State()
|
||||
|
||||
// View will be Gone
|
||||
object Hidden : State()
|
||||
|
||||
// Keys backup is not setup, numberOfKeys is the number of locally stored keys
|
||||
data class Setup(val numberOfKeys: Int) : State()
|
||||
|
||||
// Keys backup can be recovered, with version from the server
|
||||
data class Recover(val version: String) : State()
|
||||
|
||||
// Keys backup can be updated
|
||||
data class Update(val version: String) : State()
|
||||
|
||||
// Keys are backing up
|
||||
object BackingUp : State()
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface to delegate some actions to another object
|
||||
*/
|
||||
interface Delegate {
|
||||
fun setupKeysBackup()
|
||||
fun recoverKeysBackup()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Preference key for setup. Value is a boolean.
|
||||
*/
|
||||
private const val BANNER_SETUP_DO_NOT_SHOW_AGAIN = "BANNER_SETUP_DO_NOT_SHOW_AGAIN"
|
||||
|
||||
/**
|
||||
* Preference key for recover. Value is a backup version (String).
|
||||
*/
|
||||
private const val BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION = "BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION"
|
||||
|
||||
/**
|
||||
* Preference key for update. Value is a backup version (String).
|
||||
*/
|
||||
private const val BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION = "BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION"
|
||||
|
||||
/**
|
||||
* Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version
|
||||
*/
|
||||
fun onRecoverDoneForVersion(context: Context, version: String) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.riotredesign.core.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.IntRange
|
||||
import butterknife.BindColor
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
/**
|
||||
* A password strength bar custom widget
|
||||
* Strength is an Integer
|
||||
* -> 0 No strength
|
||||
* -> 1 Weak
|
||||
* -> 2 Fair
|
||||
* -> 3 Good
|
||||
* -> 4 Strong
|
||||
*/
|
||||
class PasswordStrengthBar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
@BindView(R.id.password_strength_bar_1)
|
||||
lateinit var bar1: View
|
||||
|
||||
@BindView(R.id.password_strength_bar_2)
|
||||
lateinit var bar2: View
|
||||
|
||||
@BindView(R.id.password_strength_bar_3)
|
||||
lateinit var bar3: View
|
||||
|
||||
@BindView(R.id.password_strength_bar_4)
|
||||
lateinit var bar4: View
|
||||
|
||||
|
||||
@BindColor(R.color.password_strength_bar_undefined)
|
||||
@JvmField
|
||||
var colorBackground: Int = 0
|
||||
@BindColor(R.color.password_strength_bar_weak)
|
||||
@JvmField
|
||||
var colorWeak: Int = 0
|
||||
@BindColor(R.color.password_strength_bar_low)
|
||||
@JvmField
|
||||
var colorLow: Int = 0
|
||||
@BindColor(R.color.password_strength_bar_ok)
|
||||
@JvmField
|
||||
var colorOk: Int = 0
|
||||
@BindColor(R.color.password_strength_bar_strong)
|
||||
@JvmField
|
||||
var colorStrong: Int = 0
|
||||
|
||||
@IntRange(from = 0, to = 4)
|
||||
var strength = 0
|
||||
set(newValue) {
|
||||
field = newValue.coerceIn(0, 4)
|
||||
|
||||
when (newValue) {
|
||||
0 -> {
|
||||
bar1.setBackgroundColor(colorBackground)
|
||||
bar2.setBackgroundColor(colorBackground)
|
||||
bar3.setBackgroundColor(colorBackground)
|
||||
bar4.setBackgroundColor(colorBackground)
|
||||
}
|
||||
1 -> {
|
||||
bar1.setBackgroundColor(colorWeak)
|
||||
bar2.setBackgroundColor(colorBackground)
|
||||
bar3.setBackgroundColor(colorBackground)
|
||||
bar4.setBackgroundColor(colorBackground)
|
||||
}
|
||||
2 -> {
|
||||
bar1.setBackgroundColor(colorLow)
|
||||
bar2.setBackgroundColor(colorLow)
|
||||
bar3.setBackgroundColor(colorBackground)
|
||||
bar4.setBackgroundColor(colorBackground)
|
||||
}
|
||||
3 -> {
|
||||
bar1.setBackgroundColor(colorOk)
|
||||
bar2.setBackgroundColor(colorOk)
|
||||
bar3.setBackgroundColor(colorOk)
|
||||
bar4.setBackgroundColor(colorBackground)
|
||||
}
|
||||
4 -> {
|
||||
bar1.setBackgroundColor(colorStrong)
|
||||
bar2.setBackgroundColor(colorStrong)
|
||||
bar3.setBackgroundColor(colorStrong)
|
||||
bar4.setBackgroundColor(colorStrong)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
LayoutInflater.from(context)
|
||||
.inflate(R.layout.view_password_strength_bar, this, true)
|
||||
orientation = HORIZONTAL
|
||||
ButterKnife.bind(this)
|
||||
strength = 0
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
package im.vector.riotredesign.core.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@ -87,3 +88,46 @@ private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean {
|
||||
return action.invoke(file)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the file extension of a fileUri or a filename
|
||||
*
|
||||
* @param fileUri the fileUri (can be a simple filename)
|
||||
* @return the file extension, in lower case, or null is extension is not available or empty
|
||||
*/
|
||||
fun getFileExtension(fileUri: String): String? {
|
||||
var reducedStr = fileUri
|
||||
|
||||
if (!TextUtils.isEmpty(reducedStr)) {
|
||||
// Remove fragment
|
||||
val fragment = fileUri.lastIndexOf('#')
|
||||
if (fragment > 0) {
|
||||
reducedStr = fileUri.substring(0, fragment)
|
||||
}
|
||||
|
||||
// Remove query
|
||||
val query = reducedStr.lastIndexOf('?')
|
||||
if (query > 0) {
|
||||
reducedStr = reducedStr.substring(0, query)
|
||||
}
|
||||
|
||||
// Remove path
|
||||
val filenamePos = reducedStr.lastIndexOf('/')
|
||||
val filename = if (0 <= filenamePos) reducedStr.substring(filenamePos + 1) else reducedStr
|
||||
|
||||
// Contrary to method MimeTypeMap.getFileExtensionFromUrl, we do not check the pattern
|
||||
// See https://stackoverflow.com/questions/14320527/android-should-i-use-mimetypemap-getfileextensionfromurl-bugs
|
||||
if (!filename.isEmpty()) {
|
||||
val dotPos = filename.lastIndexOf('.')
|
||||
if (0 <= dotPos) {
|
||||
val ext = filename.substring(dotPos + 1)
|
||||
|
||||
if (ext.isNotBlank()) {
|
||||
return ext.toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
@ -269,9 +269,10 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
val permissionsArrayToBeGranted = permissionsListToBeGranted.toTypedArray()
|
||||
|
||||
// for android < M, we use a custom dialog to request the contacts book access.
|
||||
/*
|
||||
if (permissionsListToBeGranted.contains(Manifest.permission.READ_CONTACTS)
|
||||
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
TODO()
|
||||
/*
|
||||
AlertDialog.Builder(activity)
|
||||
.setIcon(android.R.drawable.ic_dialog_info)
|
||||
.setTitle(R.string.permissions_rationale_popup_title)
|
||||
@ -293,13 +294,13 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
}
|
||||
}
|
||||
.show()
|
||||
*/
|
||||
} else {
|
||||
fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
|
||||
}
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
// permissions were granted, start now.
|
||||
isPermissionGranted = true
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.restore
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment
|
||||
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
||||
|
||||
class KeysBackupRestoreActivity : SimpleFragmentActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun intent(context: Context): Intent {
|
||||
return Intent(context, KeysBackupRestoreActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTitleRes() = R.string.title_activity_keys_backup_restore
|
||||
|
||||
private lateinit var viewModel: KeysBackupRestoreSharedViewModel
|
||||
|
||||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
viewModel = ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java)
|
||||
viewModel.initSession(mSession)
|
||||
|
||||
viewModel.keyVersionResult.observe(this, Observer { keyVersion ->
|
||||
|
||||
if (keyVersion != null && supportFragmentManager.fragments.isEmpty()) {
|
||||
val isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData().privateKeySalt != null
|
||||
if (isBackupCreatedFromPassphrase) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupRestoreFromPassphraseFragment.newInstance())
|
||||
.commitNow()
|
||||
} else {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupRestoreFromKeyFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.keyVersionResultError.observe(this, Observer { uxStateEvent ->
|
||||
uxStateEvent?.getContentIfNotHandled()?.let {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unknown_error)
|
||||
.setMessage(it)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
//nop
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
})
|
||||
|
||||
if (viewModel.keyVersionResult.value == null) {
|
||||
//We need to fetch from API
|
||||
viewModel.getLatestVersion(this)
|
||||
}
|
||||
|
||||
viewModel.navigateEvent.observe(this, Observer { uxStateEvent ->
|
||||
when (uxStateEvent?.getContentIfNotHandled()) {
|
||||
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupRestoreFromKeyFragment.newInstance())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_SUCCESS -> {
|
||||
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupRestoreSuccessFragment.newInstance())
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.loadingEvent.observe(this, Observer {
|
||||
updateWaitingView(it)
|
||||
})
|
||||
|
||||
viewModel.importRoomKeysFinishWithResult.observe(this, Observer {
|
||||
it?.getContentIfNotHandled()?.let {
|
||||
//set data?
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.restore
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import butterknife.OnTextChanged
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.core.utils.startImportTextFromFileIntent
|
||||
import timber.log.Timber
|
||||
|
||||
class KeysBackupRestoreFromKeyFragment : VectorBaseFragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupRestoreFromKeyFragment()
|
||||
|
||||
private const val REQUEST_TEXT_FILE_GET = 1
|
||||
}
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_key
|
||||
|
||||
private lateinit var viewModel: KeysBackupRestoreFromKeyViewModel
|
||||
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
|
||||
|
||||
@BindView(R.id.keys_backup_key_enter_til)
|
||||
lateinit var mKeyInputLayout: TextInputLayout
|
||||
@BindView(R.id.keys_restore_key_enter_edittext)
|
||||
lateinit var mKeyTextEdit: EditText
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(this).get(KeysBackupRestoreFromKeyViewModel::class.java)
|
||||
sharedViewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
mKeyTextEdit.setText(viewModel.recoveryCode.value)
|
||||
mKeyTextEdit.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
onRestoreFromKey()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
mKeyInputLayout.error = viewModel.recoveryCodeErrorText.value
|
||||
viewModel.recoveryCodeErrorText.observe(this, Observer { newValue ->
|
||||
mKeyInputLayout.error = newValue
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@OnTextChanged(R.id.keys_restore_key_enter_edittext)
|
||||
fun onRestoreKeyTextEditChange(s: Editable?) {
|
||||
s?.toString()?.let {
|
||||
viewModel.updateCode(it)
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_restore_button)
|
||||
fun onRestoreFromKey() {
|
||||
val value = viewModel.recoveryCode.value
|
||||
if (value.isNullOrBlank()) {
|
||||
viewModel.recoveryCodeErrorText.value = context?.getString(R.string.keys_backup_recovery_code_empty_error_message)
|
||||
} else {
|
||||
viewModel.recoverKeys(context!!, sharedViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_import)
|
||||
fun onImport() {
|
||||
startImportTextFromFileIntent(this, REQUEST_TEXT_FILE_GET)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == REQUEST_TEXT_FILE_GET && resultCode == Activity.RESULT_OK) {
|
||||
val dataURI = data?.data
|
||||
if (dataURI != null) {
|
||||
try {
|
||||
activity
|
||||
?.contentResolver
|
||||
?.openInputStream(dataURI)
|
||||
?.bufferedReader()
|
||||
?.use { it.readText() }
|
||||
?.let {
|
||||
mKeyTextEdit.setText(it)
|
||||
mKeyTextEdit.setSelection(it.length)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to read recovery kay from text")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.restore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.StepProgressListener
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.core.ui.views.KeysBackupBanner
|
||||
import timber.log.Timber
|
||||
|
||||
class KeysBackupRestoreFromKeyViewModel : ViewModel() {
|
||||
|
||||
var recoveryCode: MutableLiveData<String> = MutableLiveData()
|
||||
var recoveryCodeErrorText: MutableLiveData<String> = MutableLiveData()
|
||||
|
||||
init {
|
||||
recoveryCode.value = null
|
||||
recoveryCodeErrorText.value = null
|
||||
}
|
||||
|
||||
//========= Actions =========
|
||||
fun updateCode(newValue: String) {
|
||||
recoveryCode.value = newValue
|
||||
recoveryCodeErrorText.value = null
|
||||
}
|
||||
|
||||
fun recoverKeys(context: Context, sharedViewModel: KeysBackupRestoreSharedViewModel) {
|
||||
val session = sharedViewModel.session
|
||||
val keysBackup = session.getKeysBackupService()
|
||||
|
||||
recoveryCodeErrorText.value = null
|
||||
val recoveryKey = recoveryCode.value!!
|
||||
|
||||
val keysVersionResult = sharedViewModel.keyVersionResult.value!!
|
||||
|
||||
keysBackup.restoreKeysWithRecoveryKey(keysVersionResult,
|
||||
recoveryKey,
|
||||
null,
|
||||
session.sessionParams.credentials.userId,
|
||||
object : StepProgressListener {
|
||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||
when (step) {
|
||||
is StepProgressListener.Step.DownloadingKey -> {
|
||||
sharedViewModel.loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
+ "\n" + context.getString(R.string.keys_backup_restoring_downloading_backup_waiting_message),
|
||||
isIndeterminate = true)
|
||||
}
|
||||
is StepProgressListener.Step.ImportingKey -> {
|
||||
// Progress 0 can take a while, display an indeterminate progress in this case
|
||||
if (step.progress == 0) {
|
||||
sharedViewModel.loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
+ "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message),
|
||||
isIndeterminate = true)
|
||||
|
||||
} else {
|
||||
sharedViewModel.loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
+ "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message),
|
||||
step.progress,
|
||||
step.total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
object : MatrixCallback<ImportRoomKeysResult> {
|
||||
override fun onSuccess(info: ImportRoomKeysResult) {
|
||||
sharedViewModel.loadingEvent.value = null
|
||||
sharedViewModel.didRecoverSucceed(info)
|
||||
|
||||
KeysBackupBanner.onRecoverDoneForVersion(context, keysVersionResult.version!!)
|
||||
trustOnDecrypt(keysBackup, keysVersionResult)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
sharedViewModel.loadingEvent.value = null
|
||||
recoveryCodeErrorText.value = context.getString(R.string.keys_backup_recovery_code_error_decrypt)
|
||||
Timber.e(failure, "## onUnexpectedError")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) {
|
||||
keysBackup.trustKeysBackupVersion(keysVersionResult, true,
|
||||
object : MatrixCallback<Unit> {
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.d("##### trustKeysBackupVersion onSuccess")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.fragments.keysbackup.restore
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.set
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import butterknife.OnTextChanged
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.showPassword
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||
|
||||
class KeysBackupRestoreFromPassphraseFragment : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_passphrase
|
||||
|
||||
private lateinit var viewModel: KeysBackupRestoreFromPassphraseViewModel
|
||||
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_enter_til)
|
||||
lateinit var mPassphraseInputLayout: TextInputLayout
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_enter_edittext)
|
||||
lateinit var mPassphraseTextEdit: EditText
|
||||
|
||||
@BindView(R.id.keys_backup_view_show_password)
|
||||
lateinit var mPassphraseReveal: ImageView
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_help_with_link)
|
||||
lateinit var helperTextWithLink: TextView
|
||||
|
||||
@OnClick(R.id.keys_backup_view_show_password)
|
||||
fun toggleVisibilityMode() {
|
||||
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupRestoreFromPassphraseFragment()
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(KeysBackupRestoreFromPassphraseViewModel::class.java)
|
||||
sharedViewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
|
||||
viewModel.passphraseErrorText.observe(this, Observer { newValue ->
|
||||
mPassphraseInputLayout.error = newValue
|
||||
})
|
||||
|
||||
helperTextWithLink.text = spannableStringForHelperText(context!!)
|
||||
|
||||
viewModel.showPasswordMode.observe(this, Observer {
|
||||
val shouldBeVisible = it ?: false
|
||||
mPassphraseTextEdit.showPassword(shouldBeVisible)
|
||||
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
|
||||
})
|
||||
|
||||
mPassphraseTextEdit.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
onRestoreBackup()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun spannableStringForHelperText(context: Context): SpannableString {
|
||||
val clickableText = context.getString(R.string.keys_backup_restore_use_recovery_key)
|
||||
val helperText = context.getString(R.string.keys_backup_restore_with_passphrase_helper_with_link, clickableText)
|
||||
|
||||
val spanString = SpannableString(helperText)
|
||||
|
||||
// used just to have default link representation
|
||||
val clickableSpan = object : ClickableSpan() {
|
||||
override fun onClick(widget: View?) {}
|
||||
}
|
||||
val start = helperText.indexOf(clickableText)
|
||||
val end = start + clickableText.length
|
||||
spanString[start, end] = clickableSpan
|
||||
return spanString
|
||||
}
|
||||
|
||||
@OnTextChanged(R.id.keys_backup_passphrase_enter_edittext)
|
||||
fun onPassphraseTextEditChange(s: Editable?) {
|
||||
s?.toString()?.let { viewModel.updatePassphrase(it) }
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_passphrase_help_with_link)
|
||||
fun onUseRecoveryKey() {
|
||||
sharedViewModel.moveToRecoverWithKey()
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_restore_with_passphrase_submit)
|
||||
fun onRestoreBackup() {
|
||||
val value = viewModel.passphrase.value
|
||||
if (value.isNullOrBlank()) {
|
||||
viewModel.passphraseErrorText.value = context?.getString(R.string.passphrase_empty_error_message)
|
||||
} else {
|
||||
viewModel.recoverKeys(context!!, sharedViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.restore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.StepProgressListener
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.core.ui.views.KeysBackupBanner
|
||||
import timber.log.Timber
|
||||
|
||||
class KeysBackupRestoreFromPassphraseViewModel : ViewModel() {
|
||||
|
||||
var passphrase: MutableLiveData<String> = MutableLiveData()
|
||||
var passphraseErrorText: MutableLiveData<String> = MutableLiveData()
|
||||
var showPasswordMode: MutableLiveData<Boolean> = MutableLiveData()
|
||||
|
||||
init {
|
||||
passphrase.value = null
|
||||
passphraseErrorText.value = null
|
||||
showPasswordMode.value = false
|
||||
}
|
||||
|
||||
//========= Actions =========
|
||||
|
||||
fun updatePassphrase(newValue: String) {
|
||||
passphrase.value = newValue
|
||||
passphraseErrorText.value = null
|
||||
}
|
||||
|
||||
fun recoverKeys(context: Context, sharedViewModel: KeysBackupRestoreSharedViewModel) {
|
||||
val keysBackup = sharedViewModel.session.getKeysBackupService()
|
||||
|
||||
passphraseErrorText.value = null
|
||||
|
||||
val keysVersionResult = sharedViewModel.keyVersionResult.value!!
|
||||
|
||||
keysBackup.restoreKeyBackupWithPassword(keysVersionResult,
|
||||
passphrase.value!!,
|
||||
null,
|
||||
sharedViewModel.session.sessionParams.credentials.userId,
|
||||
object : StepProgressListener {
|
||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||
when (step) {
|
||||
is StepProgressListener.Step.ComputingKey -> {
|
||||
sharedViewModel.loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
+ "\n" + context.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
|
||||
step.progress,
|
||||
step.total)
|
||||
}
|
||||
is StepProgressListener.Step.DownloadingKey -> {
|
||||
sharedViewModel.loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
+ "\n" + context.getString(R.string.keys_backup_restoring_downloading_backup_waiting_message),
|
||||
isIndeterminate = true)
|
||||
}
|
||||
is StepProgressListener.Step.ImportingKey -> {
|
||||
// Progress 0 can take a while, display an indeterminate progress in this case
|
||||
if (step.progress == 0) {
|
||||
sharedViewModel.loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
+ "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message),
|
||||
isIndeterminate = true)
|
||||
|
||||
} else {
|
||||
sharedViewModel.loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message)
|
||||
+ "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message),
|
||||
step.progress,
|
||||
step.total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
object : MatrixCallback<ImportRoomKeysResult> {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
sharedViewModel.loadingEvent.value = null
|
||||
sharedViewModel.didRecoverSucceed(data)
|
||||
|
||||
KeysBackupBanner.onRecoverDoneForVersion(context, keysVersionResult.version!!)
|
||||
trustOnDecrypt(keysBackup, keysVersionResult)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
sharedViewModel.loadingEvent.value = null
|
||||
passphraseErrorText.value = context.getString(R.string.keys_backup_passphrase_error_decrypt)
|
||||
Timber.e(failure, "## onUnexpectedError")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) {
|
||||
keysBackup.trustKeysBackupVersion(keysVersionResult, true,
|
||||
object : MatrixCallback<Unit> {
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.d("##### trustKeysBackupVersion onSuccess")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.fragments.keysbackup.restore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
|
||||
class KeysBackupRestoreSharedViewModel : ViewModel() {
|
||||
|
||||
companion object {
|
||||
const val NAVIGATE_TO_RECOVER_WITH_KEY = "NAVIGATE_TO_RECOVER_WITH_KEY"
|
||||
const val NAVIGATE_TO_SUCCESS = "NAVIGATE_TO_SUCCESS"
|
||||
}
|
||||
|
||||
lateinit var session: Session
|
||||
|
||||
var keyVersionResult: MutableLiveData<KeysVersionResult> = MutableLiveData()
|
||||
|
||||
private var _keyVersionResultError: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
val keyVersionResultError: LiveData<LiveEvent<String>>
|
||||
get() = _keyVersionResultError
|
||||
|
||||
|
||||
private var _navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
val navigateEvent: LiveData<LiveEvent<String>>
|
||||
get() = _navigateEvent
|
||||
|
||||
var loadingEvent: MutableLiveData<WaitingViewData> = MutableLiveData()
|
||||
|
||||
|
||||
var importKeyResult: ImportRoomKeysResult? = null
|
||||
var importRoomKeysFinishWithResult: MutableLiveData<LiveEvent<ImportRoomKeysResult>> = MutableLiveData()
|
||||
|
||||
|
||||
init {
|
||||
keyVersionResult.value = null
|
||||
_keyVersionResultError.value = null
|
||||
loadingEvent.value = null
|
||||
}
|
||||
|
||||
fun initSession(session: Session) {
|
||||
this.session = session
|
||||
}
|
||||
|
||||
|
||||
fun getLatestVersion(context: Context) {
|
||||
val keysBackup = session.getKeysBackupService()
|
||||
|
||||
loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restore_is_getting_backup_version))
|
||||
|
||||
keysBackup.getCurrentVersion(object : MatrixCallback<KeysVersionResult?> {
|
||||
override fun onSuccess(data: KeysVersionResult?) {
|
||||
loadingEvent.value = null
|
||||
if (data?.version.isNullOrBlank()) {
|
||||
//should not happen
|
||||
_keyVersionResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, ""))
|
||||
} else {
|
||||
keyVersionResult.value = data
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
loadingEvent.value = null
|
||||
_keyVersionResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))
|
||||
|
||||
// TODO For network error
|
||||
// _keyVersionResultError.value = LiveEvent(context.getString(R.string.network_error_please_check_and_retry))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun moveToRecoverWithKey() {
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_TO_RECOVER_WITH_KEY)
|
||||
}
|
||||
|
||||
fun didRecoverSucceed(result: ImportRoomKeysResult) {
|
||||
importKeyResult = result
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_TO_SUCCESS)
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.restore
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
|
||||
class KeysBackupRestoreSuccessFragment : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_success
|
||||
|
||||
@BindView(R.id.keys_backup_restore_success)
|
||||
lateinit var mSuccessText: TextView
|
||||
@BindView(R.id.keys_backup_restore_success_info)
|
||||
lateinit var mSuccessDetailsText: TextView
|
||||
|
||||
|
||||
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
sharedViewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
sharedViewModel.importKeyResult?.let {
|
||||
val part1 = resources.getQuantityString(R.plurals.keys_backup_restore_success_description_part1,
|
||||
it.totalNumberOfKeys, it.totalNumberOfKeys)
|
||||
val part2 = resources.getQuantityString(R.plurals.keys_backup_restore_success_description_part2,
|
||||
it.successfullyNumberOfImportedKeys, it.successfullyNumberOfImportedKeys)
|
||||
mSuccessDetailsText.text = String.format("%s\n%s", part1, part2)
|
||||
}
|
||||
|
||||
//We don't put emoji in string xml as it will crash on old devices
|
||||
mSuccessText.text = context?.getString(R.string.keys_backup_restore_success_title, "🎉")
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_setup_done_button)
|
||||
fun onDone() {
|
||||
sharedViewModel.importRoomKeysFinishWithResult.value = LiveEvent(sharedViewModel.importKeyResult!!)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupRestoreSuccessFragment()
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import im.vector.fragments.keysbackup.settings.KeysBackupSettingsFragment
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
||||
|
||||
|
||||
class KeysBackupManageActivity : SimpleFragmentActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun intent(context: Context): Intent {
|
||||
val intent = Intent(context, KeysBackupManageActivity::class.java)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTitleRes() = R.string.encryption_message_recovery
|
||||
|
||||
|
||||
private lateinit var viewModel: KeysBackupSettingsViewModel
|
||||
|
||||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
viewModel = ViewModelProviders.of(this).get(KeysBackupSettingsViewModel::class.java)
|
||||
viewModel.initSession(mSession)
|
||||
|
||||
|
||||
if (supportFragmentManager.fragments.isEmpty()) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupSettingsFragment.newInstance())
|
||||
.commitNow()
|
||||
|
||||
mSession.getKeysBackupService()
|
||||
.forceUsingLastVersion(object : MatrixCallback<Boolean> {})
|
||||
}
|
||||
|
||||
viewModel.loadingEvent.observe(this, Observer {
|
||||
updateWaitingView(it)
|
||||
})
|
||||
|
||||
|
||||
viewModel.apiResultError.observe(this, Observer { uxStateEvent ->
|
||||
uxStateEvent?.getContentIfNotHandled()?.let {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unknown_error)
|
||||
.setMessage(it)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.fragments.keysbackup.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import butterknife.BindView
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
||||
|
||||
class KeysBackupSettingsFragment : VectorBaseFragment(),
|
||||
KeysBackupSettingsRecyclerViewAdapter.AdapterListener {
|
||||
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupSettingsFragment()
|
||||
}
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_settings
|
||||
|
||||
private lateinit var viewModel: KeysBackupSettingsViewModel
|
||||
|
||||
@BindView(R.id.keys_backup_settings_recycler_view)
|
||||
lateinit var recyclerView: RecyclerView
|
||||
|
||||
private var recyclerViewAdapter: KeysBackupSettingsRecyclerViewAdapter? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
|
||||
recyclerViewAdapter = KeysBackupSettingsRecyclerViewAdapter(activity!!)
|
||||
recyclerView.adapter = recyclerViewAdapter
|
||||
recyclerViewAdapter?.adapterListener = this
|
||||
|
||||
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupSettingsViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
|
||||
viewModel.keyBackupState.observe(this, Observer { keysBackupState ->
|
||||
if (keysBackupState == null) {
|
||||
//Cannot happen?
|
||||
viewModel.keyVersionTrust.value = null
|
||||
} else {
|
||||
when (keysBackupState) {
|
||||
KeysBackupState.Unknown,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver -> {
|
||||
viewModel.loadingEvent.value = WaitingViewData("")
|
||||
}
|
||||
else -> {
|
||||
viewModel.loadingEvent.value = null
|
||||
//All this cases will be manage by looking at the backup trust object
|
||||
viewModel.session?.getKeysBackupService()?.mKeysBackupVersion?.let {
|
||||
viewModel.getKeysBackupTrust(it)
|
||||
} ?: run {
|
||||
viewModel.keyVersionTrust.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the adapter for each state change
|
||||
viewModel.session?.let { session ->
|
||||
recyclerViewAdapter?.updateWithTrust(session, viewModel.keyVersionTrust.value)
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.keyVersionTrust.observe(this, Observer {
|
||||
viewModel.session?.let { session ->
|
||||
recyclerViewAdapter?.updateWithTrust(session, it)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
override fun didSelectSetupMessageRecovery() {
|
||||
context?.let {
|
||||
startActivity(KeysBackupSetupActivity.intent(it, false))
|
||||
}
|
||||
}
|
||||
|
||||
override fun didSelectRestoreMessageRecovery() {
|
||||
context?.let {
|
||||
startActivity(KeysBackupRestoreActivity.intent(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun didSelectDeleteSetupMessageRecovery() {
|
||||
activity?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.keys_backup_settings_delete_confirm_title)
|
||||
.setMessage(R.string.keys_backup_settings_delete_confirm_message)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.keys_backup_settings_delete_confirm_title) { _, _ ->
|
||||
viewModel.deleteCurrentBackup(it)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setCancelable(true)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.fragments.keysbackup.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.ui.list.GenericItemViewHolder
|
||||
import im.vector.riotredesign.core.ui.list.GenericRecyclerViewItem
|
||||
|
||||
class KeysBackupSettingsRecyclerViewAdapter(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
|
||||
private var infoList: List<GenericRecyclerViewItem> = ArrayList()
|
||||
|
||||
private var isBackupAlreadySetup = false
|
||||
|
||||
var adapterListener: AdapterListener? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
GenericItemViewHolder.resId -> GenericItemViewHolder(inflater.inflate(viewType, parent, false))
|
||||
else -> FooterViewHolder(inflater.inflate(viewType, parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position < infoList.size) {
|
||||
GenericItemViewHolder.resId
|
||||
} else {
|
||||
R.layout.item_keys_backup_settings_button_footer
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
if (holder is GenericItemViewHolder) {
|
||||
holder.bind(infoList[position])
|
||||
} else if (holder is FooterViewHolder) {
|
||||
if (isBackupAlreadySetup) {
|
||||
holder.button1.setText(R.string.keys_backup_settings_restore_backup_button)
|
||||
holder.button1.isVisible = true
|
||||
holder.button1.setOnClickListener {
|
||||
adapterListener?.didSelectRestoreMessageRecovery()
|
||||
}
|
||||
|
||||
holder.button2.setText(R.string.keys_backup_settings_delete_backup_button)
|
||||
holder.button2.isVisible = true
|
||||
holder.button2.setOnClickListener {
|
||||
adapterListener?.didSelectDeleteSetupMessageRecovery()
|
||||
}
|
||||
} else {
|
||||
holder.button1.setText(R.string.keys_backup_setup)
|
||||
holder.button1.isVisible = true
|
||||
holder.button1.setOnClickListener {
|
||||
adapterListener?.didSelectSetupMessageRecovery()
|
||||
}
|
||||
|
||||
holder.button2.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return infoList.size + 1 /*footer*/
|
||||
}
|
||||
|
||||
|
||||
fun updateWithTrust(session: Session, keyBackupVersionTrust: KeysBackupVersionTrust?) {
|
||||
val keyBackupState = session.getKeysBackupService().state
|
||||
val keyVersionResult = session.getKeysBackupService().mKeysBackupVersion
|
||||
|
||||
val infos = ArrayList<GenericRecyclerViewItem>()
|
||||
var itemSummary: GenericRecyclerViewItem? = null
|
||||
|
||||
when (keyBackupState) {
|
||||
KeysBackupState.Unknown,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver -> {
|
||||
//In this cases recycler view is hidden any way
|
||||
//so do nothing
|
||||
}
|
||||
KeysBackupState.Disabled -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_not_setup),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT)
|
||||
|
||||
isBackupAlreadySetup = false
|
||||
}
|
||||
KeysBackupState.WrongBackUpVersion,
|
||||
KeysBackupState.NotTrusted,
|
||||
KeysBackupState.Enabling -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_ko),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT).apply {
|
||||
description = keyBackupState.toString()
|
||||
endIconResourceId = R.drawable.unit_test_ko
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
KeysBackupState.ReadyToBackUp -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_ok),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT).apply {
|
||||
endIconResourceId = R.drawable.unit_test_ok
|
||||
description = context.getString(R.string.keys_backup_info_keys_all_backup_up)
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
KeysBackupState.WillBackUp,
|
||||
KeysBackupState.BackingUp -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_ok),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT).apply {
|
||||
hasIndeterminateProcess = true
|
||||
|
||||
val totalKeys = session.inboundGroupSessionsCount(false)
|
||||
?: 0
|
||||
val backedUpKeys = session.inboundGroupSessionsCount(true)
|
||||
?: 0
|
||||
|
||||
val remainingKeysToBackup = totalKeys - backedUpKeys
|
||||
|
||||
description = context.resources.getQuantityString(R.plurals.keys_backup_info_keys_backing_up, remainingKeysToBackup, remainingKeysToBackup)
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
}
|
||||
|
||||
itemSummary?.let {
|
||||
infos.add(it)
|
||||
}
|
||||
|
||||
if (keyBackupVersionTrust != null) {
|
||||
|
||||
if (!keyBackupVersionTrust.usable) {
|
||||
itemSummary?.description = context.getString(R.string.keys_backup_settings_untrusted_backup)
|
||||
}
|
||||
|
||||
//Add infos
|
||||
infos.add(GenericRecyclerViewItem(context.getString(R.string.keys_backup_info_title_version), keyVersionResult?.version
|
||||
?: ""))
|
||||
infos.add(GenericRecyclerViewItem(context.getString(R.string.keys_backup_info_title_algorithm), keyVersionResult?.algorithm
|
||||
?: ""))
|
||||
|
||||
keyBackupVersionTrust.signatures.forEach {
|
||||
val signatureInfo = GenericRecyclerViewItem(context.getString(R.string.keys_backup_info_title_signature))
|
||||
val isDeviceKnown = it.device != null
|
||||
val isDeviceVerified = it.device?.isVerified ?: false
|
||||
val isSignatureValid = it.valid
|
||||
val deviceId: String = it.deviceId ?: ""
|
||||
|
||||
if (!isDeviceKnown) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_warning
|
||||
} else {
|
||||
if (isSignatureValid) {
|
||||
if (session.sessionParams.credentials.deviceId == it.deviceId) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_valid_signature_from_this_device)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_verified
|
||||
} else {
|
||||
if (isDeviceVerified) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_valid_signature_from_verified_device, deviceId)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_verified
|
||||
} else {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_valid_signature_from_unverified_device, deviceId)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_warning
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Invalid signature
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_warning
|
||||
if (isDeviceVerified) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_invalid_signature_from_verified_device, deviceId)
|
||||
} else {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_invalid_signature_from_unverified_device, deviceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infos.add(signatureInfo)
|
||||
} //end for each
|
||||
}
|
||||
|
||||
infoList = infos
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
}
|
||||
|
||||
@BindView(R.id.keys_backup_settings_footer_button1)
|
||||
lateinit var button1: Button
|
||||
|
||||
@BindView(R.id.keys_backup_settings_footer_button2)
|
||||
lateinit var button2: Button
|
||||
|
||||
fun bind() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface AdapterListener {
|
||||
fun didSelectSetupMessageRecovery()
|
||||
fun didSelectRestoreMessageRecovery()
|
||||
fun didSelectDeleteSetupMessageRecovery()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
|
||||
|
||||
class KeysBackupSettingsViewModel : ViewModel(),
|
||||
KeysBackupService.KeysBackupStateListener {
|
||||
|
||||
var session: Session? = null
|
||||
|
||||
var keyVersionTrust: MutableLiveData<KeysBackupVersionTrust> = MutableLiveData()
|
||||
var keyBackupState: MutableLiveData<KeysBackupState> = MutableLiveData()
|
||||
|
||||
private var _apiResultError: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
val apiResultError: LiveData<LiveEvent<String>>
|
||||
get() = _apiResultError
|
||||
|
||||
var loadingEvent: MutableLiveData<WaitingViewData> = MutableLiveData()
|
||||
|
||||
fun initSession(session: Session) {
|
||||
keyBackupState.value = session.getKeysBackupService().state
|
||||
if (this.session == null) {
|
||||
this.session = session
|
||||
session.getKeysBackupService().addListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun getKeysBackupTrust(versionResult: KeysVersionResult) {
|
||||
val keysBackup = session?.getKeysBackupService()
|
||||
keysBackup?.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> {
|
||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
||||
keyVersionTrust.value = data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
session?.getKeysBackupService()?.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
keyBackupState.value = newState
|
||||
}
|
||||
|
||||
fun deleteCurrentBackup(context: Context) {
|
||||
session?.getKeysBackupService()?.run {
|
||||
loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_settings_deleting_backup))
|
||||
if (currentBackupVersion != null) {
|
||||
deleteBackup(currentBackupVersion!!, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Unit) {
|
||||
//mmmm if state is stil unknown/checking..
|
||||
loadingEvent.value = null
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
loadingEvent.value = null
|
||||
_apiResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.setup
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
||||
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
||||
|
||||
class KeysBackupSetupActivity : SimpleFragmentActivity() {
|
||||
|
||||
override fun getTitleRes() = R.string.title_activity_keys_backup_setup
|
||||
|
||||
private lateinit var viewModel: KeysBackupSetupSharedViewModel
|
||||
|
||||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
if (isFirstCreation()) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupSetupStep1Fragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java)
|
||||
viewModel.showManualExport.value = intent.getBooleanExtra(EXTRA_SHOW_MANUAL_EXPORT, false)
|
||||
viewModel.initSession(mSession)
|
||||
|
||||
|
||||
viewModel.isCreatingBackupVersion.observe(this, Observer {
|
||||
val isCreating = it ?: false
|
||||
if (isCreating) {
|
||||
showWaitingView()
|
||||
} else {
|
||||
hideWaitingView()
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.loadingStatus.observe(this, Observer {
|
||||
it?.let {
|
||||
updateWaitingView(it)
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.navigateEvent.observe(this, Observer { uxStateEvent ->
|
||||
when (uxStateEvent?.getContentIfNotHandled()) {
|
||||
KeysBackupSetupSharedViewModel.NAVIGATE_TO_STEP_2 -> {
|
||||
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupSetupStep2Fragment.newInstance())
|
||||
.commit()
|
||||
}
|
||||
KeysBackupSetupSharedViewModel.NAVIGATE_TO_STEP_3 -> {
|
||||
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupSetupStep3Fragment.newInstance())
|
||||
.commit()
|
||||
}
|
||||
KeysBackupSetupSharedViewModel.NAVIGATE_FINISH -> {
|
||||
val resultIntent = Intent()
|
||||
viewModel.keysVersion.value?.version?.let {
|
||||
resultIntent.putExtra(KEYS_VERSION, it)
|
||||
}
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
finish()
|
||||
}
|
||||
KeysBackupSetupSharedViewModel.NAVIGATE_MANUAL_EXPORT -> {
|
||||
exportKeysManually()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
viewModel.prepareRecoverFailError.observe(this, Observer { error ->
|
||||
if (error != null) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unknown_error)
|
||||
.setMessage(error.localizedMessage)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
//nop
|
||||
viewModel.prepareRecoverFailError.value = null
|
||||
}
|
||||
.show()
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.creatingBackupError.observe(this, Observer { error ->
|
||||
if (error != null) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unexpected_error)
|
||||
.setMessage(error.localizedMessage)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
//nop
|
||||
viewModel.creatingBackupError.value = null
|
||||
}
|
||||
.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun exportKeysManually() {
|
||||
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
|
||||
override fun onPassphrase(passphrase: String) {
|
||||
notImplemented()
|
||||
/*
|
||||
showWaitingView()
|
||||
|
||||
CommonActivityUtils.exportKeys(mSession, passphrase, object : SimpleApiCallback<String>(this@KeysBackupSetupActivity) {
|
||||
override fun onSuccess(filename: String) {
|
||||
hideWaitingView()
|
||||
|
||||
AlertDialog.Builder(this@KeysBackupSetupActivity)
|
||||
.setMessage(getString(R.string.encryption_export_saved_as, filename))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { dialog, which ->
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra(MANUAL_EXPORT, true)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
super.onNetworkError(e)
|
||||
hideWaitingView()
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
super.onMatrixError(e)
|
||||
hideWaitingView()
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
super.onUnexpectedError(e)
|
||||
hideWaitingView()
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (viewModel.shouldPromptOnBack) {
|
||||
if (waitingView?.isVisible == true) {
|
||||
return
|
||||
}
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.keys_backup_setup_skip_title)
|
||||
.setMessage(R.string.keys_backup_setup_skip_msg)
|
||||
.setNegativeButton(R.string.stay, null)
|
||||
.setPositiveButton(R.string.abort) { _, _ ->
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
// I think this code is useful, but it violates the code quality rules
|
||||
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
// if (item.itemId == android .R. id. home) {
|
||||
// onBackPressed()
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// return super.onOptionsItemSelected(item)
|
||||
// }
|
||||
|
||||
|
||||
companion object {
|
||||
const val KEYS_VERSION = "KEYS_VERSION"
|
||||
const val MANUAL_EXPORT = "MANUAL_EXPORT"
|
||||
const val EXTRA_SHOW_MANUAL_EXPORT = "SHOW_MANUAL_EXPORT"
|
||||
|
||||
fun intent(context: Context, showManualExport: Boolean): Intent {
|
||||
val intent = Intent(context, KeysBackupSetupActivity::class.java)
|
||||
intent.putExtra(EXTRA_SHOW_MANUAL_EXPORT, showManualExport)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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.fragments.keysbackup.setup
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.nulabinc.zxcvbn.Strength
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* The shared view model between all fragments.
|
||||
*/
|
||||
class KeysBackupSetupSharedViewModel : ViewModel() {
|
||||
|
||||
companion object {
|
||||
const val NAVIGATE_TO_STEP_2 = "NAVIGATE_TO_STEP_2"
|
||||
const val NAVIGATE_TO_STEP_3 = "NAVIGATE_TO_STEP_3"
|
||||
const val NAVIGATE_FINISH = "NAVIGATE_FINISH"
|
||||
const val NAVIGATE_MANUAL_EXPORT = "NAVIGATE_MANUAL_EXPORT"
|
||||
}
|
||||
|
||||
lateinit var session: Session
|
||||
|
||||
var showManualExport: MutableLiveData<Boolean> = MutableLiveData()
|
||||
|
||||
var navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
var shouldPromptOnBack = true
|
||||
|
||||
// Step 2
|
||||
var passphrase: MutableLiveData<String> = MutableLiveData()
|
||||
var passphraseError: MutableLiveData<String> = MutableLiveData()
|
||||
|
||||
var confirmPassphrase: MutableLiveData<String> = MutableLiveData()
|
||||
var confirmPassphraseError: MutableLiveData<String> = MutableLiveData()
|
||||
|
||||
var passwordStrength: MutableLiveData<Strength> = MutableLiveData()
|
||||
var showPasswordMode: MutableLiveData<Boolean> = MutableLiveData()
|
||||
|
||||
// Step 3
|
||||
// Var to ignore events from previous request(s) to generate a recovery key
|
||||
private var currentRequestId: MutableLiveData<Long> = MutableLiveData()
|
||||
var recoveryKey: MutableLiveData<String> = MutableLiveData()
|
||||
var prepareRecoverFailError: MutableLiveData<Throwable> = MutableLiveData()
|
||||
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
|
||||
var copyHasBeenMade = false
|
||||
var isCreatingBackupVersion: MutableLiveData<Boolean> = MutableLiveData()
|
||||
var creatingBackupError: MutableLiveData<Throwable> = MutableLiveData()
|
||||
var keysVersion: MutableLiveData<KeysVersion> = MutableLiveData()
|
||||
|
||||
|
||||
var loadingStatus: MutableLiveData<WaitingViewData> = MutableLiveData()
|
||||
|
||||
init {
|
||||
showPasswordMode.value = false
|
||||
recoveryKey.value = null
|
||||
isCreatingBackupVersion.value = false
|
||||
prepareRecoverFailError.value = null
|
||||
creatingBackupError.value = null
|
||||
loadingStatus.value = null
|
||||
}
|
||||
|
||||
fun initSession(session: Session) {
|
||||
this.session = session
|
||||
}
|
||||
|
||||
fun prepareRecoveryKey(context: Context, withPassphrase: String?) {
|
||||
// Update requestId
|
||||
currentRequestId.value = System.currentTimeMillis()
|
||||
isCreatingBackupVersion.value = true
|
||||
|
||||
// Ensure passphrase is hidden during the process
|
||||
showPasswordMode.value = false
|
||||
|
||||
recoveryKey.value = null
|
||||
prepareRecoverFailError.value = null
|
||||
session.let { mxSession ->
|
||||
val requestedId = currentRequestId.value!!
|
||||
|
||||
mxSession.getKeysBackupService().prepareKeysBackupVersion(withPassphrase,
|
||||
object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
if (requestedId != currentRequestId.value) {
|
||||
//this is an old request, we can't cancel but we can ignore
|
||||
return
|
||||
}
|
||||
|
||||
loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_step3_generating_key_status),
|
||||
progress,
|
||||
total)
|
||||
}
|
||||
},
|
||||
object : MatrixCallback<MegolmBackupCreationInfo> {
|
||||
override fun onSuccess(data: MegolmBackupCreationInfo) {
|
||||
if (requestedId != currentRequestId.value) {
|
||||
//this is an old request, we can't cancel but we can ignore
|
||||
return
|
||||
}
|
||||
recoveryKey.value = data.recoveryKey
|
||||
megolmBackupCreationInfo = data
|
||||
copyHasBeenMade = false
|
||||
|
||||
val keyBackup = session?.getKeysBackupService()
|
||||
if (keyBackup != null) {
|
||||
createKeysBackup(context, keyBackup)
|
||||
} else {
|
||||
loadingStatus.value = null
|
||||
|
||||
isCreatingBackupVersion.value = false
|
||||
prepareRecoverFailError.value = Exception()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (requestedId != currentRequestId.value) {
|
||||
//this is an old request, we can't cancel but we can ignore
|
||||
return
|
||||
}
|
||||
|
||||
loadingStatus.value = null
|
||||
|
||||
isCreatingBackupVersion.value = false
|
||||
prepareRecoverFailError.value = failure ?: Exception()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun createKeysBackup(context: Context, keysBackup: KeysBackupService) {
|
||||
loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_creating_backup), isIndeterminate = true)
|
||||
|
||||
creatingBackupError.value = null
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : MatrixCallback<KeysVersion> {
|
||||
|
||||
override fun onSuccess(data: KeysVersion) {
|
||||
loadingStatus.value = null
|
||||
|
||||
isCreatingBackupVersion.value = false
|
||||
keysVersion.value = data
|
||||
navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "## createKeyBackupVersion")
|
||||
loadingStatus.value = null
|
||||
|
||||
isCreatingBackupVersion.value = false
|
||||
creatingBackupError.value = failure
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.setup
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
|
||||
class KeysBackupSetupStep1Fragment : VectorBaseFragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupSetupStep1Fragment()
|
||||
}
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step1
|
||||
|
||||
private lateinit var viewModel: KeysBackupSetupSharedViewModel
|
||||
|
||||
@BindView(R.id.keys_backup_setup_step1_advanced)
|
||||
lateinit var advancedOptionText: TextView
|
||||
|
||||
|
||||
@BindView(R.id.keys_backup_setup_step1_manualExport)
|
||||
lateinit var manualExportButton: Button
|
||||
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
viewModel.showManualExport.observe(this, Observer {
|
||||
val showOption = it ?: false
|
||||
//Can't use isVisible because the kotlin compiler will crash with Back-end (JVM) Internal error: wrong code generated
|
||||
advancedOptionText.visibility = if (showOption) View.VISIBLE else View.GONE
|
||||
manualExportButton.visibility = if (showOption) View.VISIBLE else View.GONE
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_setup_step1_button)
|
||||
fun onButtonClick() {
|
||||
viewModel.navigateEvent.value = LiveEvent(KeysBackupSetupSharedViewModel.NAVIGATE_TO_STEP_2)
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_setup_step1_manualExport)
|
||||
fun onManualExportClick() {
|
||||
viewModel.navigateEvent.value = LiveEvent(KeysBackupSetupSharedViewModel.NAVIGATE_MANUAL_EXPORT)
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.setup
|
||||
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import butterknife.OnTextChanged
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.nulabinc.zxcvbn.Zxcvbn
|
||||
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.showPassword
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.core.ui.views.PasswordStrengthBar
|
||||
import im.vector.riotredesign.features.settings.VectorLocale
|
||||
|
||||
|
||||
class KeysBackupSetupStep2Fragment : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step2
|
||||
|
||||
@BindView(R.id.keys_backup_root)
|
||||
lateinit var rootGroup: ViewGroup
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_enter_edittext)
|
||||
lateinit var mPassphraseTextEdit: EditText
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_enter_til)
|
||||
lateinit var mPassphraseInputLayout: TextInputLayout
|
||||
|
||||
@BindView(R.id.keys_backup_view_show_password)
|
||||
lateinit var mPassphraseReveal: ImageView
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_confirm_edittext)
|
||||
lateinit var mPassphraseConfirmTextEdit: EditText
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_confirm_til)
|
||||
lateinit var mPassphraseConfirmInputLayout: TextInputLayout
|
||||
|
||||
@BindView(R.id.keys_backup_passphrase_security_progress)
|
||||
lateinit var mPassphraseProgressLevel: PasswordStrengthBar
|
||||
|
||||
private val zxcvbn = Zxcvbn()
|
||||
|
||||
@OnTextChanged(R.id.keys_backup_passphrase_enter_edittext)
|
||||
fun onPassphraseChanged() {
|
||||
viewModel.passphrase.value = mPassphraseTextEdit.text.toString()
|
||||
viewModel.confirmPassphraseError.value = null
|
||||
}
|
||||
|
||||
@OnTextChanged(R.id.keys_backup_passphrase_confirm_edittext)
|
||||
fun onConfirmPassphraseChanged() {
|
||||
viewModel.confirmPassphrase.value = mPassphraseConfirmTextEdit.text.toString()
|
||||
}
|
||||
|
||||
private lateinit var viewModel: KeysBackupSetupSharedViewModel
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
viewModel.shouldPromptOnBack = true
|
||||
bindViewToViewModel()
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* MENU
|
||||
* ========================================================================================== */
|
||||
|
||||
private fun bindViewToViewModel() {
|
||||
viewModel.passwordStrength.observe(this, Observer { strength ->
|
||||
if (strength == null) {
|
||||
mPassphraseProgressLevel.strength = 0
|
||||
mPassphraseInputLayout.error = null
|
||||
} else {
|
||||
val score = strength.score
|
||||
mPassphraseProgressLevel.strength = score
|
||||
|
||||
if (score in 1..3) {
|
||||
val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale)
|
||||
if (warning != null) {
|
||||
mPassphraseInputLayout.error = warning
|
||||
}
|
||||
|
||||
val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale)
|
||||
if (suggestions != null) {
|
||||
mPassphraseInputLayout.error = suggestions.firstOrNull()
|
||||
}
|
||||
|
||||
} else {
|
||||
mPassphraseInputLayout.error = null
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.passphrase.observe(this, Observer<String> { newValue ->
|
||||
if (TextUtils.isEmpty(newValue)) {
|
||||
viewModel.passwordStrength.value = null
|
||||
} else {
|
||||
AsyncTask.execute {
|
||||
val strength = zxcvbn.measure(newValue)
|
||||
activity?.runOnUiThread {
|
||||
viewModel.passwordStrength.value = strength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
mPassphraseTextEdit.setText(viewModel.passphrase.value)
|
||||
|
||||
viewModel.passphraseError.observe(this, Observer {
|
||||
TransitionManager.beginDelayedTransition(rootGroup)
|
||||
mPassphraseInputLayout.error = it
|
||||
})
|
||||
|
||||
mPassphraseConfirmTextEdit.setText(viewModel.confirmPassphrase.value)
|
||||
|
||||
viewModel.showPasswordMode.observe(this, Observer {
|
||||
val shouldBeVisible = it ?: false
|
||||
mPassphraseTextEdit.showPassword(shouldBeVisible)
|
||||
mPassphraseConfirmTextEdit.showPassword(shouldBeVisible)
|
||||
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
|
||||
})
|
||||
|
||||
viewModel.confirmPassphraseError.observe(this, Observer {
|
||||
TransitionManager.beginDelayedTransition(rootGroup)
|
||||
mPassphraseConfirmInputLayout.error = it
|
||||
})
|
||||
|
||||
mPassphraseConfirmTextEdit.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
doNext()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_view_show_password)
|
||||
fun toggleVisibilityMode() {
|
||||
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_setup_step2_button)
|
||||
fun doNext() {
|
||||
when {
|
||||
TextUtils.isEmpty(viewModel.passphrase.value) -> {
|
||||
viewModel.passphraseError.value = context?.getString(R.string.passphrase_empty_error_message)
|
||||
}
|
||||
viewModel.passphrase.value != viewModel.confirmPassphrase.value -> {
|
||||
viewModel.confirmPassphraseError.value = context?.getString(R.string.passphrase_passphrase_does_not_match)
|
||||
}
|
||||
viewModel.passwordStrength.value?.score ?: 0 < 4 -> {
|
||||
viewModel.passphraseError.value = context?.getString(R.string.passphrase_passphrase_too_weak)
|
||||
}
|
||||
else -> {
|
||||
viewModel.megolmBackupCreationInfo = null
|
||||
|
||||
viewModel.prepareRecoveryKey(activity!!, viewModel.passphrase.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_setup_step2_skip_button)
|
||||
fun skipPassphrase() {
|
||||
when {
|
||||
TextUtils.isEmpty(viewModel.passphrase.value) -> {
|
||||
// Generate a recovery key for the user
|
||||
viewModel.megolmBackupCreationInfo = null
|
||||
|
||||
viewModel.prepareRecoveryKey(activity!!, null)
|
||||
}
|
||||
else -> {
|
||||
// User has entered a passphrase but want to skip this step.
|
||||
viewModel.passphraseError.value = context?.getString(R.string.keys_backup_passphrase_not_empty_error_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupSetupStep2Fragment()
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.keysbackup.setup
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.core.utils.*
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step3
|
||||
|
||||
@BindView(R.id.keys_backup_setup_step3_button)
|
||||
lateinit var mFinishButton: Button
|
||||
|
||||
@BindView(R.id.keys_backup_recovery_key_text)
|
||||
lateinit var mRecoveryKeyTextView: TextView
|
||||
|
||||
@BindView(R.id.keys_backup_setup_step3_line2_text)
|
||||
lateinit var mRecoveryKeyLabel2TextView: TextView
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupSetupStep3Fragment()
|
||||
}
|
||||
|
||||
private lateinit var viewModel: KeysBackupSetupSharedViewModel
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
|
||||
viewModel.shouldPromptOnBack = false
|
||||
|
||||
viewModel.passphrase.observe(this, Observer {
|
||||
if (it.isNullOrBlank()) {
|
||||
//Recovery was generated, so show key and options to save
|
||||
mRecoveryKeyLabel2TextView.text = getString(R.string.keys_backup_setup_step3_text_line2_no_passphrase)
|
||||
mFinishButton.text = getString(R.string.keys_backup_setup_step3_button_title_no_passphrase)
|
||||
|
||||
mRecoveryKeyTextView.text = viewModel.recoveryKey.value!!
|
||||
.replace(" ", "")
|
||||
.chunked(16)
|
||||
.joinToString("\n") {
|
||||
it
|
||||
.chunked(4)
|
||||
.joinToString(" ")
|
||||
}
|
||||
mRecoveryKeyTextView.isVisible = true
|
||||
|
||||
} else {
|
||||
mRecoveryKeyLabel2TextView.text = getString(R.string.keys_backup_setup_step3_text_line2)
|
||||
mFinishButton.text = getString(R.string.keys_backup_setup_step3_button_title)
|
||||
mRecoveryKeyTextView.isVisible = false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_setup_step3_button)
|
||||
fun onFinishButtonClicked() {
|
||||
if (viewModel.megolmBackupCreationInfo == null) {
|
||||
//nothing
|
||||
} else {
|
||||
if (viewModel.passphrase.value.isNullOrBlank() && !viewModel.copyHasBeenMade) {
|
||||
Toast.makeText(context, R.string.keys_backup_setup_step3_please_make_copy, Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
viewModel.navigateEvent.value = LiveEvent(KeysBackupSetupSharedViewModel.NAVIGATE_FINISH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_setup_step3_copy_button)
|
||||
fun onCopyButtonClicked() {
|
||||
val dialog = BottomSheetDialog(activity!!)
|
||||
dialog.setContentView(R.layout.bottom_sheet_save_recovery_key)
|
||||
dialog.setCanceledOnTouchOutside(true)
|
||||
val recoveryKey = viewModel.recoveryKey.value!!
|
||||
|
||||
if (viewModel.passphrase.value.isNullOrBlank()) {
|
||||
dialog.findViewById<TextView>(R.id.keys_backup_recovery_key_text)?.isVisible = false
|
||||
} else {
|
||||
dialog.findViewById<TextView>(R.id.keys_backup_recovery_key_text)?.let {
|
||||
it.isVisible = true
|
||||
it.text = recoveryKey.replace(" ", "")
|
||||
.chunked(16)
|
||||
.joinToString("\n") {
|
||||
it
|
||||
.chunked(4)
|
||||
.joinToString(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog.findViewById<View>(R.id.keys_backup_setup_save)?.setOnClickListener {
|
||||
if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_EXPORT_KEYS)) {
|
||||
exportRecoveryKeyToFile(recoveryKey)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
dialog.findViewById<View>(R.id.keys_backup_setup_share)?.setOnClickListener {
|
||||
startSharePlainTextIntent(this,
|
||||
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
|
||||
recoveryKey,
|
||||
context?.getString(R.string.recovery_key))
|
||||
viewModel.copyHasBeenMade = true
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
@OnClick(R.id.keys_backup_recovery_key_text)
|
||||
fun onRecoveryKeyClicked() {
|
||||
viewModel.recoveryKey.value?.let {
|
||||
viewModel.copyHasBeenMade = true
|
||||
|
||||
copyToClipboard(activity!!, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun exportRecoveryKeyToFile(it: String) {
|
||||
val stream = ByteArrayInputStream(it.toByteArray())
|
||||
|
||||
TODO()
|
||||
/*
|
||||
val url = viewModel.session.mediaCache.saveMedia(stream, "recovery-key" + System.currentTimeMillis() + ".txt", "text/plain")
|
||||
stream.close()
|
||||
CommonActivityUtils.saveMediaIntoDownloads(context,
|
||||
File(Uri.parse(url).path!!), "recovery-key.txt", "text/plain", object : SimpleApiCallback<String>() {
|
||||
override fun onSuccess(path: String) {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setMessage(getString(R.string.recovery_key_export_saved_as_warning, path))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
viewModel.copyHasBeenMade = true
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
if (allGranted(grantResults)) {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_EXPORT_KEYS) {
|
||||
viewModel.recoveryKey.value?.let {
|
||||
exportRecoveryKeyToFile(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.verification
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.features.popup.PopupAlertManager
|
||||
|
||||
/**
|
||||
* Listens to the VerificationManager and add a new notification when an incoming request is detected.
|
||||
*/
|
||||
class IncomingVerificationRequestHandler(val context: Context,
|
||||
private val credentials: Credentials,
|
||||
verificationService: SasVerificationService) : SasVerificationService.SasVerificationListener {
|
||||
|
||||
init {
|
||||
verificationService.addListener(this)
|
||||
}
|
||||
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
when (tx.state) {
|
||||
SasVerificationTxState.OnStarted -> {
|
||||
//Add a notification for every incoming request
|
||||
val session = Matrix.getInstance().currentSession!!
|
||||
val name = session.getUser(tx.otherUserId)?.displayName
|
||||
?: tx.otherUserId
|
||||
|
||||
val alert = PopupAlertManager.VectorAlert(
|
||||
"kvr_${tx.transactionId}",
|
||||
context.getString(R.string.sas_incoming_request_notif_title),
|
||||
context.getString(R.string.sas_incoming_request_notif_content, name),
|
||||
R.drawable.shield
|
||||
).apply {
|
||||
contentAction = Runnable {
|
||||
val intent = SASVerificationActivity.incomingIntent(context,
|
||||
credentials.userId,
|
||||
tx.otherUserId,
|
||||
tx.transactionId)
|
||||
weakCurrentActivity?.get()?.startActivity(intent)
|
||||
}
|
||||
dismissedAction = Runnable {
|
||||
tx.cancel()
|
||||
}
|
||||
addButton(
|
||||
context.getString(R.string.ignore),
|
||||
Runnable {
|
||||
tx.cancel()
|
||||
}
|
||||
)
|
||||
addButton(
|
||||
context.getString(R.string.action_open),
|
||||
Runnable {
|
||||
val intent = SASVerificationActivity.incomingIntent(context,
|
||||
credentials.userId,
|
||||
tx.otherUserId,
|
||||
tx.transactionId)
|
||||
weakCurrentActivity?.get()?.startActivity(intent)
|
||||
}
|
||||
)
|
||||
//10mn expiration
|
||||
expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
|
||||
|
||||
}
|
||||
PopupAlertManager.postVectorAlert(alert)
|
||||
}
|
||||
SasVerificationTxState.Cancelled,
|
||||
SasVerificationTxState.OnCancelled,
|
||||
SasVerificationTxState.Verified -> {
|
||||
//cancel related notification
|
||||
PopupAlertManager.cancelAlert("kvr_${tx.transactionId}")
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.verification
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
|
||||
class SASVerificationActivity : SimpleFragmentActivity() {
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_MATRIX_ID = "EXTRA_MATRIX_ID"
|
||||
private const val EXTRA_TRANSACTION_ID = "EXTRA_TRANSACTION_ID"
|
||||
private const val EXTRA_OTHER_USER_ID = "EXTRA_OTHER_USER_ID"
|
||||
private const val EXTRA_OTHER_DEVICE_ID = "EXTRA_OTHER_DEVICE_ID"
|
||||
private const val EXTRA_IS_INCOMING = "EXTRA_IS_INCOMING"
|
||||
|
||||
/* ==========================================================================================
|
||||
* INPUT
|
||||
* ========================================================================================== */
|
||||
|
||||
fun incomingIntent(context: Context, matrixID: String, otherUserId: String, transactionID: String): Intent {
|
||||
val intent = Intent(context, SASVerificationActivity::class.java)
|
||||
intent.putExtra(EXTRA_MATRIX_ID, matrixID)
|
||||
intent.putExtra(EXTRA_TRANSACTION_ID, transactionID)
|
||||
intent.putExtra(EXTRA_OTHER_USER_ID, otherUserId)
|
||||
intent.putExtra(EXTRA_IS_INCOMING, true)
|
||||
return intent
|
||||
}
|
||||
|
||||
fun outgoingIntent(context: Context, matrixID: String, otherUserId: String, otherDeviceId: String): Intent {
|
||||
val intent = Intent(context, SASVerificationActivity::class.java)
|
||||
intent.putExtra(EXTRA_MATRIX_ID, matrixID)
|
||||
intent.putExtra(EXTRA_OTHER_DEVICE_ID, otherDeviceId)
|
||||
intent.putExtra(EXTRA_OTHER_USER_ID, otherUserId)
|
||||
intent.putExtra(EXTRA_IS_INCOMING, false)
|
||||
return intent
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* OUTPUT
|
||||
* ========================================================================================== */
|
||||
|
||||
fun getOtherUserId(intent: Intent?): String? {
|
||||
return intent?.getStringExtra(EXTRA_OTHER_USER_ID)
|
||||
}
|
||||
|
||||
fun getOtherDeviceId(intent: Intent?): String? {
|
||||
return intent?.getStringExtra(EXTRA_OTHER_DEVICE_ID)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTitleRes() = R.string.title_activity_verify_device
|
||||
|
||||
|
||||
private lateinit var viewModel: SasVerificationViewModel
|
||||
|
||||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
viewModel = ViewModelProviders.of(this).get(SasVerificationViewModel::class.java)
|
||||
val transactionID: String? = intent.getStringExtra(EXTRA_TRANSACTION_ID)
|
||||
|
||||
if (isFirstCreation()) {
|
||||
val isIncoming = intent.getBooleanExtra(EXTRA_IS_INCOMING, false)
|
||||
if (isIncoming) {
|
||||
//incoming always have a transaction id
|
||||
viewModel.initIncoming(mSession, intent.getStringExtra(EXTRA_OTHER_USER_ID), transactionID)
|
||||
} else {
|
||||
viewModel.initOutgoing(mSession, intent.getStringExtra(EXTRA_OTHER_USER_ID), intent.getStringExtra(EXTRA_OTHER_DEVICE_ID))
|
||||
}
|
||||
|
||||
if (isIncoming) {
|
||||
val incoming = viewModel.transaction as IncomingSasVerificationTransaction
|
||||
when (incoming.uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.UNKNOWN,
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT,
|
||||
IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> {
|
||||
supportActionBar?.setTitle(R.string.sas_incoming_request_title)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationIncomingFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION,
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationShortCodeFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationVerifiedFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME,
|
||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> {
|
||||
viewModel.navigateCancel()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val outgoing = viewModel.transaction as? OutgoingSasVerificationRequest
|
||||
//transaction can be null, as not yet created
|
||||
when (outgoing?.uxState) {
|
||||
null,
|
||||
OutgoingSasVerificationRequest.UxState.UNKNOWN,
|
||||
OutgoingSasVerificationRequest.UxState.WAIT_FOR_START,
|
||||
OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationStartFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS,
|
||||
OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationShortCodeFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationVerifiedFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME,
|
||||
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> {
|
||||
viewModel.navigateCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.navigateEvent.observe(this, Observer { uxStateEvent ->
|
||||
when (uxStateEvent?.getContentIfNotHandled()) {
|
||||
SasVerificationViewModel.NAVIGATE_FINISH -> {
|
||||
finish()
|
||||
}
|
||||
SasVerificationViewModel.NAVIGATE_FINISH_SUCCESS -> {
|
||||
val dataResult = Intent()
|
||||
dataResult.putExtra(EXTRA_OTHER_DEVICE_ID, viewModel.otherDeviceId)
|
||||
dataResult.putExtra(EXTRA_OTHER_USER_ID, viewModel.otherUserId)
|
||||
setResult(Activity.RESULT_OK, dataResult)
|
||||
finish()
|
||||
}
|
||||
SasVerificationViewModel.NAVIGATE_SAS_DISPLAY -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationShortCodeFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
SasVerificationViewModel.NAVIGATE_SUCCESS -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out)
|
||||
.replace(R.id.container, SASVerificationVerifiedFragment.newInstance())
|
||||
.commitNow()
|
||||
}
|
||||
SasVerificationViewModel.NAVIGATE_CANCELLED -> {
|
||||
val isCancelledByMe = viewModel.transaction?.state == SasVerificationTxState.Cancelled
|
||||
val humanReadableReason = when (viewModel.transaction?.cancelledReason) {
|
||||
CancelCode.User -> getString(R.string.sas_error_m_user)
|
||||
CancelCode.Timeout -> getString(R.string.sas_error_m_timeout)
|
||||
CancelCode.UnknownTransaction -> getString(R.string.sas_error_m_unknown_transaction)
|
||||
CancelCode.UnknownMethod -> getString(R.string.sas_error_m_unknown_method)
|
||||
CancelCode.MismatchedCommitment -> getString(R.string.sas_error_m_mismatched_commitment)
|
||||
CancelCode.MismatchedSas -> getString(R.string.sas_error_m_mismatched_sas)
|
||||
CancelCode.UnexpectedMessage -> getString(R.string.sas_error_m_unexpected_message)
|
||||
CancelCode.InvalidMessage -> getString(R.string.sas_error_m_invalid_message)
|
||||
CancelCode.MismatchedKeys -> getString(R.string.sas_error_m_key_mismatch)
|
||||
// Use user error
|
||||
CancelCode.UserMismatchError -> getString(R.string.sas_error_m_user_error)
|
||||
null -> getString(R.string.sas_error_unknown)
|
||||
}
|
||||
val message =
|
||||
if (isCancelledByMe) getString(R.string.sas_cancelled_by_me, humanReadableReason)
|
||||
else getString(R.string.sas_cancelled_by_other, humanReadableReason)
|
||||
//Show a dialog
|
||||
if (!this.isFinishing) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.sas_cancelled_dialog_title)
|
||||
.setMessage(message)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
//nop
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.loadingLiveEvent.observe(this, Observer {
|
||||
if (it == null) {
|
||||
hideWaitingView()
|
||||
} else {
|
||||
val status = if (it == -1) "" else getString(it)
|
||||
updateWaitingView(WaitingViewData(status, isIndeterminate = true))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
//we want to cancel the transaction
|
||||
viewModel.cancelTransaction()
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
|
||||
override fun onBackPressed() {
|
||||
//we want to cancel the transaction
|
||||
viewModel.cancelTransaction()
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.verification
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
class SASVerificationIncomingFragment : VectorBaseFragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = SASVerificationIncomingFragment()
|
||||
}
|
||||
|
||||
@BindView(R.id.sas_incoming_request_user_display_name)
|
||||
lateinit var otherUserDisplayNameTextView: TextView
|
||||
|
||||
@BindView(R.id.sas_incoming_request_user_id)
|
||||
lateinit var otherUserIdTextView: TextView
|
||||
|
||||
@BindView(R.id.sas_incoming_request_user_device)
|
||||
lateinit var otherDeviceTextView: TextView
|
||||
|
||||
@BindView(R.id.sas_incoming_request_user_avatar)
|
||||
lateinit var avatarImageView: ImageView
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_sas_verification_incoming_request
|
||||
|
||||
private lateinit var viewModel: SasVerificationViewModel
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
otherUserDisplayNameTextView.text = viewModel.otherUser?.displayName ?: viewModel.otherUserId
|
||||
otherUserIdTextView.text = viewModel.otherUserId
|
||||
otherDeviceTextView.text = viewModel.otherDeviceId
|
||||
|
||||
viewModel.otherUser?.let {
|
||||
AvatarRenderer.render(it, avatarImageView)
|
||||
}
|
||||
|
||||
viewModel.transactionState.observe(this, Observer {
|
||||
val uxState = (viewModel.transaction as? IncomingSasVerificationTransaction)?.uxState
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> {
|
||||
viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
viewModel.shortCodeReady()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME,
|
||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
viewModel.navigateCancel()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.sas_request_continue_button)
|
||||
fun didAccept() {
|
||||
viewModel.acceptTransaction()
|
||||
}
|
||||
|
||||
@OnClick(R.id.sas_request_cancel_button)
|
||||
fun didCancel() {
|
||||
viewModel.cancelTransaction()
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.verification
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
|
||||
class SASVerificationShortCodeFragment : VectorBaseFragment() {
|
||||
|
||||
private lateinit var viewModel: SasVerificationViewModel
|
||||
|
||||
companion object {
|
||||
fun newInstance() = SASVerificationShortCodeFragment()
|
||||
}
|
||||
|
||||
|
||||
@BindView(R.id.sas_decimal_code)
|
||||
lateinit var decimalTextView: TextView
|
||||
|
||||
@BindView(R.id.sas_emoji_description)
|
||||
lateinit var descriptionTextView: TextView
|
||||
|
||||
@BindView(R.id.sas_emoji_grid)
|
||||
lateinit var emojiGrid: ViewGroup
|
||||
|
||||
|
||||
@BindView(R.id.emoji0)
|
||||
lateinit var emoji0View: ViewGroup
|
||||
@BindView(R.id.emoji1)
|
||||
lateinit var emoji1View: ViewGroup
|
||||
@BindView(R.id.emoji2)
|
||||
lateinit var emoji2View: ViewGroup
|
||||
@BindView(R.id.emoji3)
|
||||
lateinit var emoji3View: ViewGroup
|
||||
@BindView(R.id.emoji4)
|
||||
lateinit var emoji4View: ViewGroup
|
||||
@BindView(R.id.emoji5)
|
||||
lateinit var emoji5View: ViewGroup
|
||||
@BindView(R.id.emoji6)
|
||||
lateinit var emoji6View: ViewGroup
|
||||
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_sas_verification_display_code
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
|
||||
viewModel.transaction?.let {
|
||||
if (it.supportsEmoji()) {
|
||||
val emojicodes = it.getEmojiCodeRepresentation()
|
||||
emojicodes.forEachIndexed { index, emojiRepresentation ->
|
||||
when (index) {
|
||||
0 -> {
|
||||
emoji0View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
|
||||
emoji0View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
|
||||
}
|
||||
1 -> {
|
||||
emoji1View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
|
||||
emoji1View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
|
||||
}
|
||||
2 -> {
|
||||
emoji2View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
|
||||
emoji2View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
|
||||
}
|
||||
3 -> {
|
||||
emoji3View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
|
||||
emoji3View.findViewById<TextView>(R.id.item_emoji_name_tv)?.setText(emojiRepresentation.nameResId)
|
||||
}
|
||||
4 -> {
|
||||
emoji4View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
|
||||
emoji4View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
|
||||
}
|
||||
5 -> {
|
||||
emoji5View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
|
||||
emoji5View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
|
||||
}
|
||||
6 -> {
|
||||
emoji6View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
|
||||
emoji6View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//decimal is at least supported
|
||||
decimalTextView.text = it.getDecimalCodeRepresentation()
|
||||
|
||||
|
||||
if (it.supportsEmoji()) {
|
||||
descriptionTextView.text = getString(R.string.sas_emoji_description)
|
||||
decimalTextView.isVisible = false
|
||||
emojiGrid.isVisible = true
|
||||
} else {
|
||||
descriptionTextView.text = getString(R.string.sas_decimal_description)
|
||||
decimalTextView.isVisible = true
|
||||
emojiGrid.isInvisible = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
viewModel.transactionState.observe(this, Observer {
|
||||
if (viewModel.transaction is IncomingSasVerificationTransaction) {
|
||||
val uxState = (viewModel.transaction as IncomingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
viewModel.deviceIsVerified()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME,
|
||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
viewModel.navigateCancel()
|
||||
}
|
||||
else -> {
|
||||
viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner
|
||||
}
|
||||
}
|
||||
|
||||
} else if (viewModel.transaction is OutgoingSasVerificationRequest) {
|
||||
val uxState = (viewModel.transaction as OutgoingSasVerificationRequest).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
viewModel.deviceIsVerified()
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME,
|
||||
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> {
|
||||
viewModel.loadingLiveEvent.value = null
|
||||
viewModel.navigateCancel()
|
||||
}
|
||||
else -> {
|
||||
viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@OnClick(R.id.sas_request_continue_button)
|
||||
fun didAccept() {
|
||||
viewModel.confirmEmojiSame()
|
||||
}
|
||||
|
||||
@OnClick(R.id.sas_request_cancel_button)
|
||||
fun didCancel() {
|
||||
viewModel.cancelTransaction()
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.verification
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.BindView
|
||||
import butterknife.OnClick
|
||||
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseActivity
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
|
||||
class SASVerificationStartFragment : VectorBaseFragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = SASVerificationStartFragment()
|
||||
}
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_sas_verification_start
|
||||
|
||||
private lateinit var viewModel: SasVerificationViewModel
|
||||
|
||||
|
||||
@BindView(R.id.rootLayout)
|
||||
lateinit var rootLayout: ViewGroup
|
||||
|
||||
@BindView(R.id.sas_start_button)
|
||||
lateinit var startButton: Button
|
||||
|
||||
@BindView(R.id.sas_start_button_loading)
|
||||
lateinit var startButtonLoading: ProgressBar
|
||||
|
||||
@BindView(R.id.sas_verifying_keys)
|
||||
lateinit var loadingText: TextView
|
||||
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
viewModel.transactionState.observe(this, Observer {
|
||||
val uxState = (viewModel.transaction as? OutgoingSasVerificationRequest)?.uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT -> {
|
||||
//display loading
|
||||
TransitionManager.beginDelayedTransition(this.rootLayout)
|
||||
this.loadingText.isVisible = true
|
||||
this.startButton.isInvisible = true
|
||||
this.startButtonLoading.isVisible = true
|
||||
this.startButtonLoading.animate()
|
||||
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
||||
viewModel.shortCodeReady()
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME,
|
||||
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> {
|
||||
viewModel.navigateCancel()
|
||||
}
|
||||
else -> {
|
||||
TransitionManager.beginDelayedTransition(this.rootLayout)
|
||||
this.loadingText.isVisible = false
|
||||
this.startButton.isVisible = true
|
||||
this.startButtonLoading.isVisible = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.sas_start_button)
|
||||
fun doStart() {
|
||||
viewModel.beginSasKeyVerification()
|
||||
}
|
||||
|
||||
@OnClick(R.id.sas_legacy_verification)
|
||||
fun doLegacy() {
|
||||
(requireActivity() as VectorBaseActivity).notImplemented()
|
||||
|
||||
/*
|
||||
viewModel.session.crypto?.getDeviceInfo(viewModel.otherUserId ?: "", viewModel.otherDeviceId
|
||||
?: "", object : SimpleApiCallback<MXDeviceInfo>() {
|
||||
override fun onSuccess(info: MXDeviceInfo?) {
|
||||
info?.let {
|
||||
|
||||
CommonActivityUtils.displayDeviceVerificationDialogLegacy(it, it.userId, viewModel.session, activity, object : YesNoListener {
|
||||
override fun yes() {
|
||||
viewModel.manuallyVerified()
|
||||
}
|
||||
|
||||
override fun no() {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
@OnClick(R.id.sas_cancel_button)
|
||||
fun doCancel() {
|
||||
// Transaction may be started, or not
|
||||
viewModel.cancelTransaction()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotredesign.features.crypto.verification
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import butterknife.OnClick
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
|
||||
class SASVerificationVerifiedFragment : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_sas_verification_verified
|
||||
|
||||
companion object {
|
||||
fun newInstance() = SASVerificationVerifiedFragment()
|
||||
}
|
||||
|
||||
private lateinit var viewModel: SasVerificationViewModel
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.sas_verification_verified_done_button)
|
||||
fun onDone() {
|
||||
viewModel.finishSuccess()
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.riotredesign.features.crypto.verification
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
|
||||
|
||||
class SasVerificationViewModel : ViewModel(),
|
||||
SasVerificationService.SasVerificationListener {
|
||||
|
||||
|
||||
companion object {
|
||||
const val NAVIGATE_FINISH = "NAVIGATE_FINISH"
|
||||
const val NAVIGATE_FINISH_SUCCESS = "NAVIGATE_FINISH_SUCCESS"
|
||||
const val NAVIGATE_SAS_DISPLAY = "NAVIGATE_SAS_DISPLAY"
|
||||
const val NAVIGATE_SUCCESS = "NAVIGATE_SUCCESS"
|
||||
const val NAVIGATE_CANCELLED = "NAVIGATE_CANCELLED"
|
||||
}
|
||||
|
||||
lateinit var sasVerificationService: SasVerificationService
|
||||
|
||||
var otherUserId: String? = null
|
||||
var otherDeviceId: String? = null
|
||||
var otherUser: User? = null
|
||||
var transaction: SasVerificationTransaction? = null
|
||||
|
||||
|
||||
var transactionState: MutableLiveData<SasVerificationTxState> = MutableLiveData()
|
||||
|
||||
init {
|
||||
//Force a first observe
|
||||
transactionState.value = null
|
||||
}
|
||||
|
||||
private var _navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
val navigateEvent: LiveData<LiveEvent<String>>
|
||||
get() = _navigateEvent
|
||||
|
||||
|
||||
var loadingLiveEvent: MutableLiveData<Int> = MutableLiveData()
|
||||
|
||||
var transactionID: String? = null
|
||||
set(value) {
|
||||
if (value != null) {
|
||||
transaction = sasVerificationService.getExistingTransaction(otherUserId!!, value)
|
||||
transactionState.value = transaction?.state
|
||||
otherDeviceId = transaction?.otherDeviceId
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
|
||||
fun initIncoming(session: Session, otherUserId: String, transactionID: String?) {
|
||||
this.sasVerificationService = session.getSasVerificationService()
|
||||
this.otherUserId = otherUserId
|
||||
this.transactionID = transactionID
|
||||
this.sasVerificationService.addListener(this)
|
||||
this.otherUser = session.getUser(otherUserId)
|
||||
if (transactionID == null || transaction == null) {
|
||||
//sanity, this transaction is not known anymore
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
||||
}
|
||||
}
|
||||
|
||||
fun initOutgoing(session: Session, otherUserId: String, otherDeviceId: String) {
|
||||
this.sasVerificationService = session.getSasVerificationService()
|
||||
this.otherUserId = otherUserId
|
||||
this.otherDeviceId = otherDeviceId
|
||||
this.sasVerificationService.addListener(this)
|
||||
this.otherUser = session.getUser(otherUserId)
|
||||
}
|
||||
|
||||
fun beginSasKeyVerification() {
|
||||
val verificationSAS = sasVerificationService.beginKeyVerificationSAS(otherUserId!!, otherDeviceId!!)
|
||||
this.transactionID = verificationSAS
|
||||
}
|
||||
|
||||
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {
|
||||
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
if (transactionID == tx.transactionId) {
|
||||
transactionState.value = tx.state
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
|
||||
|
||||
}
|
||||
|
||||
fun cancelTransaction() {
|
||||
transaction?.cancel()
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
||||
}
|
||||
|
||||
fun finishSuccess() {
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH_SUCCESS)
|
||||
}
|
||||
|
||||
fun manuallyVerified() {
|
||||
if (otherUserId != null && otherDeviceId != null) {
|
||||
sasVerificationService.markedLocallyAsManuallyVerified(otherUserId!!, otherDeviceId!!)
|
||||
}
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH_SUCCESS)
|
||||
}
|
||||
|
||||
|
||||
fun acceptTransaction() {
|
||||
(transaction as? IncomingSasVerificationTransaction)?.performAccept()
|
||||
}
|
||||
|
||||
fun confirmEmojiSame() {
|
||||
transaction?.userHasVerifiedShortCode()
|
||||
}
|
||||
|
||||
fun shortCodeReady() {
|
||||
loadingLiveEvent.value = null
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_SAS_DISPLAY)
|
||||
}
|
||||
|
||||
fun deviceIsVerified() {
|
||||
loadingLiveEvent.value = null
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_SUCCESS)
|
||||
}
|
||||
|
||||
fun navigateCancel() {
|
||||
_navigateEvent.value = LiveEvent(NAVIGATE_CANCELLED)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
sasVerificationService.removeListener(this)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.glide.GlideApp
|
||||
import im.vector.riotredesign.core.glide.GlideRequest
|
||||
@ -55,6 +56,11 @@ object AvatarRenderer {
|
||||
render(roomSummary.avatarUrl, roomSummary.roomId, roomSummary.displayName, imageView)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun render(user: User, imageView: ImageView) {
|
||||
render(imageView.context, GlideApp.with(imageView), user.avatarUrl, user.userId, user.displayName, DrawableImageViewTarget(imageView))
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun render(avatarUrl: String?, identifier: String, name: String?, imageView: ImageView) {
|
||||
render(imageView.context, GlideApp.with(imageView), avatarUrl, identifier, name, DrawableImageViewTarget(imageView))
|
||||
|
@ -66,6 +66,8 @@ class HomeModule {
|
||||
roomMemberItemFactory = RoomMemberItemFactory(get()),
|
||||
roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()),
|
||||
callItemFactory = CallItemFactory(get()),
|
||||
encryptionItemFactory = EncryptionItemFactory(get()),
|
||||
encryptedItemFactory = EncryptedItemFactory(get(), get(), messageItemFactory),
|
||||
defaultItemFactory = DefaultItemFactory()
|
||||
)
|
||||
TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider)
|
||||
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.riotredesign.features.home.room.detail.timeline.factory
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.StyleSpan
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.crypto.MXDecryptionException
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
|
||||
|
||||
class EncryptedItemFactory(
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider,
|
||||
private val messageItemFactory: MessageItemFactory) {
|
||||
|
||||
fun create(timelineEvent: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? {
|
||||
|
||||
return when {
|
||||
EventType.ENCRYPTED == timelineEvent.root.type -> {
|
||||
val decrypted: MXEventDecryptionResult?
|
||||
try {
|
||||
decrypted = session.decryptEvent(timelineEvent.root, "TODO")
|
||||
} catch (e: MXDecryptionException) {
|
||||
val errorDescription =
|
||||
if (e.cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
|
||||
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
|
||||
} else {
|
||||
e.localizedMessage
|
||||
}
|
||||
|
||||
val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
|
||||
|
||||
val spannableStr = SpannableString(message)
|
||||
spannableStr.setSpan(StyleSpan(Typeface.ITALIC), 0, message.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// TODO This is not correct format for error, change it
|
||||
return NoticeItem_()
|
||||
.noticeText(spannableStr)
|
||||
.avatarUrl(timelineEvent.senderAvatar)
|
||||
.memberName(timelineEvent.senderName)
|
||||
}
|
||||
|
||||
if (decrypted == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (decrypted.mClearEvent == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val decryptedTimelineEvent = timelineEvent.copy(root = decrypted.mClearEvent!!)
|
||||
|
||||
// Success
|
||||
return messageItemFactory.create(decryptedTimelineEvent, nextEvent, callback)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.riotredesign.features.home.room.detail.timeline.factory
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
|
||||
|
||||
class EncryptionItemFactory(private val stringProvider: StringProvider) {
|
||||
|
||||
fun create(event: TimelineEvent): NoticeItem? {
|
||||
val text = buildNoticeText(event.root, event.senderName) ?: return null
|
||||
return NoticeItem_()
|
||||
.noticeText(text)
|
||||
.avatarUrl(event.senderAvatar)
|
||||
.memberName(event.senderName)
|
||||
}
|
||||
|
||||
private fun buildNoticeText(event: Event, senderName: String?): CharSequence? {
|
||||
return when {
|
||||
EventType.ENCRYPTION == event.type -> {
|
||||
val content = event.content.toModel<EncryptionEventContent>() ?: return null
|
||||
stringProvider.getString(R.string.notice_end_to_end, senderName, content.algorithm)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -28,6 +28,8 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
||||
private val roomMemberItemFactory: RoomMemberItemFactory,
|
||||
private val roomHistoryVisibilityItemFactory: RoomHistoryVisibilityItemFactory,
|
||||
private val callItemFactory: CallItemFactory,
|
||||
private val encryptionItemFactory: EncryptionItemFactory,
|
||||
private val encryptedItemFactory: EncryptedItemFactory,
|
||||
private val defaultItemFactory: DefaultItemFactory) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
@ -46,8 +48,10 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
||||
EventType.CALL_HANGUP,
|
||||
EventType.CALL_ANSWER -> callItemFactory.create(event)
|
||||
|
||||
EventType.ENCRYPTED,
|
||||
EventType.ENCRYPTION,
|
||||
EventType.ENCRYPTION -> encryptionItemFactory.create(event)
|
||||
|
||||
EventType.ENCRYPTED -> encryptedItemFactory.create(event, nextEvent, callback)
|
||||
|
||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
||||
EventType.STICKER,
|
||||
EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event)
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.lifecycle
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
import im.vector.riotredesign.features.popup.PopupAlertManager
|
||||
|
||||
class VectorActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
PopupAlertManager.onNewActivityDisplayed(activity)
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
}
|
||||
|
||||
}
|
@ -61,7 +61,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent {
|
||||
Matrix.getInstance(context)?.defaultSession?.let { session ->
|
||||
session.dataHandler
|
||||
?.getRoom(roomId)
|
||||
?.markAllAsRead(object : SimpleApiCallback<Void>() {
|
||||
?.markAllAsRead(object : SimpleApiCallback<Unit>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
// Ignore
|
||||
}
|
||||
@ -100,7 +100,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent {
|
||||
|
||||
val event = Event(mxMessage, session.credentials.userId, roomId)
|
||||
room.storeOutgoingEvent(event)
|
||||
room.sendEvent(event, object : ApiCallback<Void?> {
|
||||
room.sendEvent(event, object : MatrixCallback<Void?> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
Timber.d("Send message : onSuccess ")
|
||||
val notifiableMessageEvent = NotifiableMessageEvent(
|
||||
|
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* 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.riotredesign.features.popup
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tapadoo.alerter.Alerter
|
||||
import com.tapadoo.alerter.OnHideAlertListener
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.features.crypto.verification.SASVerificationActivity
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Responsible of displaying important popup alerts on top of the screen.
|
||||
* Alerts are stacked and will be displayed sequentially
|
||||
*/
|
||||
object PopupAlertManager {
|
||||
|
||||
private var weakCurrentActivity: WeakReference<Activity>? = null
|
||||
private var currentAlerter: VectorAlert? = null
|
||||
|
||||
private val alertFiFo = ArrayList<VectorAlert>()
|
||||
|
||||
fun postVectorAlert(alert: VectorAlert) {
|
||||
synchronized(alertFiFo) {
|
||||
alertFiFo.add(alert)
|
||||
}
|
||||
displayNextIfPossible()
|
||||
}
|
||||
|
||||
fun cancelAlert(uid: String) {
|
||||
synchronized(alertFiFo) {
|
||||
alertFiFo.listIterator().apply {
|
||||
while (this.hasNext()) {
|
||||
val next = this.next()
|
||||
if (next.uid == uid) {
|
||||
this.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//it could also be the current one
|
||||
if (currentAlerter?.uid == uid) {
|
||||
Alerter.hide()
|
||||
currentIsDismissed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onNewActivityDisplayed(activity: Activity) {
|
||||
//we want to remove existing popup on previous activity and display it on new one
|
||||
if (currentAlerter != null) {
|
||||
weakCurrentActivity?.get()?.let {
|
||||
Alerter.clearCurrent(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIgnoreActivity(activity)) {
|
||||
return
|
||||
}
|
||||
|
||||
weakCurrentActivity = WeakReference(activity)
|
||||
|
||||
if (currentAlerter != null) {
|
||||
if (currentAlerter!!.expirationTimestamp != null && System.currentTimeMillis() > currentAlerter!!.expirationTimestamp!!) {
|
||||
//this alert has expired, remove it
|
||||
//perform dismiss
|
||||
try {
|
||||
currentAlerter?.dismissedAction?.run()
|
||||
} catch (e: Exception) {
|
||||
Timber.e("## failed to perform action")
|
||||
}
|
||||
currentAlerter = null
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
displayNextIfPossible()
|
||||
}, 2000)
|
||||
} else {
|
||||
showAlert(currentAlerter!!, activity, animate = false)
|
||||
}
|
||||
} else {
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
displayNextIfPossible()
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldIgnoreActivity(activity: Activity) = activity is SASVerificationActivity
|
||||
|
||||
|
||||
private fun displayNextIfPossible() {
|
||||
val currentActivity = weakCurrentActivity?.get()
|
||||
if (Alerter.isShowing || currentActivity == null) {
|
||||
//will retry later
|
||||
return
|
||||
}
|
||||
val next: VectorAlert?
|
||||
synchronized(alertFiFo) {
|
||||
next = alertFiFo.firstOrNull()
|
||||
if (next != null) alertFiFo.remove(next)
|
||||
}
|
||||
currentAlerter = next
|
||||
next?.let {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (next.expirationTimestamp != null && currentTime > next.expirationTimestamp!!) {
|
||||
//skip
|
||||
try {
|
||||
next.dismissedAction?.run()
|
||||
} catch (e: java.lang.Exception) {
|
||||
Timber.e("## failed to perform action")
|
||||
}
|
||||
displayNextIfPossible()
|
||||
} else {
|
||||
showAlert(it, currentActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAlert(alert: VectorAlert, activity: Activity, animate: Boolean = true) {
|
||||
alert.weakCurrentActivity = WeakReference(activity)
|
||||
Alerter.create(activity)
|
||||
.setTitle(alert.title)
|
||||
.setText(alert.description)
|
||||
.apply {
|
||||
|
||||
if (!animate) {
|
||||
setEnterAnimation(R.anim.anim_alerter_no_anim)
|
||||
}
|
||||
|
||||
alert.iconId?.let {
|
||||
setIcon(it)
|
||||
}
|
||||
alert.actions.forEach { action ->
|
||||
addButton(action.title, R.style.AlerterButton, View.OnClickListener {
|
||||
if (action.autoClose) {
|
||||
currentIsDismissed()
|
||||
Alerter.hide()
|
||||
}
|
||||
try {
|
||||
action.action.run()
|
||||
} catch (e: java.lang.Exception) {
|
||||
Timber.e("## failed to perform action")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
setOnClickListener(View.OnClickListener { _ ->
|
||||
alert.contentAction?.let {
|
||||
currentIsDismissed()
|
||||
Alerter.hide()
|
||||
try {
|
||||
it.run()
|
||||
} catch (e: java.lang.Exception) {
|
||||
Timber.e("## failed to perform action")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
.setOnHideListener(OnHideAlertListener {
|
||||
//called when dismissed on swipe
|
||||
try {
|
||||
alert.dismissedAction?.run()
|
||||
} catch (e: java.lang.Exception) {
|
||||
Timber.e("## failed to perform action")
|
||||
}
|
||||
currentIsDismissed()
|
||||
})
|
||||
.enableSwipeToDismiss()
|
||||
.enableInfiniteDuration(true)
|
||||
.setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun currentIsDismissed() {
|
||||
//current alert has been hidden
|
||||
currentAlerter = null
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
displayNextIfPossible()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataclass to describe an important alert with actions.
|
||||
*/
|
||||
data class VectorAlert(val uid: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
@DrawableRes val iconId: Int?) {
|
||||
|
||||
data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
|
||||
|
||||
//will be set by manager, and accessible by actions at runtime
|
||||
var weakCurrentActivity: WeakReference<Activity>? = null
|
||||
|
||||
val actions = ArrayList<Button>()
|
||||
|
||||
var contentAction: Runnable? = null
|
||||
var dismissedAction: Runnable? = null
|
||||
|
||||
/** If this timestamp is after current time, this alert will be skipped */
|
||||
var expirationTimestamp: Long? = null
|
||||
|
||||
fun addButton(title: String, action: Runnable, autoClose: Boolean = true) {
|
||||
actions.add(Button(title, action, autoClose))
|
||||
}
|
||||
|
||||
@ColorRes
|
||||
var colorRes: Int? = null
|
||||
}
|
||||
}
|
@ -199,7 +199,7 @@ object BugReporter {
|
||||
userId = session.sessionParams.credentials.userId
|
||||
deviceId = session.sessionParams.credentials.deviceId ?: "undefined"
|
||||
// TODO matrixSdkVersion = session.getVersion(true);
|
||||
// TODO olmVersion = session.getCryptoVersion(context, true);
|
||||
olmVersion = session.getCryptoVersion(context, true)
|
||||
}
|
||||
|
||||
if (!mIsCancelled) {
|
||||
|
@ -231,7 +231,7 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
|
||||
try {
|
||||
isEnabled = !TextUtils.equals(actions[0] as String, BingRule.ACTION_DONT_NOTIFY)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(LOG_TAG, "## refreshPreferences failed " + e.message, e)
|
||||
Timber.e("## refreshPreferences failed " + e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Typeface
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
@ -42,8 +43,15 @@ import androidx.core.view.isVisible
|
||||
import androidx.preference.*
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
|
||||
import im.vector.matrix.android.api.extensions.sortByLastSeen
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
||||
import im.vector.riotredesign.core.extensions.showPassword
|
||||
import im.vector.riotredesign.core.extensions.withArgs
|
||||
import im.vector.riotredesign.core.platform.SimpleTextWatcher
|
||||
@ -54,9 +62,13 @@ import im.vector.riotredesign.core.preference.UserAvatarPreference
|
||||
import im.vector.riotredesign.core.preference.VectorPreference
|
||||
import im.vector.riotredesign.core.utils.*
|
||||
import im.vector.riotredesign.features.MainActivity
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
import im.vector.riotredesign.features.themes.ThemeUtils
|
||||
import org.koin.android.ext.android.inject
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@ -88,16 +100,16 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
private var mDisplayedEmails = ArrayList<String>()
|
||||
private var mDisplayedPhoneNumber = ArrayList<String>()
|
||||
|
||||
// TODO private var mMyDeviceInfo: DeviceInfo? = null
|
||||
private var mMyDeviceInfo: DeviceInfo? = null
|
||||
|
||||
// TODO private var mDisplayedPushers = ArrayList<Pusher>()
|
||||
|
||||
private var interactionListener: VectorSettingsFragmentInteractionListener? = null
|
||||
|
||||
// devices: device IDs and device names
|
||||
// TODO private var mDevicesNameList: List<DeviceInfo> = ArrayList()
|
||||
private var mDevicesNameList: List<DeviceInfo> = ArrayList()
|
||||
// used to avoid requesting to enter the password for each deletion
|
||||
private var mAccountPassword: String? = null
|
||||
private var mAccountPassword: String = ""
|
||||
|
||||
// current publicised group list
|
||||
private var mPublicisedGroups: MutableSet<String>? = null
|
||||
@ -331,7 +343,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
if (null != newValue && newValue as Boolean != mSession.isURLPreviewEnabled) {
|
||||
displayLoadingView()
|
||||
mSession.setURLPreviewStatus(newValue, object : ApiCallback<Void> {
|
||||
mSession.setURLPreviewStatus(newValue, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
it.isChecked = mSession.isURLPreviewEnabled
|
||||
hideLoadingView()
|
||||
@ -428,7 +440,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
displayLoadingView()
|
||||
|
||||
Matrix.getInstance(activity)?.pushManager?.forceSessionsRegistration(object : ApiCallback<Void> {
|
||||
Matrix.getInstance(activity)?.pushManager?.forceSessionsRegistration(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
hideLoadingView()
|
||||
}
|
||||
@ -497,7 +509,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
/* TODO
|
||||
displayLoadingView()
|
||||
|
||||
mSession.enableCrypto(newValue, object : ApiCallback<Void> {
|
||||
mSession.enableCrypto(newValue, object : MatrixCallback<Unit> {
|
||||
private fun refresh() {
|
||||
activity?.runOnUiThread {
|
||||
hideLoadingView()
|
||||
@ -622,7 +634,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
// olm version
|
||||
findPreference(PreferencesManager.SETTINGS_OLM_VERSION_PREFERENCE_KEY)
|
||||
// TODO .summary = mSession.getCryptoVersion(appContext, false)
|
||||
.summary = mSession.getCryptoVersion(requireContext(), false)
|
||||
|
||||
// copyright
|
||||
findPreference(PreferencesManager.SETTINGS_COPYRIGHT_PREFERENCE_KEY)
|
||||
@ -819,7 +831,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
Matrix.getInstance(context)?.addNetworkEventListener(mNetworkListener)
|
||||
|
||||
mSession.myUser.refreshThirdPartyIdentifiers(object : SimpleApiCallback<Void>() {
|
||||
mSession.myUser.refreshThirdPartyIdentifiers(object : SimpleApiCallback<Unit>() {
|
||||
override fun onSuccess(info: Void?) {
|
||||
// ensure that the activity still exists
|
||||
// and the result is called in the right thread
|
||||
@ -830,7 +842,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
}
|
||||
})
|
||||
|
||||
Matrix.getInstance(context)?.pushManager?.refreshPushersList(Matrix.getInstance(context)?.sessions, object : SimpleApiCallback<Void>(activity) {
|
||||
Matrix.getInstance(context)?.pushManager?.refreshPushersList(Matrix.getInstance(context)?.sessions, object : SimpleApiCallback<Unit>(activity) {
|
||||
override fun onSuccess(info: Void?) {
|
||||
refreshPushersList()
|
||||
}
|
||||
@ -1098,7 +1110,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
/* TODO
|
||||
showPasswordLoadingView(true)
|
||||
|
||||
mSession.updatePassword(oldPwd, newPwd, object : ApiCallback<Void> {
|
||||
mSession.updatePassword(oldPwd, newPwd, object : MatrixCallback<Unit> {
|
||||
private fun onDone(@StringRes textResId: Int) {
|
||||
showPasswordLoadingView(false)
|
||||
|
||||
@ -1172,7 +1184,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
// when using FCM
|
||||
// need to register on servers
|
||||
if (isConnected && pushManager.useFcm() && (pushManager.isServerRegistered || pushManager.isServerUnRegistered)) {
|
||||
val listener = object : ApiCallback<Void> {
|
||||
val listener = object : MatrixCallback<Unit> {
|
||||
|
||||
private fun onDone() {
|
||||
activity?.runOnUiThread {
|
||||
@ -1269,7 +1281,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
if (!TextUtils.equals(mSession.myUser.displayname, value)) {
|
||||
displayLoadingView()
|
||||
|
||||
mSession.myUser.updateDisplayName(value, object : ApiCallback<Void> {
|
||||
mSession.myUser.updateDisplayName(value, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
// refresh the settings value
|
||||
PreferenceManager.getDefaultSharedPreferences(activity).edit {
|
||||
@ -1359,7 +1371,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
when (requestCode) {
|
||||
REQUEST_CALL_RINGTONE -> {
|
||||
REQUEST_CALL_RINGTONE -> {
|
||||
val callRingtoneUri: Uri? = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
val thisActivity = activity
|
||||
if (callRingtoneUri != null && thisActivity != null) {
|
||||
@ -1368,9 +1380,9 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
}
|
||||
}
|
||||
REQUEST_E2E_FILE_REQUEST_CODE -> importKeys(data)
|
||||
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
|
||||
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
|
||||
REQUEST_LOCALE -> {
|
||||
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
|
||||
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
|
||||
REQUEST_LOCALE -> {
|
||||
activity?.let {
|
||||
startActivity(it.intent)
|
||||
it.finish()
|
||||
@ -1394,7 +1406,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
override fun onUploadComplete(uploadId: String?, contentUri: String?) {
|
||||
activity?.runOnUiThread {
|
||||
mSession.myUser.updateAvatarUrl(contentUri, object : ApiCallback<Void> {
|
||||
mSession.myUser.updateAvatarUrl(contentUri, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
onCommonDone(null)
|
||||
refreshDisplay()
|
||||
@ -1494,7 +1506,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
/* TODO
|
||||
displayLoadingView()
|
||||
|
||||
mSession.myUser.delete3Pid(pid, object : ApiCallback<Void> {
|
||||
mSession.myUser.delete3Pid(pid, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
when (pid.medium) {
|
||||
ThreePid.MEDIUM_EMAIL -> refreshEmailsList()
|
||||
@ -1564,7 +1576,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
notImplemented()
|
||||
/* TODO
|
||||
mSession.unIgnoreUsers(idsList, object : ApiCallback<Void> {
|
||||
mSession.unIgnoreUsers(idsList, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
onCommonDone(null)
|
||||
}
|
||||
@ -1652,7 +1664,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
.setPositiveButton(R.string.remove)
|
||||
{ _, _ ->
|
||||
displayLoadingView()
|
||||
pushManager.unregister(mSession, pusher, object : ApiCallback<Void> {
|
||||
pushManager.unregister(mSession, pusher, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
refreshPushersList()
|
||||
onCommonDone(null)
|
||||
@ -1798,7 +1810,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
displayLoadingView()
|
||||
|
||||
mSession.myUser.requestEmailValidationToken(pid, object : ApiCallback<Void> {
|
||||
mSession.myUser.requestEmailValidationToken(pid, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
activity?.runOnUiThread { showEmailValidationDialog(pid) }
|
||||
}
|
||||
@ -1834,7 +1846,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
.setTitle(R.string.account_email_validation_title)
|
||||
.setMessage(R.string.account_email_validation_message)
|
||||
.setPositiveButton(R.string._continue) { _, _ ->
|
||||
mSession.myUser.add3Pid(pid, true, object : ApiCallback<Void> {
|
||||
mSession.myUser.add3Pid(pid, true, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
it.runOnUiThread {
|
||||
hideLoadingView()
|
||||
@ -2153,7 +2165,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
// device name
|
||||
if (null != aMyDeviceInfo) {
|
||||
cryptoInfoDeviceNamePreference.summary = "TODO" // aMyDeviceInfo.display_name
|
||||
cryptoInfoDeviceNamePreference.summary = aMyDeviceInfo.displayName
|
||||
|
||||
cryptoInfoDeviceNamePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
displayDeviceRenameDialog(aMyDeviceInfo)
|
||||
@ -2162,7 +2174,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
cryptoInfoDeviceNamePreference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
|
||||
override fun onPreferenceLongClick(preference: Preference): Boolean {
|
||||
activity?.let { copyToClipboard(it, "TODO") } //aMyDeviceInfo.display_name) }
|
||||
activity?.let { copyToClipboard(it, aMyDeviceInfo.displayName!!) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -2180,8 +2192,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
manageBackupPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
context?.let {
|
||||
notImplemented()
|
||||
// TODO startActivity(KeysBackupManageActivity.intent(it, mSession.myUserId))
|
||||
startActivity(KeysBackupManageActivity.intent(it))
|
||||
}
|
||||
false
|
||||
}
|
||||
@ -2199,48 +2210,45 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
// crypto section: device key (fingerprint)
|
||||
if (!TextUtils.isEmpty(deviceId) && !TextUtils.isEmpty(userId)) {
|
||||
/* TODO
|
||||
mSession.crypto?.getDeviceInfo(userId, deviceId, object : SimpleApiCallback<MXDeviceInfo>() {
|
||||
override fun onSuccess(deviceInfo: MXDeviceInfo?) {
|
||||
if (null != deviceInfo && !TextUtils.isEmpty(deviceInfo.fingerprint()) && null != activity) {
|
||||
cryptoInfoTextPreference.summary = deviceInfo.getFingerprintHumanReadable()
|
||||
mSession.getDeviceInfo(userId, deviceId, object : MatrixCallback<MXDeviceInfo?> {
|
||||
override fun onSuccess(data: MXDeviceInfo?) {
|
||||
if (null != data && !TextUtils.isEmpty(data.fingerprint()) && null != activity) {
|
||||
cryptoInfoTextPreference.summary = data.getFingerprintHumanReadable()
|
||||
|
||||
cryptoInfoTextPreference.setOnPreferenceClickListener {
|
||||
activity?.let { copyToClipboard(it, deviceInfo.fingerprint()) }
|
||||
data.fingerprint()?.let {
|
||||
copyToClipboard(requireActivity(), it)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
sendToUnverifiedDevicesPref.isChecked = false
|
||||
|
||||
/* TODO
|
||||
mSession.crypto?.getGlobalBlacklistUnverifiedDevices(object : SimpleApiCallback<Boolean>() {
|
||||
override fun onSuccess(status: Boolean) {
|
||||
sendToUnverifiedDevicesPref.isChecked = status
|
||||
mSession.getGlobalBlacklistUnverifiedDevices(object : MatrixCallback<Boolean> {
|
||||
override fun onSuccess(data: Boolean) {
|
||||
sendToUnverifiedDevicesPref.isChecked = data
|
||||
}
|
||||
})
|
||||
|
||||
sendToUnverifiedDevicesPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
mSession.crypto?.getGlobalBlacklistUnverifiedDevices(object : SimpleApiCallback<Boolean>() {
|
||||
override fun onSuccess(status: Boolean) {
|
||||
if (sendToUnverifiedDevicesPref.isChecked != status) {
|
||||
mSession.crypto
|
||||
?.setGlobalBlacklistUnverifiedDevices(sendToUnverifiedDevicesPref.isChecked, object : SimpleApiCallback<Void>() {
|
||||
override fun onSuccess(info: Void?) {
|
||||
mSession.getGlobalBlacklistUnverifiedDevices(object : MatrixCallback<Boolean> {
|
||||
override fun onSuccess(data: Boolean) {
|
||||
if (sendToUnverifiedDevicesPref.isChecked != data) {
|
||||
mSession.setGlobalBlacklistUnverifiedDevices(sendToUnverifiedDevicesPref.isChecked, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
true
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//==============================================================================================================
|
||||
@ -2269,32 +2277,20 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO
|
||||
mSession.getDevicesList(object : ApiCallback<DevicesListResponse> {
|
||||
override fun onSuccess(info: DevicesListResponse) {
|
||||
if (info.devices.isEmpty()) {
|
||||
mSession.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
||||
override fun onSuccess(data: DevicesListResponse) {
|
||||
if (data.devices?.isEmpty() == true) {
|
||||
removeDevicesPreference()
|
||||
} else {
|
||||
buildDevicesSettings(info.devices)
|
||||
buildDevicesSettings(data.devices!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
removeDevicesPreference()
|
||||
onCommonDone(e.message)
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
removeDevicesPreference()
|
||||
onCommonDone(e.message)
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
removeDevicesPreference()
|
||||
onCommonDone(e.message)
|
||||
onCommonDone(failure.message)
|
||||
}
|
||||
})
|
||||
*/
|
||||
} else {
|
||||
removeDevicesPreference()
|
||||
removeCryptographyPreference()
|
||||
@ -2314,7 +2310,6 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
var isNewList = true
|
||||
val myDeviceId = mSession.sessionParams.credentials.deviceId
|
||||
|
||||
/* TODO
|
||||
if (aDeviceInfoList.size == mDevicesNameList.size) {
|
||||
isNewList = !mDevicesNameList.containsAll(aDeviceInfoList)
|
||||
}
|
||||
@ -2324,14 +2319,14 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
mDevicesNameList = aDeviceInfoList
|
||||
|
||||
// sort before display: most recent first
|
||||
DeviceInfo.sortByLastSeen(mDevicesNameList)
|
||||
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.device_id) {
|
||||
if (null != myDeviceId && myDeviceId == deviceInfo.deviceId) {
|
||||
mMyDeviceInfo = deviceInfo
|
||||
typeFaceHighlight = Typeface.BOLD
|
||||
} else {
|
||||
@ -2343,16 +2338,16 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
mTypeface = typeFaceHighlight
|
||||
}
|
||||
|
||||
if (null == deviceInfo.device_id && null == deviceInfo.display_name) {
|
||||
if (null == deviceInfo.deviceId && null == deviceInfo.displayName) {
|
||||
continue
|
||||
} else {
|
||||
if (null != deviceInfo.device_id) {
|
||||
preference.title = deviceInfo.device_id
|
||||
if (null != deviceInfo.deviceId) {
|
||||
preference.title = deviceInfo.deviceId
|
||||
}
|
||||
|
||||
// display name parameter can be null (new JSON API)
|
||||
if (null != deviceInfo.display_name) {
|
||||
preference.summary = deviceInfo.display_name
|
||||
if (null != deviceInfo.displayName) {
|
||||
preference.summary = deviceInfo.displayName
|
||||
}
|
||||
}
|
||||
|
||||
@ -2370,7 +2365,6 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
refreshCryptographyPreference(mMyDeviceInfo)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2388,22 +2382,21 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
val layout = inflater.inflate(R.layout.dialog_device_details, null)
|
||||
var textView = layout.findViewById<TextView>(R.id.device_id)
|
||||
|
||||
textView.text = "TODO"//aDeviceInfo.device_id
|
||||
textView.text = aDeviceInfo.deviceId
|
||||
|
||||
// device name
|
||||
textView = layout.findViewById(R.id.device_name)
|
||||
val displayName = "TODO" // if (TextUtils.isEmpty(aDeviceInfo.display_name)) LABEL_UNAVAILABLE_DATA else aDeviceInfo.display_name
|
||||
val displayName = if (TextUtils.isEmpty(aDeviceInfo.displayName)) LABEL_UNAVAILABLE_DATA else aDeviceInfo.displayName
|
||||
textView.text = displayName
|
||||
|
||||
// last seen info
|
||||
textView = layout.findViewById(R.id.device_last_seen)
|
||||
/* TODO
|
||||
if (!TextUtils.isEmpty(aDeviceInfo.last_seen_ip)) {
|
||||
val lastSeenIp = aDeviceInfo.last_seen_ip
|
||||
if (!TextUtils.isEmpty(aDeviceInfo.lastSeenIp)) {
|
||||
val lastSeenIp = aDeviceInfo.lastSeenIp
|
||||
val dateFormatTime = SimpleDateFormat("HH:mm:ss")
|
||||
val time = dateFormatTime.format(Date(aDeviceInfo.last_seen_ts))
|
||||
val time = dateFormatTime.format(Date(aDeviceInfo.lastSeenTs))
|
||||
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
|
||||
val lastSeenTime = dateFormat.format(Date(aDeviceInfo.last_seen_ts)) + ", " + time
|
||||
val lastSeenTime = dateFormat.format(Date(aDeviceInfo.lastSeenTs)) + ", " + time
|
||||
val lastSeenInfo = getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
||||
textView.text = lastSeenInfo
|
||||
} else {
|
||||
@ -2411,7 +2404,6 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
layout.findViewById<View>(R.id.device_last_seen_title).visibility = View.GONE
|
||||
textView.visibility = View.GONE
|
||||
}
|
||||
*/
|
||||
|
||||
// title & icon
|
||||
builder.setTitle(R.string.devices_details_dialog_title)
|
||||
@ -2419,12 +2411,10 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
.setView(layout)
|
||||
.setPositiveButton(R.string.rename) { _, _ -> displayDeviceRenameDialog(aDeviceInfo) }
|
||||
|
||||
/* TODO
|
||||
// disable the deletion for our own device
|
||||
if (!TextUtils.equals(mSession.crypto?.myDevice?.deviceId, aDeviceInfo.device_id)) {
|
||||
if (!TextUtils.equals(mSession.getMyDevice()?.deviceId, aDeviceInfo.deviceId)) {
|
||||
builder.setNegativeButton(R.string.delete) { _, _ -> displayDeviceDeletionDialog(aDeviceInfo) }
|
||||
}
|
||||
*/
|
||||
|
||||
builder.setNeutralButton(R.string.cancel, null)
|
||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||
@ -2449,9 +2439,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
|
||||
|
||||
val input = layout.findViewById<EditText>(R.id.edit_text)
|
||||
notImplemented()
|
||||
/* TODO
|
||||
input.setText(aDeviceInfoToRename.display_name)
|
||||
input.setText(aDeviceInfoToRename.displayName)
|
||||
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.devices_details_device_name)
|
||||
@ -2461,8 +2449,8 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
val newName = input.text.toString()
|
||||
|
||||
mSession.setDeviceName(aDeviceInfoToRename.device_id, newName, object : ApiCallback<Void> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
mSession.setDeviceName(aDeviceInfoToRename.deviceId!!, newName, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
hideLoadingView()
|
||||
|
||||
// search which preference is updated
|
||||
@ -2471,36 +2459,27 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
for (i in 0 until count) {
|
||||
val pref = mDevicesListSettingsCategory.getPreference(i)
|
||||
|
||||
if (TextUtils.equals(aDeviceInfoToRename.device_id, pref.title)) {
|
||||
if (TextUtils.equals(aDeviceInfoToRename.deviceId, pref.title)) {
|
||||
pref.summary = newName
|
||||
}
|
||||
}
|
||||
|
||||
// detect if the updated device is the current account one
|
||||
if (TextUtils.equals(cryptoInfoDeviceIdPreference.summary, aDeviceInfoToRename.device_id)) {
|
||||
if (TextUtils.equals(cryptoInfoDeviceIdPreference.summary, aDeviceInfoToRename.deviceId)) {
|
||||
cryptoInfoDeviceNamePreference.summary = newName
|
||||
}
|
||||
|
||||
// Also change the display name in aDeviceInfoToRename, in case of multiple renaming
|
||||
aDeviceInfoToRename.display_name = newName
|
||||
aDeviceInfoToRename.displayName = newName
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
override fun onFailure(failure: Throwable) {
|
||||
onCommonDone(failure.localizedMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -2511,29 +2490,19 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
*/
|
||||
private fun deleteDevice(deviceId: String) {
|
||||
notImplemented()
|
||||
/* TODO
|
||||
|
||||
// We have to manage registration flow first, to handle what is necessary to delete a devive
|
||||
/*
|
||||
displayLoadingView()
|
||||
mSession.deleteDevice(deviceId, mAccountPassword, object : ApiCallback<Void> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
mSession.deleteDevice(deviceId, mAccountPassword, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
hideLoadingView()
|
||||
refreshDevicesList() // force settings update
|
||||
}
|
||||
|
||||
private fun onError(message: String) {
|
||||
mAccountPassword = null
|
||||
onCommonDone(message)
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
onError(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
onError(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
onError(e.localizedMessage)
|
||||
override fun onFailure(failure: Throwable) {
|
||||
mAccountPassword = ""
|
||||
onCommonDone(failure.localizedMessage)
|
||||
}
|
||||
})
|
||||
*/
|
||||
@ -2546,12 +2515,9 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
* @param aDeviceInfoToDelete device info
|
||||
*/
|
||||
private fun displayDeviceDeletionDialog(aDeviceInfoToDelete: DeviceInfo) {
|
||||
notImplemented()
|
||||
/*
|
||||
TODO
|
||||
if (aDeviceInfoToDelete.device_id != null) {
|
||||
if (aDeviceInfoToDelete.deviceId != null) {
|
||||
if (!TextUtils.isEmpty(mAccountPassword)) {
|
||||
deleteDevice(aDeviceInfoToDelete.device_id)
|
||||
deleteDevice(aDeviceInfoToDelete.deviceId!!)
|
||||
} else {
|
||||
activity?.let {
|
||||
val inflater = it.layoutInflater
|
||||
@ -2568,7 +2534,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
return@OnClickListener
|
||||
}
|
||||
mAccountPassword = passwordEditText.text.toString()
|
||||
deleteDevice(aDeviceInfoToDelete.device_id)
|
||||
deleteDevice(aDeviceInfoToDelete.deviceId!!)
|
||||
})
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
hideLoadingView()
|
||||
@ -2587,21 +2553,20 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
} else {
|
||||
Timber.e("## displayDeviceDeletionDialog(): sanity check failure")
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the e2e keys export.
|
||||
*/
|
||||
private fun exportKeys() {
|
||||
notImplemented()
|
||||
// We need WRITE_EXTERNAL permission
|
||||
/*
|
||||
TODO
|
||||
if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_EXPORT_KEYS)) {
|
||||
activity?.let { activity ->
|
||||
ExportKeysDialog().show(activity, object : ExportKeysDiaLog.ExportKeyDialogListener {
|
||||
ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
|
||||
override fun onPassphrase(passphrase: String) {
|
||||
notImplemented()
|
||||
/*
|
||||
|
||||
displayLoadingView()
|
||||
|
||||
CommonActivityUtils.exportKeys(mSession, passphrase, object : SimpleApiCallback<String>(activity) {
|
||||
@ -2630,11 +2595,11 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
hideLoadingView()
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2642,8 +2607,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private fun importKeys() {
|
||||
notImplemented()
|
||||
// TODO activity?.let { openFileSelection(it, this, false, REQUEST_E2E_FILE_REQUEST_CODE) }
|
||||
activity?.let { openFileSelection(it, this, false, REQUEST_E2E_FILE_REQUEST_CODE) }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2657,103 +2621,100 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
return
|
||||
}
|
||||
|
||||
notImplemented()
|
||||
|
||||
/*
|
||||
TODO
|
||||
val sharedDataItems = ArrayList(RoomMediaMessage.listRoomMediaMessages(intent))
|
||||
val thisActivity = activity
|
||||
val sharedDataItems = ArrayList(RoomMediaMessage.listRoomMediaMessages(intent))
|
||||
val thisActivity = activity
|
||||
|
||||
if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
||||
val sharedDataItem = sharedDataItems[0]
|
||||
val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)
|
||||
val builder = AlertDialog.Builder(thisActivity)
|
||||
.setTitle(R.string.encryption_import_room_keys)
|
||||
.setView(dialogLayout)
|
||||
if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
||||
val sharedDataItem = sharedDataItems[0]
|
||||
val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)
|
||||
val builder = AlertDialog.Builder(thisActivity)
|
||||
.setTitle(R.string.encryption_import_room_keys)
|
||||
.setView(dialogLayout)
|
||||
|
||||
val passPhraseEditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text)
|
||||
val importButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_import_button)
|
||||
val passPhraseEditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text)
|
||||
val importButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_import_button)
|
||||
|
||||
passPhraseEditText.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
passPhraseEditText.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
importButton.isEnabled = !TextUtils.isEmpty(passPhraseEditText.text)
|
||||
}
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
importButton.isEnabled = !TextUtils.isEmpty(passPhraseEditText.text)
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
val importDialog = builder.show()
|
||||
val appContext = thisActivity.applicationContext
|
||||
val importDialog = builder.show()
|
||||
val appContext = thisActivity.applicationContext
|
||||
|
||||
importButton.setOnClickListener(View.OnClickListener {
|
||||
val password = passPhraseEditText.text.toString()
|
||||
val resource = ResourceUtils.openResource(appContext, sharedDataItem.uri, sharedDataItem.getMimeType(appContext))
|
||||
importButton.setOnClickListener(View.OnClickListener {
|
||||
val password = passPhraseEditText.text.toString()
|
||||
val resource = openResource(appContext, sharedDataItem.uri, sharedDataItem.getMimeType(appContext))
|
||||
|
||||
val data: ByteArray
|
||||
if(resource?.mContentStream == null) {
|
||||
appContext.toast("Error")
|
||||
|
||||
try {
|
||||
data = ByteArray(resource.mContentStream.available())
|
||||
resource.mContentStream.read(data)
|
||||
resource.mContentStream.close()
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
resource.mContentStream.close()
|
||||
} catch (e2: Exception) {
|
||||
Timber.e("## importKeys() : " + e2.message, e2)
|
||||
return@OnClickListener
|
||||
}
|
||||
|
||||
val data: ByteArray
|
||||
|
||||
try {
|
||||
data = ByteArray(resource.mContentStream!!.available())
|
||||
resource!!.mContentStream!!.read(data)
|
||||
resource!!.mContentStream!!.close()
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
resource!!.mContentStream!!.close()
|
||||
} catch (e2: Exception) {
|
||||
Timber.e(e2, "## importKeys()")
|
||||
}
|
||||
|
||||
appContext.toast(e.localizedMessage)
|
||||
|
||||
return@OnClickListener
|
||||
}
|
||||
|
||||
displayLoadingView()
|
||||
|
||||
mSession.importRoomKeys(data,
|
||||
password,
|
||||
null,
|
||||
object : MatrixCallback<ImportRoomKeysResult> {
|
||||
override fun onSuccess(info: ImportRoomKeysResult) {
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
|
||||
hideLoadingView()
|
||||
|
||||
info?.let {
|
||||
AlertDialog.Builder(thisActivity)
|
||||
.setMessage(getString(R.string.encryption_import_room_keys_success,
|
||||
it.successfullyNumberOfImportedKeys,
|
||||
it.totalNumberOfKeys))
|
||||
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
appContext.toast(e.localizedMessage)
|
||||
override fun onFailure(failure: Throwable) {
|
||||
appContext.toast(failure.localizedMessage)
|
||||
hideLoadingView()
|
||||
}
|
||||
})
|
||||
|
||||
return@OnClickListener
|
||||
}
|
||||
|
||||
displayLoadingView()
|
||||
|
||||
mSession.crypto?.importRoomKeys(data,
|
||||
password,
|
||||
null,
|
||||
object : ApiCallback<ImportRoomKeysResult> {
|
||||
override fun onSuccess(info: ImportRoomKeysResult?) {
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
|
||||
hideLoadingView()
|
||||
|
||||
info?.let {
|
||||
AlertDialog.Builder(thisActivity)
|
||||
.setMessage(getString(R.string.encryption_import_room_keys_success,
|
||||
it.successfullyNumberOfImportedKeys,
|
||||
it.totalNumberOfKeys))
|
||||
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
appContext.toast(e.localizedMessage)
|
||||
hideLoadingView()
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
appContext.toast(e.localizedMessage)
|
||||
hideLoadingView()
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
appContext.toast(e.localizedMessage)
|
||||
hideLoadingView()
|
||||
}
|
||||
})
|
||||
|
||||
importDiaTimber.dismiss()
|
||||
})
|
||||
}
|
||||
*/
|
||||
importDialog.dismiss()
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//==============================================================================================================
|
||||
@ -2776,7 +2737,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
/*
|
||||
TODO
|
||||
mSession.groupsManager.getUserPublicisedGroups(mSession.myUserId, true, object : ApiCallback<Set<String>> {
|
||||
mSession.groupsManager.getUserPublicisedGroups(mSession.myUserId, true, object : MatrixCallback<Set<String>> {
|
||||
override fun onSuccess(publicisedGroups: Set<String>) {
|
||||
// clear everything
|
||||
mGroupsFlairCategory.removeAll()
|
||||
@ -2849,7 +2810,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
|
||||
if (newValue != isFlaired) {
|
||||
displayLoadingView()
|
||||
mSession.groupsManager.updateGroupPublicity(group.groupId, newValue, object : ApiCallback<Void> {
|
||||
mSession.groupsManager.updateGroupPublicity(group.groupId, newValue, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
hideLoadingView()
|
||||
if (newValue) {
|
||||
@ -2884,15 +2845,10 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO refreshCryptographyPreference(mMyDeviceInfo)
|
||||
refreshCryptographyPreference(mMyDeviceInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
class DeviceInfo {
|
||||
|
||||
}
|
||||
|
||||
private class ClearMediaCacheAsyncTask internal constructor(
|
||||
backgroundTask: () -> Unit,
|
||||
onCompleteTask: () -> Unit
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.riotredesign.features.workers.signout
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
@ -30,13 +31,18 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.utils.toast
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
|
||||
@ -99,13 +105,13 @@ class SignOutBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
|
||||
setupClickableView.setOnClickListener {
|
||||
context?.let { context ->
|
||||
// TODO startActivityForResult(KeysBackupSetupActivity.intent(context, getExtraMatrixID(), true), EXPORT_REQ)
|
||||
startActivityForResult(KeysBackupSetupActivity.intent(context, true), EXPORT_REQ)
|
||||
}
|
||||
}
|
||||
|
||||
activateClickableView.setOnClickListener {
|
||||
context?.let { context ->
|
||||
// TODO startActivity(KeysBackupManageActivity.intent(context, getExtraMatrixID()))
|
||||
startActivity(KeysBackupManageActivity.intent(context))
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,20 +125,19 @@ class SignOutBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
.setTitle(R.string.are_you_sure)
|
||||
.setMessage(R.string.sign_out_bottom_sheet_will_lose_secure_messages)
|
||||
.setPositiveButton(R.string.backup) { _, _ ->
|
||||
/* TODO
|
||||
when (viewModel.keysBackupState.value) {
|
||||
KeysBackupStateManager.KeysBackupState.NotTrusted -> {
|
||||
KeysBackupState.NotTrusted -> {
|
||||
context?.let { context ->
|
||||
startActivity(KeysBackupManageActivity.intent(context, getExtraMatrixID()))
|
||||
startActivity(KeysBackupManageActivity.intent(context))
|
||||
}
|
||||
}
|
||||
KeysBackupStateManager.KeysBackupState.Disabled -> {
|
||||
KeysBackupState.Disabled -> {
|
||||
context?.let { context ->
|
||||
startActivityForResult(KeysBackupSetupActivity.intent(context, getExtraMatrixID(), true), EXPORT_REQ)
|
||||
startActivityForResult(KeysBackupSetupActivity.intent(context, true), EXPORT_REQ)
|
||||
}
|
||||
}
|
||||
KeysBackupStateManager.KeysBackupState.BackingUp,
|
||||
KeysBackupStateManager.KeysBackupState.WillBackUp -> {
|
||||
KeysBackupState.BackingUp,
|
||||
KeysBackupState.WillBackUp -> {
|
||||
//keys are already backing up please wait
|
||||
context?.toast(R.string.keys_backup_is_not_finished_please_wait)
|
||||
}
|
||||
@ -140,7 +145,6 @@ class SignOutBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
//nop
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
.setNegativeButton(R.string.action_sign_out) { _, _ ->
|
||||
onSignOut?.run()
|
||||
@ -165,60 +169,57 @@ class SignOutBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
}
|
||||
})
|
||||
|
||||
/* TODO
|
||||
viewModel.keysBackupState.observe(this, Observer {
|
||||
if (viewModel.keysExportedToFile.value == true) {
|
||||
//ignore this
|
||||
return@Observer
|
||||
}
|
||||
TransitionManager.beginDelayedTransition(rootLayout)
|
||||
when (it) {
|
||||
KeysBackupStateManager.KeysBackupState.ReadyToBackUp -> {
|
||||
signoutClickableView.isVisible = true
|
||||
dontWantClickableView.isVisible = false
|
||||
setupClickableView.isVisible = false
|
||||
activateClickableView.isVisible = false
|
||||
backingUpStatusGroup.isVisible = true
|
||||
|
||||
backupProgress.isVisible = false
|
||||
backupCompleteImage.isVisible = true
|
||||
backupStatusTex.text = getString(R.string.keys_backup_info_keys_all_backup_up)
|
||||
|
||||
sheetTitle.text = getString(R.string.action_sign_out_confirmation_simple)
|
||||
viewModel.keysBackupState.observe(this, Observer {
|
||||
if (viewModel.keysExportedToFile.value == true) {
|
||||
//ignore this
|
||||
return@Observer
|
||||
}
|
||||
KeysBackupStateManager.KeysBackupState.BackingUp,
|
||||
KeysBackupStateManager.KeysBackupState.WillBackUp -> {
|
||||
backingUpStatusGroup.isVisible = true
|
||||
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backing_up)
|
||||
dontWantClickableView.isVisible = true
|
||||
setupClickableView.isVisible = false
|
||||
activateClickableView.isVisible = false
|
||||
TransitionManager.beginDelayedTransition(rootLayout)
|
||||
when (it) {
|
||||
KeysBackupState.ReadyToBackUp -> {
|
||||
signoutClickableView.isVisible = true
|
||||
dontWantClickableView.isVisible = false
|
||||
setupClickableView.isVisible = false
|
||||
activateClickableView.isVisible = false
|
||||
backingUpStatusGroup.isVisible = true
|
||||
|
||||
backupProgress.isVisible = true
|
||||
backupCompleteImage.isVisible = false
|
||||
backupStatusTex.text = getString(R.string.sign_out_bottom_sheet_backing_up_keys)
|
||||
backupProgress.isVisible = false
|
||||
backupCompleteImage.isVisible = true
|
||||
backupStatusTex.text = getString(R.string.keys_backup_info_keys_all_backup_up)
|
||||
|
||||
sheetTitle.text = getString(R.string.action_sign_out_confirmation_simple)
|
||||
}
|
||||
KeysBackupState.BackingUp,
|
||||
KeysBackupState.WillBackUp -> {
|
||||
backingUpStatusGroup.isVisible = true
|
||||
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backing_up)
|
||||
dontWantClickableView.isVisible = true
|
||||
setupClickableView.isVisible = false
|
||||
activateClickableView.isVisible = false
|
||||
|
||||
backupProgress.isVisible = true
|
||||
backupCompleteImage.isVisible = false
|
||||
backupStatusTex.text = getString(R.string.sign_out_bottom_sheet_backing_up_keys)
|
||||
|
||||
}
|
||||
KeysBackupState.NotTrusted -> {
|
||||
backingUpStatusGroup.isVisible = false
|
||||
dontWantClickableView.isVisible = true
|
||||
setupClickableView.isVisible = false
|
||||
activateClickableView.isVisible = true
|
||||
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backup_not_active)
|
||||
}
|
||||
else -> {
|
||||
backingUpStatusGroup.isVisible = false
|
||||
dontWantClickableView.isVisible = true
|
||||
setupClickableView.isVisible = true
|
||||
activateClickableView.isVisible = false
|
||||
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_no_backup)
|
||||
}
|
||||
}
|
||||
KeysBackupStateManager.KeysBackupState.NotTrusted -> {
|
||||
backingUpStatusGroup.isVisible = false
|
||||
dontWantClickableView.isVisible = true
|
||||
setupClickableView.isVisible = false
|
||||
activateClickableView.isVisible = true
|
||||
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backup_not_active)
|
||||
}
|
||||
else -> {
|
||||
backingUpStatusGroup.isVisible = false
|
||||
dontWantClickableView.isVisible = true
|
||||
setupClickableView.isVisible = true
|
||||
activateClickableView.isVisible = false
|
||||
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_no_backup)
|
||||
}
|
||||
}
|
||||
|
||||
// updateSignOutSection()
|
||||
})
|
||||
*/
|
||||
|
||||
// updateSignOutSection()
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
@ -244,14 +245,12 @@ class SignOutBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
/* TODO
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == EXPORT_REQ) {
|
||||
val manualExportDone = data?.getBooleanExtra(KeysBackupSetupActivity.MANUAL_EXPORT, false)
|
||||
viewModel.keysExportedToFile.value = manualExportDone
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
@ -19,12 +19,14 @@ package im.vector.riotredesign.features.workers.signout
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
|
||||
class SignOutViewModel : ViewModel() { // TODO, KeysBackupStateManager.KeysBackupStateListener {
|
||||
class SignOutViewModel : ViewModel(), KeysBackupService.KeysBackupStateListener {
|
||||
// Keys exported manually
|
||||
var keysExportedToFile = MutableLiveData<Boolean>()
|
||||
|
||||
// var keysBackupState = MutableLiveData<KeysBackupStateManager.KeysBackupState>()
|
||||
var keysBackupState = MutableLiveData<KeysBackupState>()
|
||||
|
||||
private var mxSession: Session? = null
|
||||
|
||||
@ -32,60 +34,52 @@ class SignOutViewModel : ViewModel() { // TODO, KeysBackupStateManager.KeysBacku
|
||||
if (mxSession == null) {
|
||||
mxSession = session
|
||||
|
||||
// TODO
|
||||
//mxSession?.crypto
|
||||
// ?.keysBackup
|
||||
// ?.addListener(this)
|
||||
mxSession?.getKeysBackupService()
|
||||
?.addListener(this)
|
||||
}
|
||||
|
||||
//keysBackupState.value = mxSession?.crypto
|
||||
// ?.keysBackup
|
||||
// ?.state
|
||||
keysBackupState.value = mxSession?.getKeysBackupService()
|
||||
?.state
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Safe way to get the current KeysBackup version
|
||||
// */
|
||||
// fun getCurrentBackupVersion(): String {
|
||||
// return mxSession
|
||||
// ?.crypto
|
||||
// ?.keysBackup
|
||||
// ?.currentBackupVersion
|
||||
// ?: ""
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Safe way to get the number of keys to backup
|
||||
// */
|
||||
// fun getNumberOfKeysToBackup(): Int {
|
||||
// return mxSession
|
||||
// ?.crypto
|
||||
// ?.cryptoStore
|
||||
// ?.inboundGroupSessionsCount(false)
|
||||
// ?: 0
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Safe way to tell if there are more keys on the server
|
||||
// */
|
||||
// fun canRestoreKeys(): Boolean {
|
||||
// return mxSession
|
||||
// ?.crypto
|
||||
// ?.keysBackup
|
||||
// ?.canRestoreKeys() == true
|
||||
// }
|
||||
//
|
||||
// override fun onCleared() {
|
||||
// super.onCleared()
|
||||
//
|
||||
// mxSession?.crypto
|
||||
// ?.keysBackup
|
||||
// ?.removeListener(this)
|
||||
// }
|
||||
//
|
||||
// override fun onStateChange(newState: KeysBackupStateManager.KeysBackupState) {
|
||||
// keysBackupState.value = newState
|
||||
// }
|
||||
/**
|
||||
* Safe way to get the current KeysBackup version
|
||||
*/
|
||||
fun getCurrentBackupVersion(): String {
|
||||
return mxSession
|
||||
?.getKeysBackupService()
|
||||
?.currentBackupVersion
|
||||
?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe way to get the number of keys to backup
|
||||
*/
|
||||
fun getNumberOfKeysToBackup(): Int {
|
||||
return mxSession
|
||||
?.inboundGroupSessionsCount(false)
|
||||
?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe way to tell if there are more keys on the server
|
||||
*/
|
||||
fun canRestoreKeys(): Boolean {
|
||||
return mxSession
|
||||
?.getKeysBackupService()
|
||||
?.canRestoreKeys() == true
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
mxSession?.getKeysBackupService()
|
||||
?.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
keysBackupState.value = newState
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
@ -94,17 +88,12 @@ class SignOutViewModel : ViewModel() { // TODO, KeysBackupStateManager.KeysBacku
|
||||
fun doYouNeedToBeDisplayed(session: Session?): Boolean {
|
||||
return false
|
||||
|
||||
/* TODO
|
||||
return session
|
||||
?.crypto
|
||||
?.cryptoStore
|
||||
?.inboundGroupSessionsCount(false)
|
||||
?: 0 > 0
|
||||
&& session
|
||||
?.crypto
|
||||
?.keysBackup
|
||||
?.state != KeysBackupStateManager.KeysBackupState.ReadyToBackUp
|
||||
*/
|
||||
?.getKeysBackupService()
|
||||
?.state != KeysBackupState.ReadyToBackUp
|
||||
}
|
||||
}
|
||||
}
|
10
vector/src/main/res/anim/anim_alerter_no_anim.xml
Normal file
10
vector/src/main/res/anim/anim_alerter_no_anim.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="0"
|
||||
android:interpolator="@anim/interpolator_slight_overshoot">
|
||||
|
||||
<translate
|
||||
android:fromYDelta="0%"
|
||||
android:toYDelta="0%" />
|
||||
|
||||
</set>
|
10
vector/src/main/res/anim/enter_fade_in.xml
Normal file
10
vector/src/main/res/anim/enter_fade_in.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/default_animation_duration"
|
||||
android:shareInterpolator="false">
|
||||
|
||||
<alpha
|
||||
android:fromAlpha="0"
|
||||
android:toAlpha="1" />
|
||||
|
||||
</set>
|
10
vector/src/main/res/anim/enter_from_left.xml
Normal file
10
vector/src/main/res/anim/enter_from_left.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shareInterpolator="false">
|
||||
<translate
|
||||
android:duration="@integer/default_animation_duration"
|
||||
android:fromXDelta="-100%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="0%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
10
vector/src/main/res/anim/enter_from_right.xml
Normal file
10
vector/src/main/res/anim/enter_from_right.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shareInterpolator="false">
|
||||
<translate
|
||||
android:duration="@integer/default_animation_duration"
|
||||
android:fromXDelta="100%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="0%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
10
vector/src/main/res/anim/exit_fade_out.xml
Normal file
10
vector/src/main/res/anim/exit_fade_out.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/default_animation_duration"
|
||||
android:shareInterpolator="false">
|
||||
|
||||
<alpha
|
||||
android:fromAlpha="1"
|
||||
android:toAlpha="0" />
|
||||
|
||||
</set>
|
10
vector/src/main/res/anim/exit_to_left.xml
Normal file
10
vector/src/main/res/anim/exit_to_left.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shareInterpolator="false">
|
||||
<translate
|
||||
android:duration="@integer/default_animation_duration"
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="-100%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
10
vector/src/main/res/anim/exit_to_right.xml
Normal file
10
vector/src/main/res/anim/exit_to_right.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shareInterpolator="false">
|
||||
<translate
|
||||
android:duration="@integer/default_animation_duration"
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="100%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
6
vector/src/main/res/anim/no_anim.xml
Normal file
6
vector/src/main/res/anim/no_anim.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/default_animation_duration"
|
||||
android:shareInterpolator="false">
|
||||
|
||||
</set>
|
BIN
vector/src/main/res/drawable-hdpi/shield.png
Normal file
BIN
vector/src/main/res/drawable-hdpi/shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
vector/src/main/res/drawable-mdpi/shield.png
Normal file
BIN
vector/src/main/res/drawable-mdpi/shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 733 B |
BIN
vector/src/main/res/drawable-xhdpi/shield.png
Normal file
BIN
vector/src/main/res/drawable-xhdpi/shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
vector/src/main/res/drawable-xxhdpi/shield.png
Normal file
BIN
vector/src/main/res/drawable-xxhdpi/shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
vector/src/main/res/drawable-xxxhdpi/shield.png
Normal file
BIN
vector/src/main/res/drawable-xxxhdpi/shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
85
vector/src/main/res/layout/activity.xml
Normal file
85
vector/src/main/res/layout/activity.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/waiting_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?vctr_waiting_background_color"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/waiting_view_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_horizontal_margin"
|
||||
android:background="?attr/colorBackgroundFloating"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_horizontal_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_percent="@dimen/dialog_width_ratio">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/waiting_view_status_circular_progress"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/waiting_view_status_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
tools:text="Waiting status..."
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/waiting_view_status_horizontal_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:visibility="gone"
|
||||
tools:max="100"
|
||||
tools:progress="30"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,92 @@
|
||||
<?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="?attr/colorBackgroundFloating"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/keys_backup_setup_step3_copy_button_title"
|
||||
android:textAlignment="center"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_recovery_key_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="@dimen/layout_horizontal_margin"
|
||||
android:fontFamily="monospace"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
tools:text="HHWJ Y8DK RDR4\nBQEN FQ4V M4F8\nBQEN FQ4V M4A8" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/keys_backup_setup_share"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="50dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:src="@drawable/ic_material_share"
|
||||
android:tint="?colorAccent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/keys_backup_setup_step3_share_recovery_file"
|
||||
android:textSize="17sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/keys_backup_setup_save"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="50dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:src="@drawable/ic_material_save"
|
||||
android:tint="?colorAccent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/keys_backup_setup_step3_save_button_title"
|
||||
android:textSize="17sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
41
vector/src/main/res/layout/dialog_device_delete.xml
Normal file
41
vector/src/main/res/layout/dialog_device_delete.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
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/delete_dialog_info_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/devices_delete_dialog_text"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/devices_delete_pswd"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/delete_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
76
vector/src/main/res/layout/dialog_device_verify.xml
Normal file
76
vector/src/main/res/layout/dialog_device_verify.xml
Normal file
@ -0,0 +1,76 @@
|
||||
<?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: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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/encryption_information_verify_device_warning"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/encryption_information_device_name"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/encrypted_device_info_device_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
tools:text="a device name" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/encryption_information_device_id"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/encrypted_device_info_device_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
tools:text="a device id" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/encryption_information_device_key"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/encrypted_device_info_device_key"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
tools:text="a device key" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/encryption_information_verify_device_warning2"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -18,13 +18,13 @@
|
||||
android:text="@string/encryption_export_notice"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColorHint="?attr/vctr_default_text_hint_color">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/dialog_e2e_keys_passphrase_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -32,10 +32,10 @@
|
||||
android:inputType="textPassword"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/dialog_e2e_keys_confirm_passphrase_til"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -43,7 +43,7 @@
|
||||
android:textColorHint="?attr/vctr_default_text_hint_color"
|
||||
app:errorEnabled="true">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/dialog_e2e_keys_confirm_passphrase_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -51,7 +51,7 @@
|
||||
android:inputType="textPassword"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_e2e_keys_export_button"
|
||||
|
37
vector/src/main/res/layout/dialog_import_e2e_keys.xml
Normal file
37
vector/src/main/res/layout/dialog_import_e2e_keys.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/layout_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?dialogPreferredPadding"
|
||||
android:paddingLeft="?dialogPreferredPadding"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="?dialogPreferredPadding"
|
||||
android:paddingRight="?dialogPreferredPadding"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColorHint="?attr/vctr_default_text_hint_color">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/dialog_e2e_keys_passphrase_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/passphrase_enter_passphrase"
|
||||
android:inputType="textPassword"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_e2e_keys_import_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/encryption_import_import" />
|
||||
</LinearLayout>
|
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/keys_backup_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_shield"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="36dp"
|
||||
android:src="@drawable/key_big"
|
||||
android:tint="?android:textColorTertiary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_restore_with_key"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/keys_backup_restore_with_recovery_key"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_shield" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/keys_backup_key_enter_til"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/keys_backup_import"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_restore_with_key">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/keys_restore_key_enter_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/keys_backup_restore_key_enter_hint"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textNoSuggestions|textMultiLine"
|
||||
android:maxLines="3"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:text="EsTy 7CiZ Zqpj eqFq Wjz1 kzfS 59DE uZyA wt7b rhBE viyt kb1p" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_import"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_import_black"
|
||||
android:tint="?attr/colorAccent"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/keys_backup_key_enter_til"
|
||||
app:layout_constraintTop_toTopOf="@id/keys_backup_key_enter_til" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_restore_key_help_with_link"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/keys_backup_restore_with_key_helper"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_key_enter_til" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_restore_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
||||
android:minWidth="200dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/keys_backup_unlock_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_restore_key_help_with_link"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/keys_backup_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_shield"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="36dp"
|
||||
android:src="@drawable/key_big"
|
||||
android:tint="?android:textColorTertiary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_restore_with_passphrase"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/keys_backup_restore_with_passphrase"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_shield" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/keys_backup_passphrase_enter_til"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/keys_backup_view_show_password"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_restore_with_passphrase">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/keys_backup_passphrase_enter_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/passphrase_enter_passphrase"
|
||||
android:maxLines="3"
|
||||
android:singleLine="false"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:inputType="textPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_view_show_password"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_eye_black"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/keys_backup_passphrase_enter_til"
|
||||
app:layout_constraintTop_toTopOf="@+id/keys_backup_passphrase_enter_til" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_passphrase_help_with_link"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_passphrase_enter_til"
|
||||
tools:text="@string/keys_backup_restore_with_passphrase_helper_with_link" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_restore_with_passphrase_submit"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
|
||||
android:minWidth="200dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/keys_backup_unlock_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_passphrase_help_with_link" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_shield"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="36dp"
|
||||
android:src="@drawable/key_big"
|
||||
android:tint="?android:textColorTertiary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_restore_success"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_shield"
|
||||
tools:text="@string/keys_backup_restore_success_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_restore_success_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_restore_success"
|
||||
tools:text="@string/keys_backup_restore_success_description" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_setup_done_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_margin="20dp"
|
||||
android:minWidth="200dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/done"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_restore_success_info" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
10
vector/src/main/res/layout/fragment_keys_backup_settings.xml
Normal file
10
vector/src/main/res/layout/fragment_keys_backup_settings.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/keys_backup_settings_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="@dimen/layout_vertical_margin"
|
||||
android:paddingBottom="@dimen/layout_vertical_margin_big"
|
||||
tools:listitem="@layout/item_notification_troubleshoot" />
|
@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".features.crypto.keysbackup.setup.KeysBackupSetupStep1Fragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_setup_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||
android:src="@drawable/backup_keys"
|
||||
android:tint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/keys_backup_setup_step1_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.1"
|
||||
app:layout_constraintVertical_chainStyle="spread" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step1_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/keys_backup_setup_step1_title"
|
||||
android:textAlignment="center"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/keys_backup_setup_step1_description"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_setup_image"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step1_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/keys_backup_setup_step1_description"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintBottom_toTopOf="@+id/keys_backup_setup_step1_button"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_setup_step1_title" />
|
||||
|
||||
<!-- Centered button -->
|
||||
<Button
|
||||
android:id="@+id/keys_backup_setup_step1_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_margin="16dp"
|
||||
android:minWidth="200dp"
|
||||
android:padding="8dp"
|
||||
android:text="@string/keys_backup_setup"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_setup_step1_description" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step1_advanced"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:text="@string/keys_backup_setup_step1_advanced"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_setup_step1_button"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_setup_step1_manualExport"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_margin="16dp"
|
||||
android:minWidth="200dp"
|
||||
android:padding="8dp"
|
||||
android:text="@string/keys_backup_setup_step1_manual_export"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_setup_step1_advanced"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
151
vector/src/main/res/layout/fragment_keys_backup_setup_step2.xml
Normal file
151
vector/src/main/res/layout/fragment_keys_backup_setup_step2.xml
Normal file
@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/keys_backup_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".features.crypto.keysbackup.setup.KeysBackupSetupStep2Fragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/keys_backup_setup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step2_text_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/keys_backup_setup_step2_text_title"
|
||||
android:textAlignment="center"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step2_text_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/keys_backup_setup_step2_text_description"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_setup_step2_text_title" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/keys_backup_passphrase_enter_til"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/keys_backup_view_show_password"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_setup_step2_text_description">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/keys_backup_passphrase_enter_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/passphrase_create_passphrase"
|
||||
android:maxLines="3"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_view_show_password"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_eye_black"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/keys_backup_passphrase_enter_til"
|
||||
app:layout_constraintTop_toTopOf="@id/keys_backup_passphrase_enter_til" />
|
||||
|
||||
<im.vector.riotredesign.core.ui.views.PasswordStrengthBar
|
||||
android:id="@+id/keys_backup_passphrase_security_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/keys_backup_passphrase_enter_til"
|
||||
app:layout_constraintStart_toStartOf="@id/keys_backup_passphrase_enter_til"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_passphrase_enter_til" />
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/keys_backup_passphrase_confirm_til"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_weight="1"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/keys_backup_view_show_password"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_passphrase_security_progress">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/keys_backup_passphrase_confirm_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/passphrase_confirm_passphrase"
|
||||
android:maxLines="3"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_setup_step2_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:text="@string/keys_backup_setup_step2_button_title"
|
||||
android:textColor="@android:color/white"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_passphrase_confirm_til" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_recovery_key_alternative"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:text="@string/keys_backup_setup_step1_recovery_key_alternative"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keys_backup_setup_step2_button" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_setup_step2_skip_button"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:text="@string/keys_backup_setup_step2_skip_button_title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/keys_backup_setup_recovery_key_alternative" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
@ -0,0 +1,92 @@
|
||||
<?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:id="@+id/keys_backup_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".features.crypto.keysbackup.setup.KeysBackupSetupStep3Fragment">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/keys_backup_setup_step3_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keys_backup_setup_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="60dp"
|
||||
android:src="@drawable/backup_keys"
|
||||
android:tint="?android:attr/textColorTertiary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step3_success_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
||||
android:text="@string/keys_backup_setup_step3_success_title"
|
||||
android:textAlignment="center"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step3_line1_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
||||
android:text="@string/keys_backup_setup_step3_text_line1"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_setup_step3_line2_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/keys_backup_setup_step3_text_line2" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keys_backup_recovery_key_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_horizontal_margin"
|
||||
android:fontFamily="monospace"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
android:visibility="gone"
|
||||
tools:text="HHWJ Y8DK RDR4\nBQEN FQ4V M4F8\nBQEN FQ4V M4A8"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_setup_step3_copy_button"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/keys_backup_setup_step3_copy_button_title" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_setup_step3_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin"
|
||||
android:minWidth="200dp"
|
||||
tools:text="@string/keys_backup_setup_step3_button_title" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_emoji_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||
android:gravity="center"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/sas_emoji_description" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_emoji_description_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:gravity="center"
|
||||
android:text="@string/sas_security_advise"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_emoji_description" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_decimal_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/sas_emoji_grid"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/sas_emoji_grid"
|
||||
tools:text="1234-4320-3905"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<GridLayout
|
||||
android:id="@+id/sas_emoji_grid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:columnCount="@integer/number_of_emoji_per_line"
|
||||
android:padding="@dimen/layout_vertical_margin"
|
||||
android:useDefaultMargins="true"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_description_2"
|
||||
tools:visibility="visible">
|
||||
|
||||
<include
|
||||
android:id="@+id/emoji0"
|
||||
layout="@layout/item_emoji_verif" />
|
||||
|
||||
<include
|
||||
android:id="@+id/emoji1"
|
||||
layout="@layout/item_emoji_verif" />
|
||||
|
||||
<include
|
||||
android:id="@+id/emoji2"
|
||||
layout="@layout/item_emoji_verif" />
|
||||
|
||||
<include
|
||||
android:id="@+id/emoji3"
|
||||
layout="@layout/item_emoji_verif" />
|
||||
|
||||
<include
|
||||
android:id="@+id/emoji4"
|
||||
layout="@layout/item_emoji_verif" />
|
||||
|
||||
<include
|
||||
android:id="@+id/emoji5"
|
||||
layout="@layout/item_emoji_verif" />
|
||||
|
||||
<include
|
||||
android:id="@+id/emoji6"
|
||||
layout="@layout/item_emoji_verif" />
|
||||
|
||||
</GridLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_request_continue_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:minWidth="160dp"
|
||||
android:text="@string/_continue"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_request_cancel_button"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:text="@string/cancel"
|
||||
app:layout_constraintEnd_toStartOf="@+id/sas_request_continue_button"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_incoming_request_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/sas_incoming_request_title"
|
||||
android:textAlignment="center"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sas_incoming_request_user_avatar"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sas_incoming_request_title"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_incoming_request_user_display_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_avatar"
|
||||
tools:text="User name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_incoming_request_user_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_display_name"
|
||||
tools:text="\@foo:matrix.org" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_incoming_request_user_device"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_id"
|
||||
tools:text="Device: Mobile" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_incoming_request_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:text="@string/sas_incoming_request_description"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_device" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_incoming_request_description_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:text="@string/sas_incoming_request_description_2"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_description" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_request_continue_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:layout_marginEnd="@dimen/layout_vertical_margin"
|
||||
android:layout_marginRight="@dimen/layout_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin"
|
||||
android:minWidth="160dp"
|
||||
android:text="@string/_continue"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sas_incoming_request_description_2" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_request_cancel_button"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="@dimen/layout_vertical_margin"
|
||||
android:layout_marginRight="@dimen/layout_vertical_margin"
|
||||
android:text="@string/cancel"
|
||||
app:layout_constraintEnd_toStartOf="@+id/sas_request_continue_button"
|
||||
app:layout_constraintTop_toTopOf="@+id/sas_request_continue_button" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
@ -0,0 +1,90 @@
|
||||
<?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="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_verification_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/sas_verify_title"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_verification_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/sas_security_advise"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_cancel_button"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/cancel" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/layout_horizontal_margin">
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_start_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:minWidth="160dp"
|
||||
android:text="@string/sas_verify_start_button_title" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/sas_start_button_loading"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_legacy_verification"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_gravity="end"
|
||||
android:layout_margin="@dimen/layout_horizontal_margin"
|
||||
android:text="@string/sas_legacy_verification_button_title"
|
||||
android:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_verifying_keys"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/sas_verifying_keys"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_verification_verified_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/sas_verified"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_verification_verified_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:gravity="center"
|
||||
android:text="@string/sas_verified_successful"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_verification_verified_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sas_verification_verified_description_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/layout_vertical_margin"
|
||||
android:gravity="center"
|
||||
android:text="@string/sas_verified_successful_description"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sas_verification_verified_description" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sas_verification_verified_done_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:layout_marginEnd="@dimen/layout_vertical_margin"
|
||||
android:layout_marginRight="@dimen/layout_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin"
|
||||
android:minWidth="160dp"
|
||||
android:text="@string/sas_got_it"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sas_verification_verified_description_2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
28
vector/src/main/res/layout/item_emoji_verif.xml
Normal file
28
vector/src/main/res/layout/item_emoji_verif.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?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="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- size in dp, because we do not want the display to be impacted by font size setting -->
|
||||
<TextView
|
||||
android:id="@+id/item_emoji_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="40dp"
|
||||
tools:ignore="SpUsage"
|
||||
tools:text="🌵" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_emoji_name_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="4dp"
|
||||
tools:text="@string/verification_emoji_cactus" />
|
||||
|
||||
</LinearLayout>
|
98
vector/src/main/res/layout/item_generic_list.xml
Normal file
98
vector/src/main/res/layout/item_generic_list.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:minHeight="50dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_generic_title_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Item Title"
|
||||
tools:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_generic_description_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/item_generic_action_button"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/item_generic_title_text"
|
||||
tools:text="At totam delectus et aliquid dolorem. Consectetur voluptas tempore et non blanditiis id optio. Dolorum impedit quidem minus nihil. "
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/item_generic_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="item_generic_accessory_image,item_generic_progress_bar"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_generic_accessory_image"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/item_generic_title_text"
|
||||
tools:srcCompat="@drawable/e2e_warning"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/item_generic_progress_bar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/item_generic_title_text"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/item_generic_action_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="?attr/colorAccent"
|
||||
android:padding="8dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/item_generic_description_text"
|
||||
tools:text="@string/settings_troubleshoot_test_device_settings_quickfix"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/keys_backup_settings_footer_button1"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:minWidth="200dp"
|
||||
android:visibility="gone"
|
||||
tools:text="@string/keys_backup_settings_restore_backup_button"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/keys_backup_settings_footer_button2"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:minWidth="200dp"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/vector_warning_color"
|
||||
tools:text="@string/keys_backup_settings_delete_backup_button"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
104
vector/src/main/res/layout/view_keys_backup_banner.xml
Normal file
104
vector/src/main/res/layout/view_keys_backup_banner.xml
Normal file
@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?vctr_list_header_background_color"
|
||||
android:minHeight="67dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/view_keys_backup_banner_picto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="19dp"
|
||||
android:layout_marginLeft="19dp"
|
||||
android:src="@drawable/key_small"
|
||||
android:tint="?android:textColorTertiary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_keys_backup_banner_text_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="27dp"
|
||||
android:layout_marginLeft="27dp"
|
||||
android:text="@string/keys_backup_banner_setup_line1"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/view_keys_backup_banner_text_2"
|
||||
app:layout_constraintEnd_toStartOf="@id/view_keys_backup_banner_barrier"
|
||||
app:layout_constraintStart_toEndOf="@id/view_keys_backup_banner_picto"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_keys_backup_banner_text_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="27dp"
|
||||
android:layout_marginLeft="27dp"
|
||||
android:text="@string/keys_backup_banner_setup_line2"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/view_keys_backup_banner_barrier"
|
||||
app:layout_constraintStart_toEndOf="@id/view_keys_backup_banner_picto"
|
||||
app:layout_constraintTop_toBottomOf="@id/view_keys_backup_banner_text_1"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/view_keys_backup_banner_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="view_keys_backup_banner_close,view_keys_backup_banner_loading"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/view_keys_backup_banner_loading"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="14dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Trick to align the close picto to top of text -->
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/view_keys_backup_banner_close_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="view_keys_backup_banner_close,view_keys_backup_banner_close_image"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
android:id="@+id/view_keys_backup_banner_close"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/view_keys_backup_banner_close_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:src="@drawable/ic_small_close"
|
||||
app:layout_constraintEnd_toEndOf="@id/view_keys_backup_banner_close"
|
||||
app:layout_constraintStart_toStartOf="@id/view_keys_backup_banner_close"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?vctr_line_divider"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
43
vector/src/main/res/layout/view_password_strength_bar.xml
Normal file
43
vector/src/main/res/layout/view_password_strength_bar.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge 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"
|
||||
tools:orientation="horizontal"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<View
|
||||
android:id="@+id/password_strength_bar_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_weight="1"
|
||||
tools:background="@color/password_strength_bar_weak" />
|
||||
|
||||
<View
|
||||
android:id="@+id/password_strength_bar_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_weight="1"
|
||||
tools:background="@color/password_strength_bar_low" />
|
||||
|
||||
<View
|
||||
android:id="@+id/password_strength_bar_3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_weight="1"
|
||||
tools:background="@color/password_strength_bar_ok" />
|
||||
|
||||
<View
|
||||
android:id="@+id/password_strength_bar_4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_weight="1"
|
||||
tools:background="@color/password_strength_bar_strong" />
|
||||
|
||||
</merge>
|
@ -41,8 +41,8 @@
|
||||
<string name="room_no_conference_call_in_encrypted_rooms">مكالمات الاجتماعات ليست مدعومة في الغرفة المعمّاة</string>
|
||||
<string name="send_anyway">أرسِل بأي حال</string>
|
||||
<string name="action_sign_out">اخرج</string>
|
||||
<string name="action_voice_call">مكالمة صورية</string>
|
||||
<string name="action_video_call">مكالمة صوتية</string>
|
||||
<string name="action_voice_call">مكالمة صوتية</string>
|
||||
<string name="action_video_call">مكالمة صورية</string>
|
||||
<string name="action_global_search">بحث عمومي</string>
|
||||
<string name="action_mark_all_as_read">علّمها كلّها كمقروءة</string>
|
||||
<string name="action_open">افتح</string>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<string name="dark_theme">Тъмна тема</string>
|
||||
<string name="black_them">Черна тема</string>
|
||||
|
||||
<string name="notification_sync_in_progress">Синхронизиране</string>
|
||||
<string name="notification_sync_in_progress">Синхронизиране…</string>
|
||||
<string name="title_activity_home">Съобщения</string>
|
||||
<string name="title_activity_room">Стая</string>
|
||||
<string name="title_activity_settings">Настройки</string>
|
||||
@ -1358,8 +1358,8 @@
|
||||
|
||||
<string name="autodiscover_invalid_response">Невалиден отговор при опит за откриване на адреса на сървъра</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Опции за откриване на сървър</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot откри конфигурация за собствен сървър за домейна от потребителското Ви име \"%s\":
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot откри конфигурация за собствен сървър за домейна от потребителското Ви име \"%1$s\":
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Използвай конфигурацията</string>
|
||||
|
||||
<string name="notification_sync_init">Инициализиране на услугата</string>
|
||||
@ -1388,4 +1388,6 @@
|
||||
<string name="notification_sender_me">Аз</string>
|
||||
<string name="notification_inline_reply_failed">** Неуспешно изпращане - моля, отворете стаята</string>
|
||||
|
||||
<string name="error_jitsi_not_supported_on_old_device">Извиняваме се, но конферентни разговори с Jitsi не се поддържат на стари устройства (устройства с Android OS под 5.0)</string>
|
||||
|
||||
</resources>
|
||||
|
@ -8,7 +8,7 @@
|
||||
<string name="black_them">কালো থিম</string>
|
||||
<string name="status_theme">Status.im থিম</string>
|
||||
|
||||
<string name="notification_sync_in_progress">সিংক্রোনাইজ হচ্ছে</string>
|
||||
<string name="notification_sync_in_progress">সিংক্রোনাইজ হচ্ছে…</string>
|
||||
<string name="notification_listening_for_events">ইভেন্টের জন্য শোনা হচ্ছে</string>
|
||||
<string name="notification_noisy_notifications">সশব্দ বিজ্ঞপ্তিগুলি</string>
|
||||
<string name="notification_silent_notifications">নীরব বিজ্ঞপ্তিগুলি</string>
|
||||
@ -411,7 +411,7 @@
|
||||
<string name="room_participants_create">সৃষ্টি</string>
|
||||
|
||||
<string name="room_participants_online">অনলাইন</string>
|
||||
<string name="room_participants_offline">অফলিনে</string>
|
||||
<string name="room_participants_offline">অফলাইন</string>
|
||||
<string name="room_participants_idle">অলস</string>
|
||||
<string name="room_participants_now">এখুন %1$s</string>
|
||||
<string name="room_participants_ago">%1$s %2$s পূর্বে</string>
|
||||
@ -499,14 +499,14 @@
|
||||
<string name="room_details_title">ঘরের বিস্তারিত</string>
|
||||
<string name="room_details_people">লোকজন</string>
|
||||
<string name="room_details_files">নথিগুলি</string>
|
||||
<string name="room_details_settings">নির্ধারণ</string>
|
||||
<string name="room_details_settings">সেটিংস</string>
|
||||
<plurals name="room_details_selected">
|
||||
<item quantity="one">%d নির্বাচিত</item>
|
||||
<item quantity="other" />
|
||||
</plurals>
|
||||
<string name="malformed_id">বিকৃত পরিচয়।একটি ইমেইল ঠিকানা বা একটি মাধ্যমিক পরিচয় হতে হবে যেমন \'@localpart:domain\'</string>
|
||||
<string name="room_details_people_invited_group_name">আমন্ত্রিত</string>
|
||||
<string name="room_details_people_present_group_name">যোগকরা</string>
|
||||
<string name="room_details_people_present_group_name">যোগ করেছেন</string>
|
||||
|
||||
<string name="room_event_action_report_prompt_reason">বিষয়বস্তুর বিবরণী দেয়ার জন কারণ</string>
|
||||
<string name="room_event_action_report_prompt_ignore_user">ব্যাবহারকারির কাছ থেকে আপনি কি সব বার্তা লোকাতে চান\?উল্লেখ্য এই প্রতিক্রিয়াটি চালু করবেন এবং এটি কিছু সময় নিতে পারে।</string>
|
||||
@ -556,7 +556,7 @@
|
||||
<string name="room_settings_add_homescreen_shortcut">ঘরের পর্দার ছোটোখাটো যোগ করুন</string>
|
||||
|
||||
<string name="room_sliding_menu_messages">বার্তা</string>
|
||||
<string name="room_sliding_menu_settings">নির্ধারণ</string>
|
||||
<string name="room_sliding_menu_settings">সেটিংস</string>
|
||||
<string name="room_sliding_menu_version">সংস্করণ</string>
|
||||
<string name="room_sliding_menu_version_x">সংস্করণ %s</string>
|
||||
<string name="room_sliding_menu_term_and_conditions">শর্তাবলী</string>
|
||||
@ -583,20 +583,21 @@
|
||||
<string name="settings_troubleshoot_diagnostic">ডায়াগনস্টিকস এর সমস্যা সমাধান</string>
|
||||
<string name="settings_troubleshoot_diagnostic_run_button_title">পরীক্ষাগুলি চালাও</string>
|
||||
<string name="settings_troubleshoot_diagnostic_running_status">"(%1$d of %2$d) চলছে… "</string>
|
||||
<string name="settings_troubleshoot_test_account_settings_failed">বিজ্ঞপ্তি আপনার অ্যাকাউন্টের জন্য নিষ্ক্রিয় করা হয়েছে।</string>
|
||||
<string name="settings_troubleshoot_test_account_settings_failed">বিজ্ঞপ্তি আপনার অ্যাকাউন্টের জন্য নিষ্ক্রিয় করা হয়েছে।
|
||||
\nঅনুগ্রহ করে একাউন্ট সেটিংস যাচাই করে নিন।</string>
|
||||
<string name="settings_troubleshoot_test_account_settings_quickfix">সক্ষম</string>
|
||||
|
||||
<string name="settings_troubleshoot_test_device_settings_title">যন্ত্র সেটিংস।</string>
|
||||
<string name="settings_troubleshoot_test_device_settings_success">বিজ্ঞপ্তি এই ডিভাইসের জন্য সক্রিয় করা হয়েছে।</string>
|
||||
<string name="settings_troubleshoot_test_device_settings_failed">বিজ্ঞপ্তি এই ডিভাইসের জন্য অনুমতি দেওয়া হয় নি।
|
||||
\nRiot এর সেটিংস চেক করুন।</string>
|
||||
<string name="settings_troubleshoot_test_device_settings_failed">বিজ্ঞপ্তি এই ডিভাইসের জন্য অনুমতি দেওয়া হয় নি।
|
||||
\nRiot এর সেটিংস যাচাই করুন।</string>
|
||||
<string name="settings_troubleshoot_test_device_settings_quickfix">সক্ষম</string>
|
||||
|
||||
<string name="settings_troubleshoot_test_bing_settings_title">কাস্টম সেটিংস।</string>
|
||||
<string name="settings_troubleshoot_test_bing_settings_success_with_warn">লক্ষ্য করুন যে কিছু বার্তা টাইপ নীরব করা হয়েছে (কোন শব্দ ছাড়াই একটি বিজ্ঞপ্তি তৈরি করবে)।</string>
|
||||
<string name="settings_troubleshoot_test_bing_settings_failed">কিছু বিজ্ঞপ্তি আপনার কাস্টম সেটিংস এ নিষ্ক্রিয় করা হয়েছে।</string>
|
||||
<string name="settings_troubleshoot_test_bing_settings_failed_to_load_rules">কাস্টম নিয়ম লোড করতে ব্যর্থ হয়েছে, আবার চেষ্টা করুন।</string>
|
||||
<string name="settings_troubleshoot_test_bing_settings_quickfix">সেটিংস চেক করুন</string>
|
||||
<string name="settings_troubleshoot_test_bing_settings_quickfix">সেটিংস যাচাই করুন</string>
|
||||
|
||||
<string name="settings_troubleshoot_test_play_services_title">Play Services পরীক্ষা</string>
|
||||
<string name="settings_troubleshoot_test_play_services_success">গুগল প্লে সার্ভিসেস APK পাওয়া গেছে এবং আপ টু ডেট রয়েছে।</string>
|
||||
@ -611,8 +612,8 @@
|
||||
\n%1$s</string>
|
||||
<string name="settings_troubleshoot_test_fcm_failed_too_many_registration">[%1$s]
|
||||
\nএই ত্রুটিটি Riot এর নিয়ন্ত্রণের বাইরে এবং Google এর মতে, এই ত্রুটিটি ইঙ্গিত করে যে ডিভাইসটিতে FCM এর সাথে নিবন্ধিত অনেকগুলি অ্যাপ্লিকেশন রয়েছে। ত্রুটিগুলি কেবলমাত্র অ্যাপ্লিকেশনের চরম সংখ্যাগুলিতে ঘটে থাকে, তাই এটি গড় ব্যবহারকারীকে প্রভাবিত করবে না।</string>
|
||||
<string name="settings_troubleshoot_test_fcm_failed_service_not_available">[%1$s]
|
||||
\nএই ত্রুটি Riot এর নিয়ন্ত্রণ বাইরে। এটা বিভিন্ন কারণে ঘটতে পারে। আপনি পরে পুনরায় চেষ্টা করলে হয়তো এটি কাজ করবে, আপনি এটিও পরীক্ষা করতে পারেন যে Google Play পরিষেবাটি সিস্টেম সেটিংসে ডেটা ব্যবহারের ক্ষেত্রে সীমাবদ্ধ নয়, অথবা আপনার ডিভাইসের ঘড়ি সঠিক, বা এটি কাস্টম রমতে ঘটতে পারে।</string>
|
||||
<string name="settings_troubleshoot_test_fcm_failed_service_not_available">[%1$s]
|
||||
\nএই ত্রুটি Riot এর নিয়ন্ত্রণের বাইরে। এটা বিভিন্ন কারণে ঘটতে পারে। আপনি পরে পুনরায় চেষ্টা করলে হয়তো এটি কাজ করবে, আপনি এটিও পরীক্ষা করতে পারেন যে Google Play পরিষেবাটি সিস্টেম সেটিংসে ডেটা ব্যবহারের ক্ষেত্রে সীমাবদ্ধ নয়, অথবা আপনার ডিভাইসের ঘড়ি সঠিক, বা এটি কাস্টম রমতে ঘটতে পারে।</string>
|
||||
<string name="settings_troubleshoot_test_fcm_failed_account_missing">[%1$s]
|
||||
\nএই ত্রুটি Riot এর নিয়ন্ত্রণের বাইরে। ফোনে কোন গুগল একাউন্ট নেই। অ্যাকাউন্ট ম্যানেজার খুলুন এবং একটি গুগল একাউন্ট যোগ করুন।</string>
|
||||
<string name="settings_troubleshoot_test_fcm_failed_account_missing_quick_fix">একাউন্ট যোগ করুন</string>
|
||||
@ -657,13 +658,49 @@
|
||||
<string name="settings_troubleshoot_diagnostic_failure_status_with_quickfix">এক অথবা অধিক পরীক্ষা বার্থ হয়েছে,প্রস্তাবিত ঠিক করে চেষ্টা করুন(es).</string>
|
||||
<string name="settings_troubleshoot_diagnostic_failure_status_no_quickfix">এক অথবা অধিক পরীক্ষা ব্যর্থ হয়েছে,দয়া করে জমা করুন একটা গুরুত্বপূর্ণ খসড়া যেটা সাহায্য করবে অনুসন্ধান করতে।</string>
|
||||
|
||||
<string name="settings_troubleshoot_test_system_settings_title">পদ্ধতি নির্ধারণ।</string>
|
||||
<string name="settings_troubleshoot_test_system_settings_success">বিজ্ঞাপ্তিকে পদ্ধতি নির্ধারণ এর মাধ্যমে সক্রিয় করা হয়েছে।</string>
|
||||
<string name="settings_troubleshoot_test_system_settings_failed">বিজ্ঞাপ্তিকে পদ্ধতি নির্ধারণ এর মাধ্যমে নিষ্ক্রিয় করা হয়েছে।
|
||||
\nদেয়া করে দেখে নিন পদ্ধতি নির্ধারণ।</string>
|
||||
<string name="open_settings">খুলুন নির্ধারণটি</string>
|
||||
<string name="settings_troubleshoot_test_system_settings_title">সিস্টেমের সেটিংস।</string>
|
||||
<string name="settings_troubleshoot_test_system_settings_success">বিজ্ঞাপ্তিকে সিস্টেমের সেটিংস এর মাধ্যমে সক্রিয় করা হয়েছে।</string>
|
||||
<string name="settings_troubleshoot_test_system_settings_failed">বিজ্ঞাপ্তিকে সিস্টেমের সেটিংস এর মাধ্যমে নিষ্ক্রিয় করা হয়েছে।
|
||||
\nদেয়া করে সিস্টেমের সেটিংসগুলি যাচাই করে নিন।</string>
|
||||
<string name="open_settings">সেটিংস খুলুন</string>
|
||||
|
||||
<string name="settings_troubleshoot_test_account_settings_title">গণনা নির্ধারণ।</string>
|
||||
<string name="settings_troubleshoot_test_account_settings_title">অক্কোউন্টের সেটিংস।</string>
|
||||
<string name="settings_troubleshoot_test_account_settings_success">বিজ্ঞাপ্তি আপনার একাউন্টএর জন্য সক্রিয় করা হোক.</string>
|
||||
<string name="action_mark_room_read">পঠিত হিসেবে চিহ্নিত</string>
|
||||
<string name="settings_user_settings">ব্যবহারকারী সেটিংস</string>
|
||||
<string name="settings_inline_url_preview">ইনলাইন URL পূর্বরূপ</string>
|
||||
<string name="settings_inline_url_preview_summary">বার্তাগুলি মধ্যে থাকা লিংকগুলি প্রিভিউ করে যখন আপনার হোম সার্ভার এই বৈশিষ্ট্য টি সাপোর্ট করে।</string>
|
||||
<string name="settings_send_typing_notifs">টাইপিং বিজ্ঞপ্তি পাঠান</string>
|
||||
<string name="settings_preview_media_before_sending">পাঠানোর আগে মিডিয়া প্রিভিউ কর</string>
|
||||
<string name="settings_interface_language">ভাষা</string>
|
||||
<string name="settings_select_language">ভাষা বেছে নিন</string>
|
||||
|
||||
<string name="settings_theme">থিম</string>
|
||||
|
||||
<string name="encryption_information_verify_device_warning">এই ডিভাইসটি বিশ্বাসযোগ্য হতে পারে তা যাচাই করতে, অন্য কোন উপায়ে (যেমন ব্যক্তি বা ফোন কল) ব্যবহার করে তার মালিকের সাথে যোগাযোগ করুন এবং এই ডিভাইসটির জন্য তাদের ব্যবহারকারী সেটিংসে এ কুঞ্জি দেখছে তা তাদের জিজ্ঞাসা করুন নীচের কুঞ্জিটির সাথে মেলে কিনা:</string>
|
||||
<string name="e2e_enabling_on_app_update">দাঙ্গা এখন শেষ-থেকে-শেষ এনক্রিপশন সমর্থন করে তবে এটি সক্ষম করতে আপনাকে আবার লগ ইন করতে হবে।
|
||||
\n
|
||||
\nআপনি এখন বা পরে এপ্লিকেশন সেটিংস থেকে এটি করতে পারেন।</string>
|
||||
|
||||
<string name="font_size">অক্ষর এর আকার</string>
|
||||
<string name="you_added_a_new_device">আপনি একটি নতুন ডিভাইস \'%s\' যোগ করেছেন, যা এনক্রিপশন কীগুলির জন্য অনুরোধ করছে।</string>
|
||||
<string name="command_description_clear_scalar_token">ম্যাট্রিক্স অ্যাপ্লিকেশন ব্যবস্থাপনা ঠিক করতে</string>
|
||||
|
||||
<string name="encrypted_message">এনক্রিপ্ট করা বার্তা</string>
|
||||
|
||||
<string name="joined">যোগ করেছেন</string>
|
||||
<string name="has_been_kicked">আপনি %1$s থেকে %2$s দ্বারা লাথি খেয়েছেন</string>
|
||||
<string name="merged_events_expand">সম্প্রসারিত</string>
|
||||
<string name="merged_events_collapse">বন্ধ</string>
|
||||
|
||||
<string name="keys_backup_restore_with_key_helper">আপনার পুনরুদ্ধারের কুঞ্জি হারিয়ে গেছে\? আপনি সেটিংস একটি নতুন সেট আপ করতে পারেন।</string>
|
||||
<string name="keys_backup_restore_success_description">"%1$d সেশন কী পুনরুদ্ধার করা হয়েছে, এবং %2$d নতুন কী (গুলি) জোড়া হয়েছে যা এই ডিভাইসটি জানত না"</string>
|
||||
<plurals name="keys_backup_restore_success_description_part2">
|
||||
<item quantity="one">%d টি নতুন কী এই ডিভাইসে যোগ করা হয়েছে।</item>
|
||||
<item quantity="other">%d টি নতুন কী এই ডিভাইসে যোগ করা হয়েছে।</item>
|
||||
</plurals>
|
||||
|
||||
<string name="new_recovery_method_popup_description">একটি নতুন সুরক্ষিত বার্তা কুঞ্জি ব্যাকআপ সনাক্ত করা হয়েছে।
|
||||
\n
|
||||
\nআপনি যদি নতুন পুনরুদ্ধারের পদ্ধতি সেট না করে থাকেন তবে একজন আক্রমণকারী আপনার অ্যাকাউন্ট অ্যাক্সেস করার চেষ্টা করছেন। আপনার অ্যাকাউন্ট পাসওয়ার্ড পরিবর্তন করুন এবং সেটিংসে অবিলম্বে একটি নতুন পুনরুদ্ধার পদ্ধতি সেট করুন।</string>
|
||||
</resources>
|
||||
|
@ -1197,7 +1197,7 @@ Versuche die Anwendung neuzustarten.</string>
|
||||
|
||||
<string name="no_valid_google_play_services_apk">Keine validen Google-Play-Dienste gefunden. Benachrichtigungen könnten nicht richtig funktionieren.</string>
|
||||
|
||||
<string name="store_title">Riot.im - Kommunizierte auf deine Weise</string>
|
||||
<string name="store_title">Riot.im - Kommuniziere auf deine Weise</string>
|
||||
<string name="store_short_description">Eine universelle, sichere Chat-App - komplett unter deiner Kontrolle.</string>
|
||||
<string name="store_full_description">"Eine Chat-App unter deiner Kontrolle und total flexibel. Riot lässt dich auf die Art kommunizieren wie du willst. Die App wurde gemacht für [matrix] - dem Standard für offene, dezentrale Komunikation.
|
||||
|
||||
@ -1423,8 +1423,8 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A
|
||||
|
||||
<string name="autodiscover_invalid_response">Ungültige Antwort beim Entdecken des Heimservers</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Serveroptionen vervollständigen</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot hat eine benutzerdefinierte Serverkonfiguration für die Domäne deines Benutzernamens gefunden \"%s\":
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot hat eine benutzerdefinierte Serverkonfiguration für die Domäne deines Benutzernamens gefunden \"%1$s\":
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Nutze Konfiguration</string>
|
||||
|
||||
<string name="notification_sync_init">Initialisiere Dienst</string>
|
||||
|
@ -663,7 +663,7 @@ Gailu ezezagunak:</string>
|
||||
<string name="dark_theme">Itxura iluna</string>
|
||||
<string name="black_them">Itxura beltza</string>
|
||||
|
||||
<string name="notification_sync_in_progress">Sinkronizatzen</string>
|
||||
<string name="notification_sync_in_progress">Sinkronizatzen…</string>
|
||||
<string name="notification_listening_for_events">Entzun gertaerak</string>
|
||||
|
||||
<string name="login_mobile_device">Mugikorra</string>
|
||||
@ -1376,8 +1376,8 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada.</string>
|
||||
|
||||
<string name="autodiscover_invalid_response">Baliogabeko hasiera-zerbitzari deskubritze erantzuna</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Automatikoki osatu zerbitzariaren aukerak</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">"Riot-ek pertsonalizatutako zerbitzari konfigurazio bat antzeman du zure erabiltzaile id-arentzat \"%s\" domeinuan:
|
||||
\n%s"</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">"Riot-ek pertsonalizatutako zerbitzari konfigurazio bat antzeman du zure erabiltzaile id-arentzat \"%1$s\" domeinuan:
|
||||
\n%2$s"</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Erabili konfigurazioa</string>
|
||||
|
||||
<string name="action_mark_room_read">Markatu irakurritako gisa</string>
|
||||
@ -1398,4 +1398,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada.</string>
|
||||
<string name="notification_sender_me">Ni</string>
|
||||
<string name="notification_inline_reply_failed">** Bidalketak huts egin du, ireki gela</string>
|
||||
|
||||
<string name="error_jitsi_not_supported_on_old_device">Sentitzen dugu, gailu zaharretan ezin dira Jitsi bidezko konferentzia deiak egin (Android OS 5.0 baino zaharragoak)</string>
|
||||
|
||||
</resources>
|
||||
|
@ -24,14 +24,14 @@
|
||||
<string name="resend">ارسال مجدد</string>
|
||||
<string name="quote">نقل قول</string>
|
||||
<string name="share">اشتراک گذاری</string>
|
||||
<string name="later">بعدا</string>
|
||||
<string name="later">بعداً</string>
|
||||
<string name="view_source">مشاهده منبع</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="rename">تغییر نام</string>
|
||||
<string name="report_content">گزارش محتوا</string>
|
||||
<string name="active_call">تماس فعال</string>
|
||||
<string name="ongoing_conference_call_voice">صدا</string>
|
||||
<string name="ongoing_conference_call_video">ویدیو</string>
|
||||
<string name="ongoing_conference_call_voice">صوتی</string>
|
||||
<string name="ongoing_conference_call_video">تصویری</string>
|
||||
<string name="device_information">اطلاعات دستگاه</string>
|
||||
<string name="send_anyway">به هر حال ارسال کنید</string>
|
||||
<string name="or">یا</string>
|
||||
@ -56,7 +56,7 @@
|
||||
<string name="bottom_action_rooms">اتاقها</string>
|
||||
<string name="low_priority_header">اولویت کم</string>
|
||||
|
||||
<string name="direct_chats_header">گفتگو</string>
|
||||
<string name="direct_chats_header">گفتگوها</string>
|
||||
<string name="no_conversation_placeholder">مکالمهای نیست</string>
|
||||
<string name="rooms_header">اتاقها</string>
|
||||
<plurals name="public_room_nb_users">
|
||||
@ -70,11 +70,11 @@
|
||||
<string name="send_bug_report">گزارش اشکال</string>
|
||||
<string name="read_receipt">خواندن</string>
|
||||
|
||||
<string name="join_room">عضویت در اتاق</string>
|
||||
<string name="join_room">پیوستن به اتاق</string>
|
||||
<string name="username">نام کاربری</string>
|
||||
<string name="create_account">ثبتنام</string>
|
||||
<string name="create_account">ساخت حساب</string>
|
||||
<string name="login">ورود</string>
|
||||
<string name="logout">خروج</string>
|
||||
<string name="logout">خروج از حساب</string>
|
||||
<string name="search">جستجو</string>
|
||||
|
||||
<string name="start_new_chat">شروع گپ جدید</string>
|
||||
@ -82,17 +82,17 @@
|
||||
<string name="start_video_call">شروع تماس تصویری</string>
|
||||
|
||||
<string name="option_take_photo_video">عکس یا فیلم بگیرید</string>
|
||||
<string name="option_take_photo">عکس گرفتن</string>
|
||||
<string name="option_take_photo">عکس بگیرید</string>
|
||||
<string name="option_take_video">فیلم بگیرید</string>
|
||||
|
||||
<string name="auth_login">ورود</string>
|
||||
<string name="auth_register">ثبت نام</string>
|
||||
<string name="auth_register">ساخت حساب</string>
|
||||
<string name="auth_submit">ارسال</string>
|
||||
<string name="auth_skip">رد کردن</string>
|
||||
<string name="auth_send_reset_email">ارسال ایمیل مجدد</string>
|
||||
<string name="auth_skip">رد شدن</string>
|
||||
<string name="auth_send_reset_email">ارسال ایمیل بازیابی</string>
|
||||
<string name="auth_user_id_placeholder">ایمیل یا نام کاربری</string>
|
||||
<string name="auth_password_placeholder">رمز عبور</string>
|
||||
<string name="auth_new_password_placeholder">رمز عبور جدید</string>
|
||||
<string name="auth_password_placeholder">گذرواژه</string>
|
||||
<string name="auth_new_password_placeholder">گذرواژه جدید</string>
|
||||
<string name="auth_user_name_placeholder">نام کاربری</string>
|
||||
<string name="auth_email_placeholder">آدرس ایمیل</string>
|
||||
<string name="auth_opt_email_placeholder">آدرس ایمیل (اختیاری)</string>
|
||||
@ -102,9 +102,9 @@
|
||||
<string name="auth_repeat_new_password_placeholder">رمز عبور جدید خود را تأیید کنید</string>
|
||||
<string name="auth_invalid_login_param">نام کاربری و/یا رمز عبور نامعتبر میباشد</string>
|
||||
<string name="auth_invalid_password">رمز عبور خیلی کوتاه است (حداقل ۶)</string>
|
||||
<string name="auth_invalid_email">آدرس ایمیل به نظر میرسد معتبر نباشد</string>
|
||||
<string name="auth_invalid_phone">شماره تلفن به نظر میرسد معتبر نباشد</string>
|
||||
<string name="auth_password_dont_match">رمز عبورها مطابقت ندارد</string>
|
||||
<string name="auth_invalid_email">آدرس ایمیل نامعتبر به نظر میرسد</string>
|
||||
<string name="auth_invalid_phone">شماره تلفن نامعتبر به نظر میرسد</string>
|
||||
<string name="auth_password_dont_match">گذرواژهها مطابقت ندارد</string>
|
||||
<string name="auth_forgot_password">رمز عبور را فراموش کردید؟</string>
|
||||
<string name="auth_use_server_options">از گزینههای سرور سفارشی استفاده کنید (پیشرفته)</string>
|
||||
<string name="auth_email_validation_message">لطفا ایمیل خود را برای ادامه ثبت نام بررسی کنید</string>
|
||||
@ -116,7 +116,7 @@
|
||||
<string name="login_error_user_in_use">این نام کاربری قبلا استفاده شده است</string>
|
||||
<string name="groups_list">فهرست گروهها</string>
|
||||
|
||||
<string name="compression_options">"ارسال به عنوان "</string>
|
||||
<string name="compression_options">ارسال به عنوان</string>
|
||||
<string name="compression_opt_list_original">اصلی</string>
|
||||
<string name="compression_opt_list_large">بزرگ</string>
|
||||
<string name="compression_opt_list_medium">متوسط</string>
|
||||
@ -129,8 +129,8 @@
|
||||
<string name="today">امروز</string>
|
||||
|
||||
<string name="room_info_room_name">نام اتاق</string>
|
||||
<string name="notification_sync_in_progress">همگامسازی</string>
|
||||
<string name="call_anyway">به هر حال تماس بگیر</string>
|
||||
<string name="notification_sync_in_progress">همگامسازی…</string>
|
||||
<string name="call_anyway">به هر حال تماس بگیرید</string>
|
||||
<string name="missing_permissions_title_to_start_conf_call">نمیتوان تماس را شروع کرد</string>
|
||||
<string name="cannot_start_call">نمیتوان تماس را شروع کرد، لطفاً بعداً تلاش نمایید</string>
|
||||
<string name="accept">پذیرفتن</string>
|
||||
@ -144,7 +144,7 @@
|
||||
<string name="bottom_action_groups">جامعهها</string>
|
||||
|
||||
<string name="home_filter_placeholder_home">جستجوی اتاقها</string>
|
||||
<string name="home_filter_placeholder_favorites">جستجوی پسندیدهها</string>
|
||||
<string name="home_filter_placeholder_favorites">جستجوی پسندها</string>
|
||||
<string name="home_filter_placeholder_people">جستجوی افراد</string>
|
||||
<string name="home_filter_placeholder_rooms">جستجوی اتاقها</string>
|
||||
<string name="home_filter_placeholder_groups">جستجوی جامعهها</string>
|
||||
@ -155,7 +155,7 @@
|
||||
<string name="local_address_book_header">مخاطبین من</string>
|
||||
<string name="user_directory_header">فهرست کاربران</string>
|
||||
<string name="matrix_only_filter">فقط نمایش مخاطبین ماتریکس</string>
|
||||
<string name="no_contact_access_placeholder">شما اجازهی دسترسی به مخاطبین را به Riot ندادهاید</string>
|
||||
<string name="no_contact_access_placeholder">شما اجازهی دسترسی به مخاطبین را به برنامه ندادهاید</string>
|
||||
<string name="no_result_placeholder">نتیجهای نیست</string>
|
||||
|
||||
<string name="rooms_directory_header">فهرست اتاقها</string>
|
||||
@ -189,8 +189,7 @@
|
||||
<string name="option_send_files">ارسال فایلها</string>
|
||||
<string name="option_send_sticker">ارسال استیکر</string>
|
||||
<string name="no_sticker_application_dialog_content">شما بستهی استیکر فعالی ندارید.
|
||||
|
||||
تمایل دارید بستهی استیکر جدید اضافه کنید؟</string>
|
||||
\nتمایل دارید بستهی استیکر جدیدی اضافه کنید؟</string>
|
||||
|
||||
<string name="go_on_with">ادامه با…</string>
|
||||
<string name="error_no_external_application_found">متاسفانه برنامهای روی گوشی شما برای انجام این کار پیدا نشد.</string>
|
||||
@ -223,4 +222,78 @@
|
||||
<string name="reject">رد کردن</string>
|
||||
|
||||
<string name="list_members">اعضای لیست</string>
|
||||
<string name="call_connected">تماس برقرار شد</string>
|
||||
<string name="call_connecting">برقراری تماس…</string>
|
||||
<string name="call_ended">تماس پایان یافت</string>
|
||||
<string name="call_ring">شمارهگیری…</string>
|
||||
<string name="incoming_call">تماس ورودی</string>
|
||||
<string name="incoming_video_call">تماس ویدئویی ورودی</string>
|
||||
<string name="incoming_voice_call">تماس صوتی ورودی</string>
|
||||
<string name="call_in_progress">تماس در جریان است…</string>
|
||||
<string name="video_call_in_progress">تماس ویدئویی در جریان است…</string>
|
||||
|
||||
<string name="call_error_user_not_responding">طرف مقابل پاسخ نداد.</string>
|
||||
<string name="call_error_ice_failed">برقراری ارتباط چندرسانهای ممکن نشد</string>
|
||||
<string name="call_error_camera_init_failed">راهاندازی دوربین ممکن نبود</string>
|
||||
<string name="call_error_answered_elsewhere">پاسخ تماس از کاربری نامعتبر دریافت شد</string>
|
||||
|
||||
<plurals name="room_title_members">
|
||||
<item quantity="one">%d عضو</item>
|
||||
<item quantity="other">%d عضو</item>
|
||||
</plurals>
|
||||
<string name="room_title_one_member">۱ عضو</string>
|
||||
|
||||
<plurals name="format_time_s">
|
||||
<item quantity="one">%d ثانیه</item>
|
||||
<item quantity="other">%d ثانیه</item>
|
||||
</plurals>
|
||||
<plurals name="format_time_m">
|
||||
<item quantity="one">%d دقیقه</item>
|
||||
<item quantity="other">%d دقیقه</item>
|
||||
</plurals>
|
||||
<plurals name="format_time_h">
|
||||
<item quantity="one">%d ساعت</item>
|
||||
<item quantity="other">%d ساعت</item>
|
||||
</plurals>
|
||||
<plurals name="format_time_d">
|
||||
<item quantity="one">%d روز</item>
|
||||
<item quantity="other">%d روز</item>
|
||||
</plurals>
|
||||
|
||||
<string name="room_participants_leave_prompt_title">ترک اتاق</string>
|
||||
<string name="room_participants_leave_prompt_msg">آیا از ترک این اتاق اطمینان دارید؟</string>
|
||||
<string name="room_participants_remove_prompt_msg">آیا میخواهید %s را از این گفتگو حذف کنید؟</string>
|
||||
<string name="room_participants_create">ساخت</string>
|
||||
|
||||
<string name="room_participants_online">آنلاین</string>
|
||||
<string name="room_participants_offline">آفلاین</string>
|
||||
<string name="room_participants_idle">بیکار</string>
|
||||
<string name="room_participants_now">در حال حاضر %1$s</string>
|
||||
<string name="room_participants_ago">%1$s در %2$s پیش</string>
|
||||
|
||||
<string name="room_participants_header_admin_tools">ابزارهای مدیر</string>
|
||||
<string name="room_participants_header_call">تماس</string>
|
||||
<string name="room_participants_header_direct_chats">پیامهای شخصی</string>
|
||||
<string name="room_participants_header_devices">دستگاهها</string>
|
||||
|
||||
<string name="room_participants_action_invite">دعوت</string>
|
||||
<string name="room_participants_action_leave">ترک این اتاق</string>
|
||||
<string name="room_participants_action_remove">حذف از این اتاق</string>
|
||||
<string name="stay">ماندن</string>
|
||||
<string name="redact">حذف</string>
|
||||
<string name="download">دریافت</string>
|
||||
<string name="ongoing_conference_call">کنفرانس در حال برگزاری است.
|
||||
\nبه صورت %1$s یا %2$s به آن بپیوندید</string>
|
||||
<string name="missing_permissions_warning">به خاطر نداشتن مجوز دسترسی، برخی امکانات ممکن است در دسترس نباشند…</string>
|
||||
<string name="missing_permissions_error">به خاطر نداشتن مجوز دسترسی، این اقدام ممکن نیست.</string>
|
||||
<string name="missing_permissions_to_start_conf_call">برای آغاز کنفرانس نیاز به دسترسی دعوت اعضا دارید</string>
|
||||
<string name="room_no_conference_call_in_encrypted_rooms">تماس گروهی در گفتگوهای رمز شده ممکن نیست</string>
|
||||
<string name="skip">رد شدن</string>
|
||||
<string name="done">انجام شد</string>
|
||||
<string name="abort">انصراف</string>
|
||||
<string name="ignore">نادیدهگیری</string>
|
||||
|
||||
<string name="action_sign_out_confirmation_simple">آیا میخواهید از حساب کاربری خود خارج شوید؟</string>
|
||||
<string name="action_mark_room_read">علامتگذاری به عنوان خوانده شده</string>
|
||||
<string name="auth_login_sso">ورود با سامانههای احراز هویت مرکزی</string>
|
||||
</resources>
|
||||
|
@ -348,7 +348,7 @@ Salli Riotin käyttää yhteystietojasi?</string>
|
||||
<string name="room_two_users_are_typing">%1$s ja %2$s kirjoittavat…</string>
|
||||
<string name="room_many_users_are_typing">%1$s, %2$s ja muut kirjoittavat…</string> <!-- ??? this ain't right -->
|
||||
<string name="room_message_placeholder_encrypted">Lähetä salattu viesti…</string>
|
||||
<string name="room_message_placeholder_not_encrypted">Lähetä viesti (ei salattu)…</string>
|
||||
<string name="room_message_placeholder_not_encrypted">Lähetä viesti (salaamaton)…</string>
|
||||
<string name="room_offline_notification">Yhteys palvelimeen katkesi.</string>
|
||||
<string name="room_unsent_messages_notification">Viesteja ei lähetetty. %1$s tai %2$s?</string>
|
||||
<string name="room_unknown_devices_messages_notification">Viestejä ei lähetetty koska huoneessa on tuntemattomia laitteita. %1$s tai %2$s?</string>
|
||||
@ -597,9 +597,9 @@ Salli Riotin käyttää yhteystietojasi?</string>
|
||||
<string name="room_settings_copy_room_address">Kopioi huoneen osoite</string>
|
||||
|
||||
<string name="room_settings_addresses_e2e_enabled">Tämä huone on salattu.</string>
|
||||
<string name="room_settings_addresses_e2e_disabled">Tämä huone ei ole salattu.</string>
|
||||
<string name="room_settings_addresses_e2e_encryption_warning">Käytä salausta
|
||||
(HUOM! Salausta ei voi poistaa käytöstä!)</string>
|
||||
<string name="room_settings_addresses_e2e_disabled">Tämä huone ei käytä salausta.</string>
|
||||
<string name="room_settings_addresses_e2e_encryption_warning">Ota salaus käyttöön
|
||||
\n(varoitus: salausta ei voi poistaa käytöstä!)</string>
|
||||
|
||||
<!-- Directory -->
|
||||
<string name="directory_title">Luettelo</string>
|
||||
@ -1409,8 +1409,8 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös
|
||||
\nJos et asettanut uutta palautustapaa, hyökkääjä saattaa yrittää päästä käsiksi tunnukseesi. Vaihda tunnuksesi salasana ja aseta uusi palautustapa asetuksissa välittömästi.</string>
|
||||
<string name="autodiscover_invalid_response">Epäkelpo kotipalvelimen löytövastaus</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Automaattitäydennyksen palvelinasetukset</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot löysi mukautetun palvelinasetuksen userId:si domainille ”%s”:
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot löysi mukautetun palvelinasetuksen userId:si domainille ”%1$s”:
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Käytä asetuksia</string>
|
||||
|
||||
<string name="notification_sync_init">Alustetaan palvelua</string>
|
||||
|
@ -1373,8 +1373,8 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq
|
||||
|
||||
<string name="autodiscover_invalid_response">Réponse de découverte du serveur d’accueil non valide</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Auto-compléter les options du serveur</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot a détecté une configuration de serveur personnalisée pour le domaine de votre identifiant « %s » :
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot a détecté une configuration de serveur personnalisée pour le domaine de votre identifiant « %1$s » :
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Utiliser la configuration</string>
|
||||
|
||||
<string name="notification_sync_init">Initialisation du service</string>
|
||||
|
@ -1372,8 +1372,8 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
|
||||
|
||||
<string name="autodiscover_invalid_response">A Matrix szerver felderítésére érvénytelen válasz érkezett</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Szerver beállítások automatikus kiegészítése</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot egyedi szerver beállítást észlelt a felhasználói azonosítód domain-jéhez: \"%s\":
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot egyedi szerver beállítást észlelt a felhasználói azonosítód domain-jéhez: \"%1$s\":
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Beállítás használata</string>
|
||||
|
||||
<string name="notification_sync_init">Szolgáltatás indítása</string>
|
||||
|
@ -1417,8 +1417,8 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."</stri
|
||||
|
||||
<string name="autodiscover_invalid_response">Risposta alla scoperta di un homeserver non valida</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Opzioni autocompletamento server</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot ha rilevato una configurazione server personalizzata per il tuo dominio userId \"%s\":
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot ha rilevato una configurazione server personalizzata per il tuo dominio userId \"%1$s\":
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Usa configurazione</string>
|
||||
|
||||
<string name="notification_sync_init">Inizializzazione del servizio</string>
|
||||
|
@ -1450,8 +1450,8 @@
|
||||
|
||||
<string name="autodiscover_invalid_response">Ongeldig thuisserverontdekkingsantwoord</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Serveropties automatisch aanvullen</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot heeft een aangepaste serverconfiguratie gedetecteerd voor uw gebruikers-ID-domein ‘%s’:
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot heeft een aangepaste serverconfiguratie gedetecteerd voor uw gebruikers-ID-domein ‘%1$s’:
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Configuratie gebruiken</string>
|
||||
|
||||
<string name="error_jitsi_not_supported_on_old_device">Sorry, vergadergesprekken met Jitsi worden nog niet ondersteund op oudere apparaten (met een Android-versie lager dan 5.0)</string>
|
||||
|
@ -1331,8 +1331,8 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani
|
||||
<string name="new_recovery_method_popup_was_me">Unë qeshë</string>
|
||||
<string name="autodiscover_invalid_response">Përgjigje e pavlefshme zbulimi shërbyesi Home</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_title">Mundësi Vetëplotësimi Shërbyesi</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot-i pikasi një formësim shërbyesi të përshtatur për përkatësinë tuaj userId \"%s\":
|
||||
\n%s</string>
|
||||
<string name="autodiscover_well_known_autofill_dialog_message">Riot-i pikasi një formësim shërbyesi të përshtatur për përkatësinë tuaj userId \"%1$s\":
|
||||
\n%2$s</string>
|
||||
<string name="autodiscover_well_known_autofill_confirm">Përdor Formësim</string>
|
||||
|
||||
<string name="notification_sync_init">Po gatitet shërbimi</string>
|
||||
|
6
vector/src/main/res/values-w480dp/integers.xml
Normal file
6
vector/src/main/res/values-w480dp/integers.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<integer name="number_of_emoji_per_line">7</integer>
|
||||
|
||||
</resources>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user