forked from GitHub-Mirror/riotX-android
Import keys: WIP
This commit is contained in:
parent
99d2e8388a
commit
907a1d1a4b
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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.intent
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipDescription
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.TextUtils
|
||||||
|
import androidx.core.util.PatternsCompat.WEB_URL
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired from Riot code: RoomMediaMessage.java
|
||||||
|
*/
|
||||||
|
sealed class ExternalIntentData {
|
||||||
|
/**
|
||||||
|
* Constructor for a text message.
|
||||||
|
*
|
||||||
|
* @param text the text
|
||||||
|
* @param htmlText the HTML text
|
||||||
|
* @param format the formatted text format
|
||||||
|
*/
|
||||||
|
data class IntentDataText(
|
||||||
|
val text: CharSequence? = null,
|
||||||
|
val htmlText: String? = null,
|
||||||
|
val format: String? = null,
|
||||||
|
val clipDataItem: ClipData.Item = ClipData.Item(text, htmlText),
|
||||||
|
val mimeType: String? = if (null == htmlText) ClipDescription.MIMETYPE_TEXT_PLAIN else format
|
||||||
|
) : ExternalIntentData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clip data
|
||||||
|
*/
|
||||||
|
data class IntentDataClipData(
|
||||||
|
val clipDataItem: ClipData.Item,
|
||||||
|
val mimeType: String?
|
||||||
|
) : ExternalIntentData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor from a media Uri/
|
||||||
|
*
|
||||||
|
* @param uri the media uri
|
||||||
|
* @param filename the media file name
|
||||||
|
*/
|
||||||
|
data class IntentDataUri(
|
||||||
|
val uri: Uri,
|
||||||
|
val filename: String? = null
|
||||||
|
) : ExternalIntentData()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun analyseIntent(intent: Intent): List<ExternalIntentData> {
|
||||||
|
val externalIntentDataList = ArrayList<ExternalIntentData>()
|
||||||
|
|
||||||
|
|
||||||
|
// chrome adds many items when sharing an web page link
|
||||||
|
// so, test first the type
|
||||||
|
if (TextUtils.equals(intent.type, ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||||
|
var message: String? = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
|
||||||
|
if (null == message) {
|
||||||
|
val sequence = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)
|
||||||
|
if (null != sequence) {
|
||||||
|
message = sequence.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(subject)) {
|
||||||
|
if (TextUtils.isEmpty(message)) {
|
||||||
|
message = subject
|
||||||
|
} else if (WEB_URL.matcher(message!!).matches()) {
|
||||||
|
message = subject + "\n" + message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(message)) {
|
||||||
|
externalIntentDataList.add(ExternalIntentData.IntentDataText(message!!, null, intent.type))
|
||||||
|
return externalIntentDataList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var clipData: ClipData? = null
|
||||||
|
var mimetypes: MutableList<String>? = null
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||||
|
clipData = intent.clipData
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiple data
|
||||||
|
if (null != clipData) {
|
||||||
|
if (null != clipData.description) {
|
||||||
|
if (0 != clipData.description.mimeTypeCount) {
|
||||||
|
mimetypes = ArrayList()
|
||||||
|
|
||||||
|
for (i in 0 until clipData.description.mimeTypeCount) {
|
||||||
|
mimetypes.add(clipData.description.getMimeType(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the filter is "accept anything" the mimetype does not make sense
|
||||||
|
if (1 == mimetypes.size) {
|
||||||
|
if (mimetypes[0].endsWith("/*")) {
|
||||||
|
mimetypes = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val count = clipData.itemCount
|
||||||
|
|
||||||
|
for (i in 0 until count) {
|
||||||
|
val item = clipData.getItemAt(i)
|
||||||
|
var mimetype: String? = null
|
||||||
|
|
||||||
|
if (null != mimetypes) {
|
||||||
|
if (i < mimetypes.size) {
|
||||||
|
mimetype = mimetypes[i]
|
||||||
|
} else {
|
||||||
|
mimetype = mimetypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// uris list is not a valid mimetype
|
||||||
|
if (TextUtils.equals(mimetype, ClipDescription.MIMETYPE_TEXT_URILIST)) {
|
||||||
|
mimetype = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalIntentDataList.add(ExternalIntentData.IntentDataClipData(item, mimetype))
|
||||||
|
}
|
||||||
|
} else if (null != intent.data) {
|
||||||
|
externalIntentDataList.add(ExternalIntentData.IntentDataUri(intent.data!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalIntentDataList
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.intent
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
|
||||||
|
fun getFilenameFromUri(context: Context, uri: Uri): String? {
|
||||||
|
var result: String? = null
|
||||||
|
if (uri.scheme == "content") {
|
||||||
|
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
|
||||||
|
try {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = uri.path
|
||||||
|
val cut = result.lastIndexOf('/')
|
||||||
|
if (cut != -1) {
|
||||||
|
result = result.substring(cut + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotredesign.core.intent
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import im.vector.riotredesign.core.utils.getFileExtension
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mimetype from a uri.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
* @return the mimetype
|
||||||
|
*/
|
||||||
|
fun getMimeTypeFromUri(context: Context, uri: Uri): String? {
|
||||||
|
var mimeType: String? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null != mimeType) {
|
||||||
|
// the mimetype is sometimes in uppercase.
|
||||||
|
mimeType = mimeType.toLowerCase()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Failed to open resource input stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mimeType
|
||||||
|
}
|
@ -49,18 +49,24 @@ import im.vector.matrix.android.api.extensions.sortByLastSeen
|
|||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
||||||
import im.vector.riotredesign.core.extensions.showPassword
|
import im.vector.riotredesign.core.extensions.showPassword
|
||||||
import im.vector.riotredesign.core.extensions.withArgs
|
import im.vector.riotredesign.core.extensions.withArgs
|
||||||
|
import im.vector.riotredesign.core.intent.ExternalIntentData
|
||||||
|
import im.vector.riotredesign.core.intent.analyseIntent
|
||||||
|
import im.vector.riotredesign.core.intent.getFilenameFromUri
|
||||||
|
import im.vector.riotredesign.core.intent.getMimeTypeFromUri
|
||||||
import im.vector.riotredesign.core.platform.SimpleTextWatcher
|
import im.vector.riotredesign.core.platform.SimpleTextWatcher
|
||||||
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
|
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
|
||||||
import im.vector.riotredesign.core.preference.BingRule
|
import im.vector.riotredesign.core.preference.BingRule
|
||||||
import im.vector.riotredesign.core.preference.ProgressBarPreference
|
import im.vector.riotredesign.core.preference.ProgressBarPreference
|
||||||
import im.vector.riotredesign.core.preference.UserAvatarPreference
|
import im.vector.riotredesign.core.preference.UserAvatarPreference
|
||||||
import im.vector.riotredesign.core.preference.VectorPreference
|
import im.vector.riotredesign.core.preference.VectorPreference
|
||||||
|
import im.vector.riotredesign.core.resources.openResource
|
||||||
import im.vector.riotredesign.core.utils.*
|
import im.vector.riotredesign.core.utils.*
|
||||||
import im.vector.riotredesign.features.MainActivity
|
import im.vector.riotredesign.features.MainActivity
|
||||||
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
||||||
@ -2643,100 +2649,115 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
notImplemented()
|
val sharedDataItems = analyseIntent(intent)
|
||||||
|
val thisActivity = activity
|
||||||
|
|
||||||
/*
|
if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
||||||
val sharedDataItems = ArrayList(RoomMediaMessage.listRoomMediaMessages(intent))
|
val sharedDataItem = sharedDataItems[0]
|
||||||
val thisActivity = activity
|
|
||||||
|
|
||||||
if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
val uri = when (sharedDataItem) {
|
||||||
val sharedDataItem = sharedDataItems[0]
|
is ExternalIntentData.IntentDataUri -> sharedDataItem.uri
|
||||||
val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)
|
is ExternalIntentData.IntentDataClipData -> sharedDataItem.clipDataItem.uri
|
||||||
val builder = AlertDialog.Builder(thisActivity)
|
else -> null
|
||||||
.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)
|
|
||||||
|
|
||||||
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 afterTextChanged(s: Editable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
val importDialog = builder.show()
|
|
||||||
val appContext = thisActivity.applicationContext
|
|
||||||
|
|
||||||
importButton.setOnClickListener(View.OnClickListener {
|
|
||||||
val password = passPhraseEditText.text.toString()
|
|
||||||
val resource = openResource(appContext, sharedDataItem.uri, sharedDataItem.getMimeType(appContext))
|
|
||||||
|
|
||||||
if (resource?.mContentStream == null) {
|
|
||||||
appContext.toast("Error")
|
|
||||||
|
|
||||||
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)
|
val mimetype = when (sharedDataItem) {
|
||||||
|
is ExternalIntentData.IntentDataClipData -> sharedDataItem.mimeType
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
return@OnClickListener
|
if (uri == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val appContext = thisActivity.applicationContext
|
||||||
|
|
||||||
|
val filename = getFilenameFromUri(appContext, uri)
|
||||||
|
|
||||||
|
val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)
|
||||||
|
|
||||||
|
val textView = dialogLayout.findViewById<TextView>(R.id.dialog_e2e_keys_passphrase_filename)
|
||||||
|
|
||||||
|
if (filename.isNullOrBlank()) {
|
||||||
|
textView.isVisible = false
|
||||||
|
} else {
|
||||||
|
textView.isVisible = true
|
||||||
|
textView.text = getString(R.string.import_e2e_keys_from_file, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
passPhraseEditText.addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
|
importButton.isEnabled = !TextUtils.isEmpty(passPhraseEditText.text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val importDialog = builder.show()
|
||||||
|
|
||||||
|
importButton.setOnClickListener(View.OnClickListener {
|
||||||
|
val password = passPhraseEditText.text.toString()
|
||||||
|
val resource = openResource(appContext, uri, mimetype ?: getMimeTypeFromUri(appContext, uri))
|
||||||
|
|
||||||
|
if (resource?.mContentStream == null) {
|
||||||
|
appContext.toast("Error")
|
||||||
|
|
||||||
|
return@OnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val data: ByteArray
|
||||||
|
// TODO BG
|
||||||
|
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(data: ImportRoomKeysResult) {
|
||||||
|
if (!isAdded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoadingView()
|
||||||
|
|
||||||
|
AlertDialog.Builder(thisActivity)
|
||||||
|
.setMessage(getString(R.string.encryption_import_room_keys_success,
|
||||||
|
data.successfullyNumberOfImportedKeys,
|
||||||
|
data.totalNumberOfKeys))
|
||||||
|
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
appContext.toast(failure.localizedMessage)
|
||||||
|
hideLoadingView()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
importDialog.dismiss()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
displayLoadingView()
|
|
||||||
|
|
||||||
session.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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
appContext.toast(failure.localizedMessage)
|
|
||||||
hideLoadingView()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
importDialog.dismiss()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================================================
|
//==============================================================================================================
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/layout_root"
|
android:id="@+id/layout_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@ -11,6 +12,18 @@
|
|||||||
android:paddingRight="?dialogPreferredPadding"
|
android:paddingRight="?dialogPreferredPadding"
|
||||||
android:paddingBottom="12dp">
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_e2e_keys_passphrase_filename"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="filename.txt"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/VectorTextInputLayout"
|
style="@style/VectorTextInputLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -4,4 +4,6 @@
|
|||||||
<!-- Strings not defined in Riot -->
|
<!-- Strings not defined in Riot -->
|
||||||
|
|
||||||
|
|
||||||
|
<string name="import_e2e_keys_from_file">"Import e2e keys from file \"%1$s\"."</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue
Block a user