Merge pull request #3227 from vector-im/dependabot/gradle/kotlin_version-1.5.0

Bump kotlin_version from 1.4.32 to 1.5.0
This commit is contained in:
Benoit Marty 2021-05-17 16:15:27 +02:00 committed by GitHub
commit 1a70fa0fcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 336 additions and 241 deletions

View File

@ -17,6 +17,8 @@ SDK API changes ⚠️:
- -
Build 🧱: Build 🧱:
- Compile with Kotlin 1.5.
- Upgrade some dependencies: gradle wrapper, third party lib, etc.
- Sign APK with build tools 30.0.3 - Sign APK with build tools 30.0.3
Test: Test:

View File

@ -2,8 +2,8 @@
buildscript { buildscript {
// Ref: https://kotlinlang.org/releases.html // Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.4.32' ext.kotlin_version = '1.5.0'
ext.kotlin_coroutines_version = "1.4.2" ext.kotlin_coroutines_version = "1.5.0-RC"
repositories { repositories {
google() google()
jcenter() jcenter()
@ -12,7 +12,7 @@ buildscript {
} }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.android.tools.build:gradle:4.2.0'
classpath 'com.google.gms:google-services:4.3.5' classpath 'com.google.gms:google-services:4.3.5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.2.0' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.2.0'

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=ca42877db3519b667cd531c414be517b294b0467059d401e7133f0e55b9bf265 distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -226,12 +226,12 @@ class QrCodeTest : InstrumentedTest {
private fun checkHeader(byteArray: ByteArray) { private fun checkHeader(byteArray: ByteArray) {
// MATRIX // MATRIX
byteArray[0] shouldBeEqualTo 'M'.toByte() byteArray[0] shouldBeEqualTo 'M'.code.toByte()
byteArray[1] shouldBeEqualTo 'A'.toByte() byteArray[1] shouldBeEqualTo 'A'.code.toByte()
byteArray[2] shouldBeEqualTo 'T'.toByte() byteArray[2] shouldBeEqualTo 'T'.code.toByte()
byteArray[3] shouldBeEqualTo 'R'.toByte() byteArray[3] shouldBeEqualTo 'R'.code.toByte()
byteArray[4] shouldBeEqualTo 'I'.toByte() byteArray[4] shouldBeEqualTo 'I'.code.toByte()
byteArray[5] shouldBeEqualTo 'X'.toByte() byteArray[5] shouldBeEqualTo 'X'.code.toByte()
// Version // Version
byteArray[6] shouldBeEqualTo 2 byteArray[6] shouldBeEqualTo 2

View File

@ -20,6 +20,7 @@ import android.annotation.SuppressLint
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.util.safeCapitalize
/** /**
* Ref: https://github.com/matrix-org/matrix-doc/issues/1236 * Ref: https://github.com/matrix-org/matrix-doc/issues/1236
@ -39,6 +40,6 @@ data class WidgetContent(
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
fun getHumanName(): String { fun getHumanName(): String {
return (name ?: type ?: "").capitalize() return (name ?: type ?: "").safeCapitalize()
} }
} }

View File

@ -117,22 +117,22 @@ sealed class MatrixItem(
var first = dn[startIndex] var first = dn[startIndex]
// LEFT-TO-RIGHT MARK // LEFT-TO-RIGHT MARK
if (dn.length >= 2 && 0x200e == first.toInt()) { if (dn.length >= 2 && 0x200e == first.code) {
startIndex++ startIndex++
first = dn[startIndex] first = dn[startIndex]
} }
// check if its the start of a surrogate pair // check if its the start of a surrogate pair
if (first.toInt() in 0xD800..0xDBFF && dn.length > startIndex + 1) { if (first.code in 0xD800..0xDBFF && dn.length > startIndex + 1) {
val second = dn[startIndex + 1] val second = dn[startIndex + 1]
if (second.toInt() in 0xDC00..0xDFFF) { if (second.code in 0xDC00..0xDFFF) {
length++ length++
} }
} }
dn.substring(startIndex, startIndex + length) dn.substring(startIndex, startIndex + length)
} }
.toUpperCase(Locale.ROOT) .uppercase(Locale.ROOT)
} }
companion object { companion object {

View File

@ -345,7 +345,7 @@ internal abstract class SASDefaultVerificationTransaction(
} }
protected fun hashUsingAgreedHashMethod(toHash: String): String? { protected fun hashUsingAgreedHashMethod(toHash: String): String? {
if ("sha256" == accepted?.hash?.toLowerCase(Locale.ROOT)) { if ("sha256" == accepted?.hash?.lowercase(Locale.ROOT)) {
val olmUtil = OlmUtility() val olmUtil = OlmUtility()
val hashBytes = olmUtil.sha256(toHash) val hashBytes = olmUtil.sha256(toHash)
olmUtil.releaseUtility() olmUtil.releaseUtility()
@ -355,7 +355,7 @@ internal abstract class SASDefaultVerificationTransaction(
} }
private fun macUsingAgreedMethod(message: String, info: String): String? { private fun macUsingAgreedMethod(message: String, info: String): String? {
return when (accepted?.messageAuthenticationCode?.toLowerCase(Locale.ROOT)) { return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) {
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info) SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info) SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
else -> null else -> null

View File

@ -48,7 +48,7 @@ fun QrCodeData.toEncodedString(): String {
// TransactionId // TransactionId
transactionId.forEach { transactionId.forEach {
result += it.toByte() result += it.code.toByte()
} }
// Keys // Keys

View File

@ -291,7 +291,7 @@ internal class DefaultFileService @Inject constructor(
Timber.v("Get size of ${it.absolutePath}") Timber.v("Get size of ${it.absolutePath}")
true true
} }
.sumBy { it.length().toInt() } .sumOf { it.length().toInt() }
} }
override fun clearCache() { override fun clearCache() {

View File

@ -117,7 +117,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
return withOlmUtility { olmUtility -> return withOlmUtility { olmUtility ->
threePids.map { threePid -> threePids.map { threePid ->
base64ToBase64Url( base64ToBase64Url(
olmUtility.sha256(threePid.value.toLowerCase(Locale.ROOT) olmUtility.sha256(threePid.value.lowercase(Locale.ROOT)
+ " " + threePid.toMedium() + " " + pepper) + " " + threePid.toMedium() + " " + pepper)
) )
} }

View File

@ -27,7 +27,7 @@ fun String.md5() = try {
digest.update(toByteArray()) digest.update(toByteArray())
digest.digest() digest.digest()
.joinToString("") { String.format("%02X", it) } .joinToString("") { String.format("%02X", it) }
.toLowerCase(Locale.ROOT) .lowercase(Locale.ROOT)
} catch (exc: Exception) { } catch (exc: Exception) {
// Should not happen, but just in case // Should not happen, but just in case
hashCode().toString() hashCode().toString()

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.util package org.matrix.android.sdk.internal.util
import timber.log.Timber import timber.log.Timber
import java.util.Locale
/** /**
* Convert a string to an UTF8 String * Convert a string to an UTF8 String
@ -24,7 +25,7 @@ import timber.log.Timber
* @param s the string to convert * @param s the string to convert
* @return the utf-8 string * @return the utf-8 string
*/ */
fun convertToUTF8(s: String): String { internal fun convertToUTF8(s: String): String {
return try { return try {
val bytes = s.toByteArray(Charsets.UTF_8) val bytes = s.toByteArray(Charsets.UTF_8)
String(bytes) String(bytes)
@ -40,7 +41,7 @@ fun convertToUTF8(s: String): String {
* @param s the string to convert * @param s the string to convert
* @return the utf-16 string * @return the utf-16 string
*/ */
fun convertFromUTF8(s: String): String { internal fun convertFromUTF8(s: String): String {
return try { return try {
val bytes = s.toByteArray() val bytes = s.toByteArray()
String(bytes, Charsets.UTF_8) String(bytes, Charsets.UTF_8)
@ -56,7 +57,7 @@ fun convertFromUTF8(s: String): String {
* @param subString the string to search for * @param subString the string to search for
* @return whether a match was found * @return whether a match was found
*/ */
fun String.caseInsensitiveFind(subString: String): Boolean { internal fun String.caseInsensitiveFind(subString: String): Boolean {
// add sanity checks // add sanity checks
if (subString.isEmpty() || isEmpty()) { if (subString.isEmpty() || isEmpty()) {
return false return false
@ -78,3 +79,14 @@ internal val spaceChars = "[\u00A0\u2000-\u200B\u2800\u3000]".toRegex()
* Strip all the UTF-8 chars which are actually spaces * Strip all the UTF-8 chars which are actually spaces
*/ */
internal fun String.replaceSpaceChars() = replace(spaceChars, "") internal fun String.replaceSpaceChars() = replace(spaceChars, "")
// String.capitalize is now deprecated
internal fun String.safeCapitalize(): String {
return replaceFirstChar { char ->
if (char.isLowerCase()) {
char.titlecase(Locale.getDefault())
} else {
char.toString()
}
}
}

View File

@ -19,13 +19,14 @@ import android.content.Intent
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.troubleshoot.TroubleshootTest import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -38,7 +39,8 @@ import javax.inject.Inject
class TestPushFromPushGateway @Inject constructor(private val context: AppCompatActivity, class TestPushFromPushGateway @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter, private val errorFormatter: ErrorFormatter,
private val pushersManager: PushersManager) private val pushersManager: PushersManager,
private val activeSessionHolder: ActiveSessionHolder)
: TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) { : TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
private var action: Job? = null private var action: Job? = null
@ -50,7 +52,7 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat
status = TestStatus.FAILED status = TestStatus.FAILED
return return
} }
action = GlobalScope.launch { action = activeSessionHolder.getActiveSession().coroutineScope.launch {
val result = runCatching { pushersManager.testPush(fcmToken) } val result = runCatching { pushersManager.testPush(fcmToken) }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@ -22,10 +22,10 @@ import androidx.lifecycle.OnLifecycleEvent
import arrow.core.Option import arrow.core.Option
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.core.utils.BehaviorDataSource
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -63,30 +63,30 @@ class AppStateHandler @Inject constructor(
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? = selectedSpaceDataSource.currentValue?.orNull() fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? = selectedSpaceDataSource.currentValue?.orNull()
fun setCurrentSpace(spaceId: String?, session: Session? = null) { fun setCurrentSpace(spaceId: String?, session: Session? = null) {
val uSession = session ?: activeSessionHolder.getSafeActiveSession() val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.BySpace if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.BySpace
&& spaceId == selectedSpaceDataSource.currentValue?.orNull()?.space()?.roomId) return && spaceId == selectedSpaceDataSource.currentValue?.orNull()?.space()?.roomId) return
val spaceSum = spaceId?.let { uSession?.getRoomSummary(spaceId) } val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.BySpace(spaceSum))) selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.BySpace(spaceSum)))
if (spaceId != null) { if (spaceId != null) {
GlobalScope.launch(Dispatchers.IO) { uSession.coroutineScope.launch(Dispatchers.IO) {
tryOrNull { tryOrNull {
uSession?.getRoom(spaceId)?.loadRoomMembersIfNeeded() uSession.getRoom(spaceId)?.loadRoomMembersIfNeeded()
} }
} }
} }
} }
fun setCurrentGroup(groupId: String?, session: Session? = null) { fun setCurrentGroup(groupId: String?, session: Session? = null) {
val uSession = session ?: activeSessionHolder.getSafeActiveSession() val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.ByLegacyGroup if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.ByLegacyGroup
&& groupId == selectedSpaceDataSource.currentValue?.orNull()?.group()?.groupId) return && groupId == selectedSpaceDataSource.currentValue?.orNull()?.group()?.groupId) return
val activeGroup = groupId?.let { uSession?.getGroupSummary(groupId) } val activeGroup = groupId?.let { uSession.getGroupSummary(groupId) }
selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.ByLegacyGroup(activeGroup))) selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.ByLegacyGroup(activeGroup)))
if (groupId != null) { if (groupId != null) {
GlobalScope.launch { uSession.coroutineScope.launch {
tryOrNull { tryOrNull {
uSession?.getGroup(groupId)?.fetchGroupData() uSession.getGroup(groupId)?.fetchGroupData()
} }
} }
} }

View File

@ -45,7 +45,7 @@ fun getMimeTypeFromUri(context: Context, uri: Uri): String? {
if (null != mimeType) { if (null != mimeType) {
// the mimetype is sometimes in uppercase. // the mimetype is sometimes in uppercase.
mimeType = mimeType.toLowerCase(Locale.ROOT) mimeType = mimeType.lowercase(Locale.ROOT)
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Failed to open resource input stream") Timber.e(e, "Failed to open resource input stream")

View File

@ -42,13 +42,13 @@ fun CharSequence.splitEmoji(): List<CharSequence> {
while (index < length) { while (index < length) {
val firstChar = get(index) val firstChar = get(index)
if (firstChar.toInt() == 0x200e) { if (firstChar.code == 0x200e) {
// Left to right mark. What should I do with it? // Left to right mark. What should I do with it?
} else if (firstChar.toInt() in 0xD800..0xDBFF && index + 1 < length) { } else if (firstChar.code in 0xD800..0xDBFF && index + 1 < length) {
// We have the start of a surrogate pair // We have the start of a surrogate pair
val secondChar = get(index + 1) val secondChar = get(index + 1)
if (secondChar.toInt() in 0xDC00..0xDFFF) { if (secondChar.code in 0xDC00..0xDFFF) {
// We have an emoji // We have an emoji
result.add("$firstChar$secondChar") result.add("$firstChar$secondChar")
index++ index++

View File

@ -43,8 +43,7 @@ import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.withContext
import kotlinx.coroutines.launch
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import okio.source import okio.source
@ -57,6 +56,7 @@ import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.lang.IllegalStateException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@ -344,90 +344,93 @@ private fun appendTimeToFilename(name: String): String {
return """${filename}_$dateExtension.$fileExtension""" return """${filename}_$dateExtension.$fileExtension"""
} }
fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String?, notificationUtils: NotificationUtils) { suspend fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String?, notificationUtils: NotificationUtils) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { withContext(Dispatchers.IO) {
val filename = appendTimeToFilename(title) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val filename = appendTimeToFilename(title)
val values = ContentValues().apply { val values = ContentValues().apply {
put(MediaStore.Images.Media.TITLE, filename) put(MediaStore.Images.Media.TITLE, filename)
put(MediaStore.Images.Media.DISPLAY_NAME, filename) put(MediaStore.Images.Media.DISPLAY_NAME, filename)
put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType) put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType)
put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis()) put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
} }
val externalContentUri = when { val externalContentUri = when {
mediaMimeType?.isMimeTypeImage() == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI mediaMimeType?.isMimeTypeImage() == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
mediaMimeType?.isMimeTypeVideo() == true -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI mediaMimeType?.isMimeTypeVideo() == true -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
mediaMimeType?.isMimeTypeAudio() == true -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI mediaMimeType?.isMimeTypeAudio() == true -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
else -> MediaStore.Downloads.EXTERNAL_CONTENT_URI else -> MediaStore.Downloads.EXTERNAL_CONTENT_URI
} }
val uri = context.contentResolver.insert(externalContentUri, values) val uri = context.contentResolver.insert(externalContentUri, values)
if (uri == null) { if (uri == null) {
Toast.makeText(context, R.string.error_saving_media_file, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.error_saving_media_file, Toast.LENGTH_LONG).show()
} else { throw IllegalStateException(context.getString(R.string.error_saving_media_file))
val source = file.inputStream().source().buffer() } else {
context.contentResolver.openOutputStream(uri)?.sink()?.buffer()?.let { sink -> val source = file.inputStream().source().buffer()
source.use { input -> context.contentResolver.openOutputStream(uri)?.sink()?.buffer()?.let { sink ->
sink.use { output -> source.use { input ->
output.writeAll(input) sink.use { output ->
output.writeAll(input)
}
} }
} }
notificationUtils.buildDownloadFileNotification(
uri,
filename,
mediaMimeType ?: MimeTypes.OctetStream
).let { notification ->
notificationUtils.showNotificationMessage("DL", uri.hashCode(), notification)
}
} }
notificationUtils.buildDownloadFileNotification( } else {
uri, saveMediaLegacy(context, mediaMimeType, title, file)
filename,
mediaMimeType ?: MimeTypes.OctetStream
).let { notification ->
notificationUtils.showNotificationMessage("DL", uri.hashCode(), notification)
}
} }
} else {
saveMediaLegacy(context, mediaMimeType, title, file)
} }
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: String, file: File) { private fun saveMediaLegacy(context: Context,
mediaMimeType: String?,
title: String,
file: File) {
val state = Environment.getExternalStorageState() val state = Environment.getExternalStorageState()
if (Environment.MEDIA_MOUNTED != state) { if (Environment.MEDIA_MOUNTED != state) {
context.toast(context.getString(R.string.error_saving_media_file)) context.toast(context.getString(R.string.error_saving_media_file))
return throw IllegalStateException(context.getString(R.string.error_saving_media_file))
} }
GlobalScope.launch(Dispatchers.IO) { val dest = when {
val dest = when { mediaMimeType?.isMimeTypeImage() == true -> Environment.DIRECTORY_PICTURES
mediaMimeType?.isMimeTypeImage() == true -> Environment.DIRECTORY_PICTURES mediaMimeType?.isMimeTypeVideo() == true -> Environment.DIRECTORY_MOVIES
mediaMimeType?.isMimeTypeVideo() == true -> Environment.DIRECTORY_MOVIES mediaMimeType?.isMimeTypeAudio() == true -> Environment.DIRECTORY_MUSIC
mediaMimeType?.isMimeTypeAudio() == true -> Environment.DIRECTORY_MUSIC else -> Environment.DIRECTORY_DOWNLOADS
else -> Environment.DIRECTORY_DOWNLOADS }
val downloadDir = Environment.getExternalStoragePublicDirectory(dest)
try {
val outputFilename = if (title.substringAfterLast('.', "").isEmpty()) {
val extension = mediaMimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) }
"$title.$extension"
} else {
title
} }
val downloadDir = Environment.getExternalStoragePublicDirectory(dest) val savedFile = saveFileIntoLegacy(file, downloadDir, outputFilename)
try { if (savedFile != null) {
val outputFilename = if (title.substringAfterLast('.', "").isEmpty()) { val downloadManager = context.getSystemService<DownloadManager>()
val extension = mediaMimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } downloadManager?.addCompletedDownload(
"$title.$extension" savedFile.name,
} else { title,
title true,
} mediaMimeType ?: MimeTypes.OctetStream,
val savedFile = saveFileIntoLegacy(file, downloadDir, outputFilename) savedFile.absolutePath,
if (savedFile != null) { savedFile.length(),
val downloadManager = context.getSystemService<DownloadManager>() true)
downloadManager?.addCompletedDownload( addToGallery(savedFile, mediaMimeType, context)
savedFile.name,
title,
true,
mediaMimeType ?: MimeTypes.OctetStream,
savedFile.absolutePath,
savedFile.length(),
true)
addToGallery(savedFile, mediaMimeType, context)
}
} catch (error: Throwable) {
GlobalScope.launch(Dispatchers.Main) {
context.toast(context.getString(R.string.error_saving_media_file))
}
} }
} catch (error: Throwable) {
context.toast(context.getString(R.string.error_saving_media_file))
throw error
} }
} }

View File

@ -112,7 +112,7 @@ fun getFileExtension(fileUri: String): String? {
val ext = filename.substring(dotPos + 1) val ext = filename.substring(dotPos + 1)
if (ext.isNotBlank()) { if (ext.isNotBlank()) {
return ext.toLowerCase(Locale.ROOT) return ext.lowercase(Locale.ROOT)
} }
} }
} }
@ -131,5 +131,5 @@ fun getSizeOfFiles(root: File): Int {
Timber.v("Get size of ${it.absolutePath}") Timber.v("Get size of ${it.absolutePath}")
true true
} }
.sumBy { it.length().toInt() } .sumOf { it.length().toInt() }
} }

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2021 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.app.core.utils
import java.util.Locale
// String.capitalize is now deprecated
fun String.safeCapitalize(locale: Locale): String {
return replaceFirstChar { char ->
if (char.isLowerCase()) {
char.titlecase(locale)
} else {
char.toString()
}
}
}

View File

@ -33,11 +33,12 @@ import im.vector.app.features.call.utils.awaitCreateOffer
import im.vector.app.features.call.utils.awaitSetLocalDescription import im.vector.app.features.call.utils.awaitSetLocalDescription
import im.vector.app.features.call.utils.awaitSetRemoteDescription import im.vector.app.features.call.utils.awaitSetRemoteDescription
import im.vector.app.features.call.utils.mapToCallCandidate import im.vector.app.features.call.utils.mapToCallCandidate
import im.vector.app.features.session.coroutineScope
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.ReplaySubject import io.reactivex.subjects.ReplaySubject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -103,6 +104,9 @@ class WebRtcCall(val mxCall: MxCall,
private val listeners = CopyOnWriteArrayList<Listener>() private val listeners = CopyOnWriteArrayList<Listener>()
private val sessionScope: CoroutineScope?
get() = sessionProvider.get()?.coroutineScope
fun addListener(listener: Listener) { fun addListener(listener: Listener) {
listeners.add(listener) listeners.add(listener)
} }
@ -191,7 +195,7 @@ class WebRtcCall(val mxCall: MxCall,
fun onIceCandidate(iceCandidate: IceCandidate) = iceCandidateSource.onNext(iceCandidate) fun onIceCandidate(iceCandidate: IceCandidate) = iceCandidateSource.onNext(iceCandidate)
fun onRenegotiationNeeded(restartIce: Boolean) { fun onRenegotiationNeeded(restartIce: Boolean) {
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
if (mxCall.state != CallState.CreateOffer && mxCall.opponentVersion == 0) { if (mxCall.state != CallState.CreateOffer && mxCall.opponentVersion == 0) {
Timber.v("Opponent does not support renegotiation: ignoring onRenegotiationNeeded event") Timber.v("Opponent does not support renegotiation: ignoring onRenegotiationNeeded event")
return@launch return@launch
@ -262,7 +266,7 @@ class WebRtcCall(val mxCall: MxCall,
localSurfaceRenderers.addIfNeeded(localViewRenderer) localSurfaceRenderers.addIfNeeded(localViewRenderer)
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer) remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
when (mode) { when (mode) {
VectorCallActivity.INCOMING_ACCEPT -> { VectorCallActivity.INCOMING_ACCEPT -> {
internalAcceptIncomingCall() internalAcceptIncomingCall()
@ -283,7 +287,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
fun acceptIncomingCall() { fun acceptIncomingCall() {
GlobalScope.launch { sessionScope?.launch {
Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}") Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
if (mxCall.state == CallState.LocalRinging) { if (mxCall.state == CallState.LocalRinging) {
internalAcceptIncomingCall() internalAcceptIncomingCall()
@ -564,7 +568,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
fun updateRemoteOnHold(onHold: Boolean) { fun updateRemoteOnHold(onHold: Boolean) {
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
if (remoteOnHold == onHold) return@launch if (remoteOnHold == onHold) return@launch
val direction: RtpTransceiver.RtpTransceiverDirection val direction: RtpTransceiver.RtpTransceiverDirection
if (onHold) { if (onHold) {
@ -688,7 +692,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
fun onAddStream(stream: MediaStream) { fun onAddStream(stream: MediaStream) {
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
// reportError("Weird-looking stream: " + stream); // reportError("Weird-looking stream: " + stream);
if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) { if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) {
Timber.e("## VOIP StreamObserver weird looking stream: $stream") Timber.e("## VOIP StreamObserver weird looking stream: $stream")
@ -712,7 +716,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
fun onRemoveStream() { fun onRemoveStream() {
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
remoteSurfaceRenderers remoteSurfaceRenderers
.mapNotNull { it.get() } .mapNotNull { it.get() }
.forEach { remoteVideoTrack?.removeSink(it) } .forEach { remoteVideoTrack?.removeSink(it) }
@ -734,7 +738,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
val wasRinging = mxCall.state is CallState.LocalRinging val wasRinging = mxCall.state is CallState.LocalRinging
mxCall.state = CallState.Terminated mxCall.state = CallState.Terminated
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
release() release()
} }
onCallEnded(callId) onCallEnded(callId)
@ -750,7 +754,7 @@ class WebRtcCall(val mxCall: MxCall,
// Call listener // Call listener
fun onCallIceCandidateReceived(iceCandidatesContent: CallCandidatesContent) { fun onCallIceCandidateReceived(iceCandidatesContent: CallCandidatesContent) {
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
iceCandidatesContent.candidates.forEach { iceCandidatesContent.candidates.forEach {
if (it.sdpMid.isNullOrEmpty() || it.candidate.isNullOrEmpty()) { if (it.sdpMid.isNullOrEmpty() || it.candidate.isNullOrEmpty()) {
return@forEach return@forEach
@ -763,7 +767,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) { fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
Timber.v("## VOIP onCallAnswerReceived ${callAnswerContent.callId}") Timber.v("## VOIP onCallAnswerReceived ${callAnswerContent.callId}")
val sdp = SessionDescription(SessionDescription.Type.ANSWER, callAnswerContent.answer.sdp) val sdp = SessionDescription(SessionDescription.Type.ANSWER, callAnswerContent.answer.sdp)
try { try {
@ -779,7 +783,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent) { fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent) {
GlobalScope.launch(dispatcher) { sessionScope?.launch(dispatcher) {
val description = callNegotiateContent.description val description = callNegotiateContent.description
val type = description?.type val type = description?.type
val sdpText = description?.sdp val sdpText = description?.sdp

View File

@ -18,8 +18,8 @@ package im.vector.app.features.crypto.keys
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
@ -33,7 +33,7 @@ class KeysExporter(private val session: Session) {
* Export keys and return the file path with the callback * Export keys and return the file path with the callback
*/ */
fun export(context: Context, password: String, uri: Uri, callback: MatrixCallback<Boolean>) { fun export(context: Context, password: String, uri: Uri, callback: MatrixCallback<Boolean>) {
GlobalScope.launch(Dispatchers.Main) { session.coroutineScope.launch(Dispatchers.Main) {
runCatching { runCatching {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val data = awaitCallback<ByteArray> { session.cryptoService().exportRoomKeys(password, it) } val data = awaitCallback<ByteArray> { session.cryptoService().exportRoomKeys(password, it) }

View File

@ -20,8 +20,8 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.resources.openResource import im.vector.app.core.resources.openResource
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
@ -41,7 +41,7 @@ class KeysImporter(private val session: Session) {
mimetype: String?, mimetype: String?,
password: String, password: String,
callback: MatrixCallback<ImportRoomKeysResult>) { callback: MatrixCallback<ImportRoomKeysResult>) {
GlobalScope.launch(Dispatchers.Main) { session.coroutineScope.launch(Dispatchers.Main) {
runCatching { runCatching {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri)) val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri))

View File

@ -25,6 +25,7 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import arrow.core.Try import arrow.core.Try
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.app.R import im.vector.app.R
@ -37,7 +38,6 @@ import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentKeysBackupSetupStep3Binding import im.vector.app.databinding.FragmentKeysBackupSetupStep3Binding
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.IOException import java.io.IOException
@ -163,7 +163,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
} }
private fun exportRecoveryKeyToFile(uri: Uri, data: String) { private fun exportRecoveryKeyToFile(uri: Uri, data: String) {
GlobalScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
Try { Try {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
requireContext().contentResolver.openOutputStream(uri) requireContext().contentResolver.openOutputStream(uri)

View File

@ -24,6 +24,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
@ -35,7 +36,6 @@ import im.vector.app.core.utils.toast
import im.vector.app.databinding.FragmentBootstrapSaveKeyBinding import im.vector.app.databinding.FragmentBootstrapSaveKeyBinding
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -80,7 +80,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
private val saveStartForActivityResult = registerStartForActivityResult { activityResult -> private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) { if (activityResult.resultCode == Activity.RESULT_OK) {
val uri = activityResult.data?.data ?: return@registerStartForActivityResult val uri = activityResult.data?.data ?: return@registerStartForActivityResult
GlobalScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
try { try {
sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!)) sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!))
} catch (failure: Throwable) { } catch (failure: Throwable) {

View File

@ -33,7 +33,6 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -427,7 +426,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
} }
private fun tentativeRestoreBackup(res: Map<String, String>?) { private fun tentativeRestoreBackup(res: Map<String, String>?) {
GlobalScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
Timber.v("## Keybackup secret not restored from SSSS") Timber.v("## Keybackup secret not restored from SSSS")

View File

@ -27,9 +27,9 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UIABaseAuth
@ -184,7 +184,7 @@ class HomeActivityViewModel @AssistedInject constructor(
private fun maybeBootstrapCrossSigningAfterInitialSync() { private fun maybeBootstrapCrossSigningAfterInitialSync() {
// We do not use the viewModel context because we do not want to tie this action to activity view model // We do not use the viewModel context because we do not want to tie this action to activity view model
GlobalScope.launch(Dispatchers.IO) { activeSessionHolder.getSafeActiveSession()?.coroutineScope?.launch(Dispatchers.IO) {
val session = activeSessionHolder.getSafeActiveSession() ?: return@launch val session = activeSessionHolder.getSafeActiveSession() ?: return@launch
tryOrNull("## MaybeBootstrapCrossSigning: Failed to download keys") { tryOrNull("## MaybeBootstrapCrossSigning: Failed to download keys") {

View File

@ -1745,20 +1745,19 @@ class RoomDetailFragment @Inject constructor(
session.coroutineScope.launch { session.coroutineScope.launch {
val result = runCatching { session.fileService().downloadFile(messageContent = action.messageContent) } val result = runCatching { session.fileService().downloadFile(messageContent = action.messageContent) }
if (!isAdded) return@launch if (!isAdded) return@launch
result.fold( result.mapCatching {
{ saveMedia(
saveMedia( context = requireContext(),
context = requireContext(), file = it,
file = it, title = action.messageContent.body,
title = action.messageContent.body, mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), it.toUri()),
mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), it.toUri()), notificationUtils = notificationUtils
notificationUtils = notificationUtils )
) }
}, .onFailure {
{ if (!isAdded) return@onFailure
showErrorInSnackbar(it) showErrorInSnackbar(it)
} }
)
} }
} }

View File

@ -449,7 +449,7 @@ class RoomDetailViewModel @AssistedInject constructor(
widgetSessionId = widgetSessionId.substring(0, 7) widgetSessionId = widgetSessionId.substring(0, 7)
} }
val roomId: String = room.roomId val roomId: String = room.roomId
val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.toLowerCase(VectorLocale.applicationLocale) val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.lowercase(VectorLocale.applicationLocale)
val preferredJitsiDomain = tryOrNull { val preferredJitsiDomain = tryOrNull {
rawService.getElementWellknown(session.myUserId) rawService.getElementWellknown(session.myUserId)

View File

@ -50,7 +50,7 @@ class MatrixItemColorProvider @Inject constructor(
fun getColorFromUserId(userId: String?): Int { fun getColorFromUserId(userId: String?): Int {
var hash = 0 var hash = 0
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() } userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.code }
return when (abs(hash) % 8) { return when (abs(hash) % 8) {
1 -> R.color.riotx_username_2 1 -> R.color.riotx_username_2
@ -66,7 +66,7 @@ class MatrixItemColorProvider @Inject constructor(
@ColorRes @ColorRes
private fun getColorFromRoomId(roomId: String?): Int { private fun getColorFromRoomId(roomId: String?): Int {
return when ((roomId?.toList()?.sumBy { it.toInt() } ?: 0) % 3) { return when ((roomId?.toList()?.sumOf { it.code } ?: 0) % 3) {
1 -> R.color.riotx_avatar_fill_2 1 -> R.color.riotx_avatar_fill_2
2 -> R.color.riotx_avatar_fill_3 2 -> R.color.riotx_avatar_fill_3
else -> R.color.riotx_avatar_fill_1 else -> R.color.riotx_avatar_fill_1

View File

@ -18,6 +18,7 @@ package im.vector.app.features.media
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -30,24 +31,31 @@ class AttachmentProviderFactory @Inject constructor(
private val session: Session private val session: Session
) { ) {
fun createProvider(attachments: List<TimelineEvent>): RoomEventsAttachmentProvider { fun createProvider(attachments: List<TimelineEvent>,
coroutineScope: CoroutineScope
): RoomEventsAttachmentProvider {
return RoomEventsAttachmentProvider( return RoomEventsAttachmentProvider(
attachments, attachments = attachments,
imageContentRenderer, imageContentRenderer = imageContentRenderer,
vectorDateFormatter, dateFormatter = vectorDateFormatter,
session.fileService(), fileService = session.fileService(),
stringProvider coroutineScope = coroutineScope,
stringProvider = stringProvider
) )
} }
fun createProvider(attachments: List<AttachmentData>, room: Room?): DataAttachmentRoomProvider { fun createProvider(attachments: List<AttachmentData>,
room: Room?,
coroutineScope: CoroutineScope
): DataAttachmentRoomProvider {
return DataAttachmentRoomProvider( return DataAttachmentRoomProvider(
attachments, attachments = attachments,
room, room = room,
imageContentRenderer, imageContentRenderer = imageContentRenderer,
vectorDateFormatter, dateFormatter = vectorDateFormatter,
session.fileService(), fileService = session.fileService(),
stringProvider coroutineScope = coroutineScope,
stringProvider = stringProvider
) )
} }
} }

View File

@ -31,8 +31,8 @@ import im.vector.lib.attachmentviewer.AttachmentInfo
import im.vector.lib.attachmentviewer.AttachmentSourceProvider import im.vector.lib.attachmentviewer.AttachmentSourceProvider
import im.vector.lib.attachmentviewer.ImageLoaderTarget import im.vector.lib.attachmentviewer.ImageLoaderTarget
import im.vector.lib.attachmentviewer.VideoLoaderTarget import im.vector.lib.attachmentviewer.VideoLoaderTarget
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage
@ -44,6 +44,7 @@ abstract class BaseAttachmentProvider<Type>(
private val attachments: List<Type>, private val attachments: List<Type>,
private val imageContentRenderer: ImageContentRenderer, private val imageContentRenderer: ImageContentRenderer,
protected val fileService: FileService, protected val fileService: FileService,
private val coroutineScope: CoroutineScope,
private val dateFormatter: VectorDateFormatter, private val dateFormatter: VectorDateFormatter,
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : AttachmentSourceProvider { ) : AttachmentSourceProvider {
@ -155,7 +156,7 @@ abstract class BaseAttachmentProvider<Type>(
target.onVideoURLReady(info.uid, data.url) target.onVideoURLReady(info.uid, data.url)
} else { } else {
target.onVideoFileLoading(info.uid) target.onVideoFileLoading(info.uid)
GlobalScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.IO) {
val result = runCatching { val result = runCatching {
fileService.downloadFile( fileService.downloadFile(
fileName = data.filename, fileName = data.filename,
@ -178,5 +179,5 @@ abstract class BaseAttachmentProvider<Type>(
// TODO("Not yet implemented") // TODO("Not yet implemented")
} }
abstract fun getFileForSharing(position: Int, callback: ((File?) -> Unit)) abstract suspend fun getFileForSharing(position: Int): File?
} }

View File

@ -19,10 +19,8 @@ package im.vector.app.features.media
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentInfo
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import org.matrix.android.sdk.api.extensions.tryOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -35,8 +33,16 @@ class DataAttachmentRoomProvider(
imageContentRenderer: ImageContentRenderer, imageContentRenderer: ImageContentRenderer,
dateFormatter: VectorDateFormatter, dateFormatter: VectorDateFormatter,
fileService: FileService, fileService: FileService,
coroutineScope: CoroutineScope,
stringProvider: StringProvider stringProvider: StringProvider
) : BaseAttachmentProvider<AttachmentData>(attachments, imageContentRenderer, fileService, dateFormatter, stringProvider) { ) : BaseAttachmentProvider<AttachmentData>(
attachments = attachments,
imageContentRenderer = imageContentRenderer,
fileService = fileService,
coroutineScope = coroutineScope,
dateFormatter = dateFormatter,
stringProvider = stringProvider
) {
override fun getAttachmentInfoAt(position: Int): AttachmentInfo { override fun getAttachmentInfoAt(position: Int): AttachmentInfo {
return getItem(position).let { return getItem(position).let {
@ -78,20 +84,17 @@ class DataAttachmentRoomProvider(
return room?.getTimeLineEvent(item.eventId) return room?.getTimeLineEvent(item.eventId)
} }
override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { override suspend fun getFileForSharing(position: Int): File? {
val item = getItem(position) return getItem(position)
GlobalScope.launch { .let { item ->
val result = runCatching { tryOrNull {
fileService.downloadFile( fileService.downloadFile(
fileName = item.filename, fileName = item.filename,
mimeType = item.mimeType, mimeType = item.mimeType,
url = item.url, url = item.url,
elementToDecrypt = item.elementToDecrypt elementToDecrypt = item.elementToDecrypt
) )
} }
withContext(Dispatchers.Main) { }
callback(result.getOrNull())
}
}
} }
} }

View File

@ -19,10 +19,8 @@ package im.vector.app.features.media
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentInfo
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import org.matrix.android.sdk.api.extensions.tryOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@ -41,8 +39,16 @@ class RoomEventsAttachmentProvider(
imageContentRenderer: ImageContentRenderer, imageContentRenderer: ImageContentRenderer,
dateFormatter: VectorDateFormatter, dateFormatter: VectorDateFormatter,
fileService: FileService, fileService: FileService,
coroutineScope: CoroutineScope,
stringProvider: StringProvider stringProvider: StringProvider
) : BaseAttachmentProvider<TimelineEvent>(attachments, imageContentRenderer, fileService, dateFormatter, stringProvider) { ) : BaseAttachmentProvider<TimelineEvent>(
attachments = attachments,
imageContentRenderer = imageContentRenderer,
fileService = fileService,
coroutineScope = coroutineScope,
dateFormatter = dateFormatter,
stringProvider = stringProvider
) {
override fun getAttachmentInfoAt(position: Int): AttachmentInfo { override fun getAttachmentInfoAt(position: Int): AttachmentInfo {
return getItem(position).let { return getItem(position).let {
@ -121,24 +127,19 @@ class RoomEventsAttachmentProvider(
return getItem(position) return getItem(position)
} }
override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { override suspend fun getFileForSharing(position: Int): File? {
getItem(position).let { timelineEvent -> return getItem(position)
.let { timelineEvent ->
val messageContent = timelineEvent.root.getClearContent().toModel<MessageContent>() timelineEvent.root.getClearContent().toModel<MessageContent>() as? MessageWithAttachmentContent
as? MessageWithAttachmentContent
?: return@let
GlobalScope.launch {
val result = runCatching {
fileService.downloadFile(
fileName = messageContent.body,
mimeType = messageContent.mimeType,
url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt())
} }
withContext(Dispatchers.Main) { ?.let { messageContent ->
callback(result.getOrNull()) tryOrNull {
fileService.downloadFile(
fileName = messageContent.body,
mimeType = messageContent.mimeType,
url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt())
}
} }
}
}
} }
} }

View File

@ -28,7 +28,7 @@ import androidx.core.transition.addListener
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope
import androidx.transition.Transition import androidx.transition.Transition
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
@ -42,6 +42,9 @@ import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.lib.attachmentviewer.AttachmentCommands import im.vector.lib.attachmentviewer.AttachmentCommands
import im.vector.lib.attachmentviewer.AttachmentViewerActivity import im.vector.lib.attachmentviewer.AttachmentViewerActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -119,11 +122,11 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen
val inMemoryData = intent.getParcelableArrayListExtra<AttachmentData>(EXTRA_IN_MEMORY_DATA) val inMemoryData = intent.getParcelableArrayListExtra<AttachmentData>(EXTRA_IN_MEMORY_DATA)
val sourceProvider = if (inMemoryData != null) { val sourceProvider = if (inMemoryData != null) {
initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0)
dataSourceFactory.createProvider(inMemoryData, room) dataSourceFactory.createProvider(inMemoryData, room, lifecycleScope)
} else { } else {
val events = room?.getAttachmentMessages().orEmpty() val events = room?.getAttachmentMessages().orEmpty()
initialIndex = events.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) initialIndex = events.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0)
dataSourceFactory.createProvider(events) dataSourceFactory.createProvider(events, lifecycleScope)
} }
sourceProvider.interactionListener = this sourceProvider.interactionListener = this
setSourceProvider(sourceProvider) setSourceProvider(sourceProvider)
@ -264,9 +267,15 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen
} }
override fun onShareTapped() { override fun onShareTapped() {
currentSourceProvider?.getFileForSharing(currentPosition) { data -> lifecycleScope.launch(Dispatchers.IO) {
if (data != null && lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { val file = currentSourceProvider?.getFileForSharing(currentPosition) ?: return@launch
shareMedia(this@VectorAttachmentViewerActivity, data, getMimeTypeFromUri(this@VectorAttachmentViewerActivity, data.toUri()))
withContext(Dispatchers.Main) {
shareMedia(
this@VectorAttachmentViewerActivity,
file,
getMimeTypeFromUri(this@VectorAttachmentViewerActivity, file.toUri())
)
} }
} }
} }

View File

@ -25,8 +25,9 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.files.LocalFilesHelper
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -39,6 +40,9 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val errorFormatter: ErrorFormatter) { private val errorFormatter: ErrorFormatter) {
private val sessionScope: CoroutineScope
get() = activeSessionHolder.getActiveSession().coroutineScope
@Parcelize @Parcelize
data class Data( data class Data(
override val eventId: String, override val eventId: String,
@ -76,7 +80,7 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc
thumbnailView.isVisible = true thumbnailView.isVisible = true
loadingView.isVisible = true loadingView.isVisible = true
GlobalScope.launch { sessionScope.launch {
val result = runCatching { val result = runCatching {
activeSessionHolder.getActiveSession().fileService() activeSessionHolder.getActiveSession().fileService()
.downloadFile( .downloadFile(
@ -119,7 +123,7 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc
thumbnailView.isVisible = true thumbnailView.isVisible = true
loadingView.isVisible = true loadingView.isVisible = true
GlobalScope.launch { sessionScope.launch {
val result = runCatching { val result = runCatching {
activeSessionHolder.getActiveSession().fileService() activeSessionHolder.getActiveSession().fileService()
.downloadFile( .downloadFile(

View File

@ -60,6 +60,7 @@ class PinLocker @Inject constructor(
return liveState return liveState
} }
@Suppress("EXPERIMENTAL_API_USAGE")
private fun computeState() { private fun computeState() {
GlobalScope.launch { GlobalScope.launch {
val state = if (shouldBeLocked && pinCodeStore.hasEncodedPin()) { val state = if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {

View File

@ -88,6 +88,7 @@ class VectorFileLogger @Inject constructor(
} }
} }
@Suppress("EXPERIMENTAL_API_USAGE")
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
fileHandler ?: return fileHandler ?: return
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {

View File

@ -19,6 +19,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.EmojiChooserFragmentBinding import im.vector.app.databinding.EmojiChooserFragmentBinding
@ -51,6 +52,8 @@ class EmojiChooserFragment @Inject constructor(
} }
} }
override fun getCoroutineScope() = lifecycleScope
override fun firstVisibleSectionChange(section: Int) { override fun firstVisibleSectionChange(section: Int) {
viewModel.setCurrentSection(section) viewModel.setCurrentSection(section)
} }

View File

@ -31,8 +31,8 @@ import androidx.transition.AutoTransition
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import im.vector.app.R import im.vector.app.R
import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.reactions.data.EmojiDataSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
@ -221,7 +221,7 @@ class EmojiRecyclerAdapter @Inject constructor(
} }
override fun getItemCount() = dataSource.rawData.categories override fun getItemCount() = dataSource.rawData.categories
.sumBy { emojiCategory -> 1 /* Section */ + emojiCategory.emojis.size } .sumOf { emojiCategory -> 1 /* Section */ + emojiCategory.emojis.size }
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(s: String?) abstract fun bind(s: String?)
@ -278,6 +278,7 @@ class EmojiRecyclerAdapter @Inject constructor(
} }
interface InteractionListener { interface InteractionListener {
fun getCoroutineScope(): CoroutineScope
fun firstVisibleSectionChange(section: Int) fun firstVisibleSectionChange(section: Int)
} }
@ -323,11 +324,11 @@ class EmojiRecyclerAdapter @Inject constructor(
// Log.i("SCROLL SPEED","scroll speed $dy") // Log.i("SCROLL SPEED","scroll speed $dy")
isFastScroll = abs(dy) > 50 isFastScroll = abs(dy) > 50
val visible = (recyclerView.layoutManager as GridLayoutManager).findFirstCompletelyVisibleItemPosition() val visible = (recyclerView.layoutManager as GridLayoutManager).findFirstCompletelyVisibleItemPosition()
GlobalScope.launch { interactionListener?.getCoroutineScope()?.launch {
val section = getSectionForAbsoluteIndex(visible) val section = getSectionForAbsoluteIndex(visible)
if (section != currentFirstVisibleSection) { if (section != currentFirstVisibleSection) {
currentFirstVisibleSection = section currentFirstVisibleSection = section
GlobalScope.launch(Dispatchers.Main) { interactionListener?.getCoroutineScope()?.launch(Dispatchers.Main) {
interactionListener?.firstVisibleSectionChange(currentFirstVisibleSection) interactionListener?.firstVisibleSectionChange(currentFirstVisibleSection)
} }
} }

View File

@ -21,6 +21,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -36,6 +37,7 @@ import im.vector.app.databinding.FragmentRoomUploadsBinding
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileArgs
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -76,13 +78,21 @@ class RoomUploadsFragment @Inject constructor(
shareMedia(requireContext(), it.file, getMimeTypeFromUri(requireContext(), it.file.toUri())) shareMedia(requireContext(), it.file, getMimeTypeFromUri(requireContext(), it.file.toUri()))
} }
is RoomUploadsViewEvents.FileReadyForSaving -> { is RoomUploadsViewEvents.FileReadyForSaving -> {
saveMedia( lifecycleScope.launch {
context = requireContext(), runCatching {
file = it.file, saveMedia(
title = it.title, context = requireContext(),
mediaMimeType = getMimeTypeFromUri(requireContext(), it.file.toUri()), file = it.file,
notificationUtils = notificationUtils title = it.title,
) mediaMimeType = getMimeTypeFromUri(requireContext(), it.file.toUri()),
notificationUtils = notificationUtils
)
}.onFailure { failure ->
if (!isAdded) return@onFailure
showErrorInSnackbar(failure)
}
}
Unit
} }
is RoomUploadsViewEvents.Failure -> showFailure(it.throwable) is RoomUploadsViewEvents.Failure -> showFailure(it.throwable)
}.exhaustive }.exhaustive

View File

@ -181,7 +181,7 @@ object VectorLocale {
} }
} }
// sort by human display names // sort by human display names
.sortedBy { localeToLocalisedString(it).toLowerCase(it) } .sortedBy { localeToLocalisedString(it).lowercase(it) }
supportedLocales.clear() supportedLocales.clear()
supportedLocales.addAll(list) supportedLocales.addAll(list)

View File

@ -52,7 +52,6 @@ import im.vector.app.features.MainActivityArgs
import im.vector.app.features.workers.signout.SignOutUiWorker import im.vector.app.features.workers.signout.SignOutUiWorker
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
@ -224,7 +223,7 @@ class VectorSettingsGeneralFragment @Inject constructor(
it.summary = TextUtils.formatFileSize(requireContext(), size.toLong()) it.summary = TextUtils.formatFileSize(requireContext(), size.toLong())
it.onPreferenceClickListener = Preference.OnPreferenceClickListener { it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
GlobalScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
// On UI Thread // On UI Thread
displayLoadingView() displayLoadingView()

View File

@ -24,6 +24,7 @@ import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.epoxy.profiles.profileSectionItem import im.vector.app.core.epoxy.profiles.profileSectionItem
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.safeCapitalize
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import java.util.Locale import java.util.Locale
@ -46,7 +47,7 @@ class LocalePickerController @Inject constructor(
} }
localeItem { localeItem {
id(data.currentLocale.toString()) id(data.currentLocale.toString())
title(VectorLocale.localeToLocalisedString(data.currentLocale).capitalize(data.currentLocale)) title(VectorLocale.localeToLocalisedString(data.currentLocale).safeCapitalize(data.currentLocale))
if (vectorPreferences.developerMode()) { if (vectorPreferences.developerMode()) {
subtitle(VectorLocale.localeToLocalisedStringInfo(data.currentLocale)) subtitle(VectorLocale.localeToLocalisedStringInfo(data.currentLocale))
} }
@ -75,7 +76,7 @@ class LocalePickerController @Inject constructor(
.forEach { .forEach {
localeItem { localeItem {
id(it.toString()) id(it.toString())
title(VectorLocale.localeToLocalisedString(it).capitalize(it)) title(VectorLocale.localeToLocalisedString(it).safeCapitalize(it))
if (vectorPreferences.developerMode()) { if (vectorPreferences.developerMode()) {
subtitle(VectorLocale.localeToLocalisedStringInfo(it)) subtitle(VectorLocale.localeToLocalisedStringInfo(it))
} }

View File

@ -36,11 +36,11 @@ import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.rageshake.ReportType import im.vector.app.features.rageshake.ReportType
import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.spaces.manage.ManageType import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceManageActivity import im.vector.app.features.spaces.manage.SpaceManageActivity
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
@ -140,7 +140,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setMessage(getString(R.string.space_leave_prompt_msg)) .setMessage(getString(R.string.space_leave_prompt_msg))
.setPositiveButton(R.string.leave) { _, _ -> .setPositiveButton(R.string.leave) { _, _ ->
GlobalScope.launch { session.coroutineScope.launch {
try { try {
session.getRoom(spaceArgs.spaceId)?.leave(null) session.getRoom(spaceArgs.spaceId)?.leave(null)
} catch (failure: Throwable) { } catch (failure: Throwable) {

View File

@ -22,7 +22,7 @@ import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.GlobalScope import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
@ -465,7 +465,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
} }
private fun launchWidgetAPIAction(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict, block: suspend () -> Unit): Job { private fun launchWidgetAPIAction(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict, block: suspend () -> Unit): Job {
return GlobalScope.launch { // We should probably use a scope tight to the lifecycle here...
return session.coroutineScope.launch {
kotlin.runCatching { kotlin.runCatching {
block() block()
}.fold( }.fold(

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<array name="com_google_android_gms_fonts_certs"> <array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item> <item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item> <item>@array/com_google_android_gms_fonts_certs_prod</item>
</array> </array>
<string-array name="com_google_android_gms_fonts_certs_dev"> <string-array name="com_google_android_gms_fonts_certs_dev">
<item> <item tools:ignore="Typos">
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
</item> </item>
</string-array> </string-array>
<string-array name="com_google_android_gms_fonts_certs_prod"> <string-array name="com_google_android_gms_fonts_certs_prod">
<item> <item tools:ignore="Typos">
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
</item> </item>
</string-array> </string-array>
</resources> </resources>