1
0
mirror of https://github.com/vector-im/element-android.git synced 2025-08-07 14:42:55 +03:00

Merge branch 'feature/ons/qr_code_login_ui' into feature/hughns/qr_code_login

This commit is contained in:
Hugh Nimmo-Smith
2022-10-14 13:57:04 +01:00
218 changed files with 2740 additions and 577 deletions

View File

@@ -5,7 +5,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Thank you for taking the time to propose a new feature or make a suggestion. Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion here](https://github.com/vector-im/element-meta/discussions/new?category=ideas).
- type: textarea - type: textarea
id: usecase id: usecase
attributes: attributes:

View File

@@ -30,10 +30,10 @@ buildscript {
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
classpath "com.likethesalad.android:stem-plugin:2.2.2" classpath "com.likethesalad.android:stem-plugin:2.2.2"
classpath 'org.owasp:dependency-check-gradle:7.2.1' classpath 'org.owasp:dependency-check-gradle:7.2.1'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
classpath 'app.cash.paparazzi:paparazzi-gradle-plugin:1.0.0' classpath 'app.cash.paparazzi:paparazzi-gradle-plugin:1.1.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }

1
changelog.d/5968.bugfix Normal file
View File

@@ -0,0 +1 @@
Fix wrong mic button direction to cancel on RTL languages

1
changelog.d/7257.wip Normal file
View File

@@ -0,0 +1 @@
[Device Management] Save "matrix_client_information" events on login/registration

1
changelog.d/7281.wip Normal file
View File

@@ -0,0 +1 @@
Links "Enable Notifications for this session" setting to enabled value in pusher

1
changelog.d/7294.wip Normal file
View File

@@ -0,0 +1 @@
[Device Management] Render extended device info

1
changelog.d/7300.wip Normal file
View File

@@ -0,0 +1 @@
Implements client-side of local notification settings event

1
changelog.d/7321.wip Normal file
View File

@@ -0,0 +1 @@
[Device management] Improve the parsing for OS of Desktop/Web sessions

1
changelog.d/7324.wip Normal file
View File

@@ -0,0 +1 @@
[Device management] Hide the IP address and last activity date on current session

1
changelog.d/7335.misc Normal file
View File

@@ -0,0 +1 @@
Dependency to arrow has been removed. Please use `org.matrix.android.sdk.api.util.Optional` instead.

1
changelog.d/7336.feature Normal file
View File

@@ -0,0 +1 @@
[Device management] Add lab flag for the feature

1
changelog.d/7338.wip Normal file
View File

@@ -0,0 +1 @@
Implement QR Code Login UI

1
changelog.d/7354.misc Normal file
View File

@@ -0,0 +1 @@
Update WYSIWYG editor designs.

View File

@@ -1,8 +1,8 @@
ext.versions = [ ext.versions = [
'minSdk' : 21, 'minSdk' : 21,
'compileSdk' : 32, 'compileSdk' : 33,
'targetSdk' : 32, 'targetSdk' : 33,
'sourceCompat' : JavaVersion.VERSION_11, 'sourceCompat' : JavaVersion.VERSION_11,
'targetCompat' : JavaVersion.VERSION_11, 'targetCompat' : JavaVersion.VERSION_11,
] ]
@@ -14,12 +14,11 @@ def kotlinCoroutines = "1.6.4"
def dagger = "2.44" def dagger = "2.44"
def appDistribution = "16.0.0-beta04" def appDistribution = "16.0.0-beta04"
def retrofit = "2.9.0" def retrofit = "2.9.0"
def arrow = "0.8.2"
def markwon = "4.6.2" def markwon = "4.6.2"
def moshi = "1.14.0" def moshi = "1.14.0"
def lifecycle = "2.5.1" def lifecycle = "2.5.1"
def flowBinding = "1.2.0" def flowBinding = "1.2.0"
def flipper = "0.169.0" def flipper = "0.170.0"
def epoxy = "5.0.0" def epoxy = "5.0.0"
def mavericks = "3.0.1" def mavericks = "3.0.1"
def glide = "4.14.2" def glide = "4.14.2"
@@ -51,10 +50,10 @@ ext.libs = [
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
], ],
androidx : [ androidx : [
'activity' : "androidx.activity:activity:1.5.1", 'activity' : "androidx.activity:activity-ktx:1.6.0",
'appCompat' : "androidx.appcompat:appcompat:1.5.1", 'appCompat' : "androidx.appcompat:appcompat:1.5.1",
'biometric' : "androidx.biometric:biometric:1.1.0", 'biometric' : "androidx.biometric:biometric:1.1.0",
'core' : "androidx.core:core-ktx:1.8.0", 'core' : "androidx.core:core-ktx:1.9.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.4", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.4",
'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
@@ -115,10 +114,6 @@ ext.libs = [
rx : [ rx : [
'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0" 'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0"
], ],
arrow : [
'core' : "io.arrow-kt:arrow-core:$arrow",
'instances' : "io.arrow-kt:arrow-instances-core:$arrow"
],
markwon : [ markwon : [
'core' : "io.noties.markwon:core:$markwon", 'core' : "io.noties.markwon:core:$markwon",
'extLatex' : "io.noties.markwon:ext-latex:$markwon", 'extLatex' : "io.noties.markwon:ext-latex:$markwon",

View File

@@ -134,7 +134,6 @@ ext.groups = [
'commons-io', 'commons-io',
'commons-logging', 'commons-logging',
'info.picocli', 'info.picocli',
'io.arrow-kt',
'io.element.android', 'io.element.android',
'io.github.davidburstrom.contester', 'io.github.davidburstrom.contester',
'io.github.detekt.sarif4k', 'io.github.detekt.sarif4k',

View File

@@ -316,10 +316,6 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
} }
return false return false
} }
override fun onDoubleTap(e: MotionEvent?): Boolean {
return super.onDoubleTap(e)
}
}) })
override fun onEvent(event: AttachmentEvents) { override fun onEvent(event: AttachmentEvents) {

View File

@@ -96,31 +96,27 @@ class SwipeToDismissHandler(
.setDuration(ANIMATION_DURATION) .setDuration(ANIMATION_DURATION)
.setInterpolator(AccelerateInterpolator()) .setInterpolator(AccelerateInterpolator())
.setUpdateListener { onSwipeViewMove(swipeView.translationY, translationLimit) } .setUpdateListener { onSwipeViewMove(swipeView.translationY, translationLimit) }
.setAnimatorListener(onAnimationEnd = { .setAnimatorEndListener {
if (translationTo != 0f) { if (translationTo != 0f) {
onDismiss() onDismiss()
} }
// remove the update listener, otherwise it will be saved on the next animation execution: // remove the update listener, otherwise it will be saved on the next animation execution:
swipeView.animate().setUpdateListener(null) swipeView.animate().setUpdateListener(null)
}) }
.start() .start()
} }
} }
internal fun ViewPropertyAnimator.setAnimatorListener( private fun ViewPropertyAnimator.setAnimatorEndListener(
onAnimationEnd: ((Animator?) -> Unit)? = null, onAnimationEnd: () -> Unit,
onAnimationStart: ((Animator?) -> Unit)? = null ) = setListener(
) = this.setListener(
object : AnimatorListenerAdapter() { object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator) {
onAnimationEnd?.invoke(animation) onAnimationEnd()
} }
override fun onAnimationStart(animation: Animator?) {
onAnimationStart?.invoke(animation)
} }
}) )
internal val View?.hitRect: Rect private val View.hitRect: Rect
get() = Rect().also { this?.getHitRect(it) } get() = Rect().also { getHitRect(it) }

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2022 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.lib.core.utils.compat
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import java.io.Serializable
inline fun <reified T> Intent.getParcelableExtraCompat(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableExtra(key, T::class.java)
else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T?
}
inline fun <reified T : Parcelable> Intent.getParcelableArrayListExtraCompat(key: String): ArrayList<T>? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableArrayListExtra(key, T::class.java)
else -> @Suppress("DEPRECATION") getParcelableArrayListExtra<T>(key)
}
inline fun <reified T> Bundle.getParcelableCompat(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelable(key, T::class.java)
else -> @Suppress("DEPRECATION") getParcelable(key) as? T?
}
inline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializable(key) as? T?
}
inline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T?
}
fun PackageManager.queryIntentActivitiesCompat(data: Intent, flags: Int): List<ResolveInfo> {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> queryIntentActivities(
data,
PackageManager.ResolveInfoFlags.of(flags.toLong())
)
else -> @Suppress("DEPRECATION") queryIntentActivities(data, flags)
}
}
fun PackageManager.resolveActivityCompat(data: Intent, flags: Int): ResolveInfo? {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> resolveActivity(
data,
PackageManager.ResolveInfoFlags.of(flags.toLong())
)
else -> @Suppress("DEPRECATION") resolveActivity(data, flags)
}
}

View File

@@ -103,6 +103,7 @@ public class DialpadView extends LinearLayout {
@Override @Override
protected void onFinishInflate() { protected void onFinishInflate() {
super.onFinishInflate();
setupKeypad(); setupKeypad();
mDigits = (EditText) findViewById(R.id.digits); mDigits = (EditText) findViewById(R.id.digits);
mDelete = (ImageButton) findViewById(R.id.deleteButton); mDelete = (ImageButton) findViewById(R.id.deleteButton);
@@ -201,14 +202,6 @@ public class DialpadView extends LinearLayout {
zero.setLongHoverContentDescription(resources.getText(R.string.description_image_button_plus)); zero.setLongHoverContentDescription(resources.getText(R.string.description_image_button_plus));
} }
private Drawable getDrawableCompat(Context context, int id) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return context.getDrawable(id);
} else {
return context.getResources().getDrawable(id);
}
}
public void setShowVoicemailButton(boolean show) { public void setShowVoicemailButton(boolean show) {
View view = findViewById(R.id.dialpad_key_voicemail); View view = findViewById(R.id.dialpad_key_voicemail);
if (view != null) { if (view != null) {

View File

@@ -23,6 +23,7 @@ import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import im.vector.lib.core.utils.compat.getParcelableCompat
class JSonViewerDialog : DialogFragment() { class JSonViewerDialog : DialogFragment() {
@@ -36,11 +37,12 @@ class JSonViewerDialog : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val args: JSonViewerFragmentArgs = arguments?.getParcelable(Mavericks.KEY_ARG) ?: return val args: JSonViewerFragmentArgs = arguments?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
if (savedInstanceState == null) { if (savedInstanceState == null) {
childFragmentManager.beginTransaction() childFragmentManager.beginTransaction()
.replace( .replace(
R.id.fragmentContainer, JSonViewerFragment.newInstance( R.id.fragmentContainer,
JSonViewerFragment.newInstance(
args.jsonString, args.jsonString,
args.defaultOpenDepth, args.defaultOpenDepth,
true, true,

View File

@@ -28,6 +28,7 @@ import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.MavericksView
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
@@ -53,7 +54,7 @@ class JSonViewerFragment : Fragment(), MavericksView {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val args: JSonViewerFragmentArgs? = arguments?.getParcelable(Mavericks.KEY_ARG) val args: JSonViewerFragmentArgs? = arguments?.getParcelableCompat(Mavericks.KEY_ARG)
val inflate = val inflate =
if (args?.wrap == true) { if (args?.wrap == true) {
inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false) inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false)

View File

@@ -35,9 +35,19 @@ android {
} }
} }
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
kotlinOptions {
jvmTarget = "11"
}
} }
dependencies { dependencies {
implementation project(":library:core-utils")
api libs.androidx.activity api libs.androidx.activity
implementation libs.androidx.exifinterface implementation libs.androidx.exifinterface
implementation libs.androidx.core implementation libs.androidx.core

View File

@@ -22,6 +22,9 @@ import android.content.pm.PackageManager
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.net.Uri import android.net.Uri
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import im.vector.lib.core.utils.compat.queryIntentActivitiesCompat
/** /**
* Abstract class to provide all types of Pickers. * Abstract class to provide all types of Pickers.
@@ -45,13 +48,13 @@ abstract class Picker<T> {
val uriList = mutableListOf<Uri>() val uriList = mutableListOf<Uri>()
if (data.action == Intent.ACTION_SEND) { if (data.action == Intent.ACTION_SEND) {
(data.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { uriList.add(it) } data.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM)?.let { uriList.add(it) }
} else if (data.action == Intent.ACTION_SEND_MULTIPLE) { } else if (data.action == Intent.ACTION_SEND_MULTIPLE) {
val extraUriList: List<Uri>? = data.getParcelableArrayListExtra(Intent.EXTRA_STREAM) val extraUriList: List<Uri>? = data.getParcelableArrayListExtraCompat(Intent.EXTRA_STREAM)
extraUriList?.let { uriList.addAll(it) } extraUriList?.let { uriList.addAll(it) }
} }
val resInfoList: List<ResolveInfo> = context.packageManager.queryIntentActivities(data, PackageManager.MATCH_DEFAULT_ONLY) val resInfoList: List<ResolveInfo> = context.packageManager.queryIntentActivitiesCompat(data, PackageManager.MATCH_DEFAULT_ONLY)
uriList.forEach { uriList.forEach {
for (resolveInfo in resInfoList) { for (resolveInfo in resInfoList) {
val packageName: String = resolveInfo.activityInfo.packageName val packageName: String = resolveInfo.activityInfo.packageName
@@ -91,6 +94,7 @@ abstract class Picker<T> {
} else if (dataUri != null) { } else if (dataUri != null) {
selectedUriList.add(dataUri) selectedUriList.add(dataUri)
} else { } else {
@Suppress("DEPRECATION")
data?.extras?.get(Intent.EXTRA_STREAM)?.let { data?.extras?.get(Intent.EXTRA_STREAM)?.let {
(it as? List<*>)?.filterIsInstance<Uri>()?.let { uriList -> (it as? List<*>)?.filterIsInstance<Uri>()?.let { uriList ->
selectedUriList.addAll(uriList) selectedUriList.addAll(uriList)

View File

@@ -447,7 +447,7 @@
<string name="labs_enable_deferred_dm_summary">Create DM only on first message</string> <string name="labs_enable_deferred_dm_summary">Create DM only on first message</string>
<string name="labs_enable_rich_text_editor_title">Enable rich text editor</string> <string name="labs_enable_rich_text_editor_title">Enable rich text editor</string>
<string name="labs_enable_rich_text_editor_summary">Use a rich text editor to send formatted messages</string> <string name="labs_enable_rich_text_editor_summary">Try out the rich text editor (plain text mode coming soon)</string>
<!-- Home fragment --> <!-- Home fragment -->
<string name="invitations_header">Invites</string> <string name="invitations_header">Invites</string>
@@ -638,6 +638,8 @@
<string name="permissions_rationale_msg_record_audio">${app_name} needs permission to access your microphone to perform audio calls.</string> <string name="permissions_rationale_msg_record_audio">${app_name} needs permission to access your microphone to perform audio calls.</string>
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name --> <!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
<string name="permissions_rationale_msg_camera_and_audio">${app_name} needs permission to access your camera and your microphone to perform video calls.\n\nPlease allow access on the next pop-ups to be able to make the call.</string> <string name="permissions_rationale_msg_camera_and_audio">${app_name} needs permission to access your camera and your microphone to perform video calls.\n\nPlease allow access on the next pop-ups to be able to make the call.</string>
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
<string name="permissions_rationale_msg_notification">${app_name} needs permission to display notifications. Notifications can display your messages, your invitations, etc.\n\nPlease allow access on the next pop-ups to be able to view notification.</string>
<string name="permissions_denied_qr_code">To scan a QR code, you need to allow camera access.</string> <string name="permissions_denied_qr_code">To scan a QR code, you need to allow camera access.</string>
<string name="permissions_denied_add_contact">Allow permission to access your contacts.</string> <string name="permissions_denied_add_contact">Allow permission to access your contacts.</string>
@@ -857,7 +859,9 @@
<string name="settings_troubleshoot_test_system_settings_title">System Settings.</string> <string name="settings_troubleshoot_test_system_settings_title">System Settings.</string>
<string name="settings_troubleshoot_test_system_settings_success">Notifications are enabled in the system settings.</string> <string name="settings_troubleshoot_test_system_settings_success">Notifications are enabled in the system settings.</string>
<string name="settings_troubleshoot_test_system_settings_failed">Notifications are disabled in the system settings.\nPlease check system settings.</string> <string name="settings_troubleshoot_test_system_settings_failed">Notifications are disabled in the system settings.\nPlease check system settings.</string>
<string name="settings_troubleshoot_test_system_settings_permission_failed">${app_name} needs the permission to show notifications.\nPlease grant the permission.</string>
<string name="open_settings">Open Settings</string> <string name="open_settings">Open Settings</string>
<string name="grant_permission">Grant Permission</string>
<string name="settings_troubleshoot_test_account_settings_title">Account Settings.</string> <string name="settings_troubleshoot_test_account_settings_title">Account Settings.</string>
<string name="settings_troubleshoot_test_account_settings_success">Notifications are enabled for your account.</string> <string name="settings_troubleshoot_test_account_settings_success">Notifications are enabled for your account.</string>
@@ -1666,6 +1670,7 @@
<string name="create_new_room">Create New Room</string> <string name="create_new_room">Create New Room</string>
<string name="create_new_space">Create New Space</string> <string name="create_new_space">Create New Space</string>
<string name="error_no_network">No network. Please check your Internet connection.</string> <string name="error_no_network">No network. Please check your Internet connection.</string>
<string name="error_check_network">Something went wrong. Please check your network connection and try again.</string>
<string name="change_room_directory_network">"Change network"</string> <string name="change_room_directory_network">"Change network"</string>
<string name="please_wait">"Please wait…"</string> <string name="please_wait">"Please wait…"</string>
<string name="updating_your_data">Updating your data…</string> <string name="updating_your_data">Updating your data…</string>
@@ -3240,7 +3245,8 @@
<string name="labs_enable_element_call_permission_shortcuts_summary">Auto-approve Element Call widgets and grant camera / mic access</string> <string name="labs_enable_element_call_permission_shortcuts_summary">Auto-approve Element Call widgets and grant camera / mic access</string>
<!-- Device Manager --> <!-- Device Manager -->
<string name="device_manager_settings_active_sessions_show_all">Show All Sessions (V2, WIP)</string> <!-- TODO remove this key -->
<string name="device_manager_settings_active_sessions_show_all" tools:ignore="UnusedResources">Show All Sessions (V2, WIP)</string>
<string name="device_manager_sessions_other_title">Other sessions</string> <string name="device_manager_sessions_other_title">Other sessions</string>
<string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you dont recognize or use anymore.</string> <string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you dont recognize or use anymore.</string>
<string name="a11y_device_manager_device_type_mobile">Mobile</string> <string name="a11y_device_manager_device_type_mobile">Mobile</string>
@@ -3313,6 +3319,13 @@
<string name="device_manager_session_details_session_name">Session name</string> <string name="device_manager_session_details_session_name">Session name</string>
<string name="device_manager_session_details_session_id">Session ID</string> <string name="device_manager_session_details_session_id">Session ID</string>
<string name="device_manager_session_details_session_last_activity">Last activity</string> <string name="device_manager_session_details_session_last_activity">Last activity</string>
<string name="device_manager_session_details_application">Application</string>
<string name="device_manager_session_details_application_name">Name</string>
<string name="device_manager_session_details_application_version">Version</string>
<string name="device_manager_session_details_application_url">URL</string>
<string name="device_manager_session_details_device_browser">Browser</string>
<string name="device_manager_session_details_device_model">Model</string>
<string name="device_manager_session_details_device_operating_system">Operating system</string>
<string name="device_manager_session_details_device_ip_address">IP address</string> <string name="device_manager_session_details_device_ip_address">IP address</string>
<string name="device_manager_session_rename">Rename session</string> <string name="device_manager_session_rename">Rename session</string>
<string name="device_manager_session_rename_edit_hint">Session name</string> <string name="device_manager_session_rename_edit_hint">Session name</string>
@@ -3329,6 +3342,8 @@
<string name="device_manager_learn_more_sessions_verified">Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.</string> <string name="device_manager_learn_more_sessions_verified">Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.</string>
<string name="device_manager_learn_more_session_rename_title">Renaming sessions</string> <string name="device_manager_learn_more_session_rename_title">Renaming sessions</string>
<string name="device_manager_learn_more_session_rename">Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.</string> <string name="device_manager_learn_more_session_rename">Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.</string>
<string name="labs_enable_session_manager_title">Enable new session manager</string>
<string name="labs_enable_session_manager_summary">Have greater visibility and control over all your sessions.</string>
<!-- Note to translators: %s will be replaces with selected space name --> <!-- Note to translators: %s will be replaces with selected space name -->
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string> <string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
@@ -3386,4 +3401,10 @@
<string name="qr_code_login_confirm_security_code">Confirm</string> <string name="qr_code_login_confirm_security_code">Confirm</string>
<string name="qr_code_login_confirm_security_code_description">Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.</string> <string name="qr_code_login_confirm_security_code_description">Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.</string>
<!-- WYSIWYG Composer -->
<string name="rich_text_editor_format_bold">Apply bold format</string>
<string name="rich_text_editor_format_italic">Apply italic format</string>
<string name="rich_text_editor_format_strikethrough">Apply strikethrough format</string>
<string name="rich_text_editor_format_underline">Apply underline format</string>
</resources> </resources>

View File

@@ -152,4 +152,9 @@
<color name="vctr_badge_color_border_light">@color/palette_white</color> <color name="vctr_badge_color_border_light">@color/palette_white</color>
<color name="vctr_badge_color_border_dark">@color/palette_black_950</color> <color name="vctr_badge_color_border_dark">@color/palette_black_950</color>
<!-- WYSIWYG Colors -->
<attr name="vctr_rich_text_editor_menu_button_background" format="color" />
<color name="vctr_rich_text_editor_menu_button_background_light">#EEF8F4</color>
<color name="vctr_rich_text_editor_menu_button_background_dark">#1D292A</color>
</resources> </resources>

View File

@@ -47,7 +47,8 @@
<dimen name="composer_min_height">56dp</dimen> <dimen name="composer_min_height">56dp</dimen>
<dimen name="composer_attachment_size">52dp</dimen> <dimen name="composer_attachment_size">52dp</dimen>
<dimen name="composer_attachment_margin">1dp</dimen> <dimen name="composer_attachment_margin">1dp</dimen>
<dimen name="rich_text_composer_corner_radius_single_line">28dp</dimen>
<dimen name="rich_text_composer_corner_radius_expanded">14dp</dimen>
<dimen name="chat_bubble_margin_start">28dp</dimen> <dimen name="chat_bubble_margin_start">28dp</dimen>
<dimen name="chat_bubble_margin_end">6dp</dimen> <dimen name="chat_bubble_margin_end">6dp</dimen>

View File

@@ -11,4 +11,14 @@
<item name="android:textColor">?vctr_message_text_color</item> <item name="android:textColor">?vctr_message_text_color</item>
</style> </style>
<style name="Widget.Vector.EditText.RichTextComposer" parent="Widget.AppCompat.EditText">
<item name="android:background">@android:color/transparent</item>
<item name="android:inputType">textCapSentences|textMultiLine</item>
<item name="android:maxLines">12</item>
<item name="android:minHeight">20dp</item>
<item name="android:padding">0dp</item>
<item name="android:textSize">15sp</item>
<item name="android:textColor">?vctr_message_text_color</item>
</style>
</resources> </resources>

View File

@@ -152,6 +152,9 @@
<!-- Material 3 --> <!-- Material 3 -->
<item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item> <item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item>
<!-- WYSIWYG Editor -->
<item name="vctr_rich_text_editor_menu_button_background">@color/vctr_rich_text_editor_menu_button_background_dark</item>
</style> </style>
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" /> <style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />

View File

@@ -153,6 +153,9 @@
<!-- Material 3 --> <!-- Material 3 -->
<item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item> <item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item>
<!-- WYSIWYG Editor -->
<item name="vctr_rich_text_editor_menu_button_background">@color/vctr_rich_text_editor_menu_button_background_light</item>
</style> </style>
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" /> <style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.account
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class LocalNotificationSettingsContent(
@Json(name = "is_silenced") val isSilenced: Boolean = false
)

View File

@@ -28,4 +28,5 @@ object UserAccountDataTypes {
const val TYPE_IDENTITY_SERVER = "m.identity_server" const val TYPE_IDENTITY_SERVER = "m.identity_server"
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms" const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
const val TYPE_OVERRIDE_COLORS = "im.vector.setting.override_colors" const val TYPE_OVERRIDE_COLORS = "im.vector.setting.override_colors"
const val TYPE_LOCAL_NOTIFICATION_SETTINGS = "org.matrix.msc3890.local_notification_settings."
} }

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.util
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getApplicationInfo(
packageName,
PackageManager.ApplicationInfoFlags.of(flags.toLong())
)
else -> @Suppress("DEPRECATION") getApplicationInfo(packageName, flags)
}
}
fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int): PackageInfo {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getPackageInfo(
packageName,
PackageManager.PackageInfoFlags.of(flags.toLong())
)
else -> @Suppress("DEPRECATION") getPackageInfo(packageName, flags)
}
}

View File

@@ -15,15 +15,13 @@
*/ */
package org.matrix.android.sdk.api.util package org.matrix.android.sdk.api.util
data class Optional<T : Any> constructor(private val value: T?) { data class Optional<T : Any>(private val value: T?) {
fun get(): T { fun get(): T = value!!
return value!!
}
fun getOrNull(): T? { fun orNull(): T? = value
return value
} fun getOrNull(): T? = value
fun <U : Any> map(fn: (T) -> U?): Optional<U> { fun <U : Any> map(fn: (T) -> U?): Optional<U> {
return if (value == null) { return if (value == null) {
@@ -33,23 +31,19 @@ data class Optional<T : Any> constructor(private val value: T?) {
} }
} }
fun getOrElse(fn: () -> T): T { fun orElse(fn: () -> T): T {
return value ?: fn() return value ?: fn()
} }
fun hasValue(): Boolean { fun hasValue(): Boolean = value != null
return value != null
}
companion object { companion object {
fun <T : Any> from(value: T?): Optional<T> { fun <T : Any> from(value: T?): Optional<T> = Optional(value)
return Optional(value)
fun <T : Any> empty(): Optional<T> = Optional(null)
}
} }
fun <T : Any> empty(): Optional<T> { fun <T : Any> T?.toOption() = Optional(this)
return Optional(null)
}
}
}
fun <T : Any> T?.toOptional() = Optional(this) fun <T : Any> T?.toOptional() = Optional(this)

View File

@@ -17,18 +17,15 @@
package org.matrix.android.sdk.internal.database.migration package org.matrix.android.sdk.internal.database.migration
import io.realm.DynamicRealm import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PusherEntityFields
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
import org.matrix.android.sdk.internal.util.database.RealmMigrator import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateSessionTo038(realm: DynamicRealm) : RealmMigrator(realm, 38) { internal class MigrateSessionTo038(realm: DynamicRealm) : RealmMigrator(realm, 38) {
override fun doMigrate(realm: DynamicRealm) { override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity") realm.schema.get("PusherEntity")
?.addField(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, Boolean::class.java) ?.addField(PusherEntityFields.ENABLED, Boolean::class.java)
?.transform { obj -> ?.addField(PusherEntityFields.DEVICE_ID, String::class.java)
obj.set(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, false) ?.transform { obj -> obj.set(PusherEntityFields.ENABLED, true) }
}
?.forceRefreshOfHomeServerCapabilities()
} }
} }

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateSessionTo039(realm: DynamicRealm) : RealmMigrator(realm, 39) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, Boolean::class.java)
?.transform { obj ->
obj.set(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, false)
}
?.forceRefreshOfHomeServerCapabilities()
}
}

View File

@@ -20,6 +20,8 @@ import android.content.Context
import android.os.Build import android.os.Build
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.util.getApplicationInfoCompat
import org.matrix.android.sdk.api.util.getPackageInfoCompat
import javax.inject.Inject import javax.inject.Inject
class ComputeUserAgentUseCase @Inject constructor( class ComputeUserAgentUseCase @Inject constructor(
@@ -36,7 +38,7 @@ class ComputeUserAgentUseCase @Inject constructor(
val appPackageName = context.applicationContext.packageName val appPackageName = context.applicationContext.packageName
val pm = context.packageManager val pm = context.packageManager
val appName = tryOrNull { pm.getApplicationLabel(pm.getApplicationInfo(appPackageName, 0)).toString() } val appName = tryOrNull { pm.getApplicationLabel(pm.getApplicationInfoCompat(appPackageName, 0)).toString() }
?.takeIf { ?.takeIf {
it.matches("\\A\\p{ASCII}*\\z".toRegex()) it.matches("\\A\\p{ASCII}*\\z".toRegex())
} }
@@ -44,7 +46,7 @@ class ComputeUserAgentUseCase @Inject constructor(
// Use appPackageName instead of appName if appName is null or contains any non-ASCII character // Use appPackageName instead of appName if appName is null or contains any non-ASCII character
appPackageName appPackageName
} }
val appVersion = tryOrNull { pm.getPackageInfo(context.applicationContext.packageName, 0).versionName } ?: FALLBACK_APP_VERSION val appVersion = tryOrNull { pm.getPackageInfoCompat(context.applicationContext.packageName, 0).versionName } ?: FALLBACK_APP_VERSION
val deviceManufacturer = Build.MANUFACTURER val deviceManufacturer = Build.MANUFACTURER
val deviceModel = Build.MODEL val deviceModel = Build.MODEL

View File

@@ -66,7 +66,7 @@ internal class DefaultSessionAccountDataService @Inject constructor(
override suspend fun updateUserAccountData(type: String, content: Content) { override suspend fun updateUserAccountData(type: String, content: Content) {
val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content)
awaitCallback<Unit> { callback -> awaitCallback { callback ->
updateUserAccountDataTask.configureWith(params) { updateUserAccountDataTask.configureWith(params) {
this.retryCount = 5 // TODO Need to refactor retrying out into a helper method. this.retryCount = 5 // TODO Need to refactor retrying out into a helper method.
this.callback = callback this.callback = callback

View File

@@ -27,6 +27,8 @@ import org.amshove.kluent.shouldBeEqualTo
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.util.getApplicationInfoCompat
import org.matrix.android.sdk.api.util.getPackageInfoCompat
import java.lang.Exception import java.lang.Exception
private const val A_PACKAGE_NAME = "org.matrix.sdk" private const val A_PACKAGE_NAME = "org.matrix.sdk"
@@ -49,8 +51,8 @@ class ComputeUserAgentUseCaseTest {
every { context.applicationContext } returns context every { context.applicationContext } returns context
every { context.packageName } returns A_PACKAGE_NAME every { context.packageName } returns A_PACKAGE_NAME
every { context.packageManager } returns packageManager every { context.packageManager } returns packageManager
every { packageManager.getApplicationInfo(any(), any()) } returns applicationInfo every { packageManager.getApplicationInfoCompat(any(), any()) } returns applicationInfo
every { packageManager.getPackageInfo(any<String>(), any()) } returns packageInfo every { packageManager.getPackageInfoCompat(any(), any()) } returns packageInfo
} }
@Test @Test

25
tools/adb/notification.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
## From https://developer.android.com/develop/ui/views/notifications/notification-permission#test
PACKAGE_NAME=im.vector.app.debug
# App is newly installed on a device that runs Android 13 or higher:
adb shell pm revoke ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed
# The user keeps notifications enabled when the app is installed on a device that runs 12L or lower,
# then the device upgrades to Android 13 or higher:
# adb shell pm grant ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
# adb shell pm set-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
# adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed
# The user manually disables notifications when the app is installed on a device that runs 12L or lower,
# then the device upgrades to Android 13 or higher:
# adb shell pm revoke ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
# adb shell pm set-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
# adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed

View File

@@ -68,15 +68,14 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code.txt \
./matrix-sdk-android/src/main/java \ ./matrix-sdk-android/src/main/java \
./matrix-sdk-android-flow/src/main/java \ ./matrix-sdk-android-flow/src/main/java \
./library/core-utils/src/main/java \ ./library/core-utils/src/main/java \
./library/jsonviewer/src/main/java \ ./library/external/jsonviewer/src/main/java \
./library/ui-styles/src/main/java \ ./library/ui-styles/src/main/java \
./vector/src/main/java \ ./vector/src/main/java \
./vector/src/debug/java \ ./vector-app/src/debug/java \
./vector/src/release/java \ ./vector-app/src/fdroid/java \
./vector/src/fdroid/java \
./vector/src/gplay/java \
./vector-app/src/gplay/java \ ./vector-app/src/gplay/java \
./vector-app/src/main/java ./vector-app/src/main/java \
./vector-app/src/release/java
resultForbiddenStringInCode=$? resultForbiddenStringInCode=$?
@@ -93,13 +92,15 @@ echo
echo "Search for forbidden patterns specific for App code..." echo "Search for forbidden patterns specific for App code..."
${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code_app.txt \ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code_app.txt \
./library/core-utils/src/main/java \
./library/external/jsonviewer/src/main/java \
./library/ui-styles/src/main/java \
./vector/src/main/java \ ./vector/src/main/java \
./vector/src/debug/java \ ./vector-app/src/debug/java \
./vector/src/release/java \ ./vector-app/src/fdroid/java \
./vector/src/fdroid/java \
./vector/src/gplay/java \
./vector-app/src/gplay/java \ ./vector-app/src/gplay/java \
./vector-app/src/main/java ./vector-app/src/main/java \
./vector-app/src/release/java
resultForbiddenStringInCodeApp=$? resultForbiddenStringInCodeApp=$?
@@ -120,8 +121,7 @@ echo
echo "Search for forbidden patterns in layouts..." echo "Search for forbidden patterns in layouts..."
${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_layout.txt \ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_layout.txt \
./vector/src/main/res/layout \ ./vector/src/main/res/layout
./vector-app/src/main/res/layout
resultForbiddenStringInLayout=$? resultForbiddenStringInLayout=$?
@@ -154,17 +154,19 @@ echo "Search for kotlin files with more than ${maxLines} lines..."
${checkLongFilesScript} ${maxLines} \ ${checkLongFilesScript} ${maxLines} \
./matrix-sdk-android/src/main/java \ ./matrix-sdk-android/src/main/java \
./matrix-sdk-android-flow/src/main/java \ ./matrix-sdk-android-flow/src/main/java \
./library/core-utils/src/main/java \
./library/external/jsonviewer/src/main/java \
./library/ui-styles/src/main/java \
./vector/src/androidTest/java \ ./vector/src/androidTest/java \
./vector/src/debug/java \
./vector/src/fdroid/java \
./vector/src/gplay/java \
./vector/src/main/java \ ./vector/src/main/java \
./vector/src/release/java \
./vector/src/sharedTest/java \ ./vector/src/sharedTest/java \
./vector/src/test/java \ ./vector/src/test/java \
./vector/src/androidTest/java \ ./vector-app/src/androidTest/java \
./vector/src/gplay/java \ ./vector-app/src/debug/java \
./vector/src/main/java ./vector-app/src/fdroid/java \
./vector-app/src/gplay/java \
./vector-app/src/main/java \
./vector-app/src/release/java
resultLongFiles=$? resultLongFiles=$?
@@ -179,8 +181,11 @@ echo "Search for png files in /drawable..."
ls -1U ./vector/src/main/res/drawable/*.png ls -1U ./vector/src/main/res/drawable/*.png
resultTmp=$? resultTmp=$?
ls -1U ./vector-app/src/main/res/drawable/*.png
resultTmp2=$?
# Inverse the result, cause no file found is an error for ls but this is what we want! # Inverse the result, cause no file found is an error for ls but this is what we want!
if [[ ${resultTmp} -eq 0 ]]; then if [[ ${resultTmp} -eq 0 ]] || [[ ${resultTmp2} -eq 0 ]]; then
echo "ERROR, png files detected in /drawable" echo "ERROR, png files detected in /drawable"
resultPngInDrawable=1 resultPngInDrawable=1
else else

View File

@@ -76,3 +76,7 @@
-keep class org.bouncycastle.** { *; } -keep class org.bouncycastle.** { *; }
-keepnames class org.bouncycastle.** { *; } -keepnames class org.bouncycastle.** { *; }
-dontwarn org.bouncycastle.** -dontwarn org.bouncycastle.**
# JNA
-keep class com.sun.jna.** { *; }
-keep class * implements com.sun.jna.** { *; }

View File

@@ -22,6 +22,7 @@ import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
@@ -46,7 +47,15 @@ class DebugPermissionActivity : VectorBaseActivity<ActivityDebugPermissionBindin
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_CONTACTS Manifest.permission.READ_CONTACTS
) ) + getAndroid13Permissions()
private fun getAndroid13Permissions(): List<String> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
listOf(Manifest.permission.POST_NOTIFICATIONS)
} else {
emptyList()
}
}
private var lastPermissions = emptyList<String>() private var lastPermissions = emptyList<String>()
@@ -77,6 +86,14 @@ class DebugPermissionActivity : VectorBaseActivity<ActivityDebugPermissionBindin
lastPermissions = listOf(Manifest.permission.READ_CONTACTS) lastPermissions = listOf(Manifest.permission.READ_CONTACTS)
checkPerm() checkPerm()
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
views.notification.setOnClickListener {
lastPermissions = listOf(Manifest.permission.POST_NOTIFICATIONS)
checkPerm()
}
} else {
views.notification.isVisible = false
}
} }
private fun checkPerm() { private fun checkPerm() {

View File

@@ -85,11 +85,6 @@ class DebugFeaturesStateFactory @Inject constructor(
key = DebugFeatureKeys.newAppLayoutEnabled, key = DebugFeatureKeys.newAppLayoutEnabled,
factory = VectorFeatures::isNewAppLayoutFeatureEnabled factory = VectorFeatures::isNewAppLayoutFeatureEnabled
), ),
createBooleanFeature(
label = "Enable New Device Management",
key = DebugFeatureKeys.newDeviceManagementEnabled,
factory = VectorFeatures::isNewDeviceManagementEnabled
),
createBooleanFeature( createBooleanFeature(
label = "Enable QR Code Login", label = "Enable QR Code Login",
key = DebugFeatureKeys.qrCodeLoginEnabled, key = DebugFeatureKeys.qrCodeLoginEnabled,

View File

@@ -76,9 +76,6 @@ class DebugVectorFeatures(
override fun isNewAppLayoutFeatureEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled) override fun isNewAppLayoutFeatureEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
?: vectorFeatures.isNewAppLayoutFeatureEnabled() ?: vectorFeatures.isNewAppLayoutFeatureEnabled()
override fun isNewDeviceManagementEnabled(): Boolean = read(DebugFeatureKeys.newDeviceManagementEnabled)
?: vectorFeatures.isNewDeviceManagementEnabled()
override fun isQrCodeLoginEnabled() = read(DebugFeatureKeys.qrCodeLoginEnabled) override fun isQrCodeLoginEnabled() = read(DebugFeatureKeys.qrCodeLoginEnabled)
?: vectorFeatures.isQrCodeLoginEnabled() ?: vectorFeatures.isQrCodeLoginEnabled()
@@ -149,9 +146,7 @@ object DebugFeatureKeys {
val liveLocationSharing = booleanPreferencesKey("live-location-sharing") val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
val screenSharing = booleanPreferencesKey("screen-sharing") val screenSharing = booleanPreferencesKey("screen-sharing")
val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder") val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder")
val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg")
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled") val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled")
val newDeviceManagementEnabled = booleanPreferencesKey("new-device-management-enabled")
val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled") val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled")
val allowQrCodeLoginForAllServers = booleanPreferencesKey("allow-qr-code-login-for-all-servers") val allowQrCodeLoginForAllServers = booleanPreferencesKey("allow-qr-code-login-for-all-servers")
val allowReciprocateQrCodeLogin = booleanPreferencesKey("allow-reciprocate-qr-code-login") val allowReciprocateQrCodeLogin = booleanPreferencesKey("allow-reciprocate-qr-code-login")

View File

@@ -30,43 +30,43 @@
android:id="@+id/camera" android:id="@+id/camera"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="CAMERA" android:text="CAMERA" />
android:textAllCaps="false" />
<Button <Button
android:id="@+id/audio" android:id="@+id/audio"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="RECORD_AUDIO" android:text="RECORD_AUDIO" />
android:textAllCaps="false" />
<Button <Button
android:id="@+id/camera_audio" android:id="@+id/camera_audio"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="CAMERA + RECORD_AUDIO" android:text="CAMERA + RECORD_AUDIO" />
android:textAllCaps="false" />
<Button <Button
android:id="@+id/write" android:id="@+id/write"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="WRITE_EXTERNAL_STORAGE" android:text="WRITE_EXTERNAL_STORAGE" />
android:textAllCaps="false" />
<Button <Button
android:id="@+id/read" android:id="@+id/read"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="READ_EXTERNAL_STORAGE" android:text="READ_EXTERNAL_STORAGE" />
android:textAllCaps="false" />
<Button <Button
android:id="@+id/contact" android:id="@+id/contact"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="READ_CONTACTS" android:text="READ_CONTACTS" />
android:textAllCaps="false" />
<Button
android:id="@+id/notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="POST_NOTIFICATIONS" />
</LinearLayout> </LinearLayout>

View File

@@ -15,8 +15,6 @@
*/ */
package im.vector.app.fdroid.features.settings.troubleshoot package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
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 im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
@@ -32,7 +30,7 @@ class TestAutoStartBoot @Inject constructor(
) : ) :
TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) { TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(testParameters: TestParameters) {
if (vectorPreferences.autoStartOnBoot()) { if (vectorPreferences.autoStartOnBoot()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success) description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
@@ -42,7 +40,7 @@ class TestAutoStartBoot @Inject constructor(
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
override fun doFix() { override fun doFix() {
vectorPreferences.setAutoStartOnBoot(true) vectorPreferences.setAutoStartOnBoot(true)
manager?.retry(activityResultLauncher) manager?.retry(testParameters)
} }
} }
status = TestStatus.FAILED status = TestStatus.FAILED

View File

@@ -15,9 +15,7 @@
*/ */
package im.vector.app.fdroid.features.settings.troubleshoot package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import android.net.ConnectivityManager import android.net.ConnectivityManager
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.net.ConnectivityManagerCompat import androidx.core.net.ConnectivityManagerCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@@ -32,7 +30,7 @@ class TestBackgroundRestrictions @Inject constructor(
) : ) :
TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) { TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(testParameters: TestParameters) {
context.getSystemService<ConnectivityManager>()!!.apply { context.getSystemService<ConnectivityManager>()!!.apply {
// Checks if the device is on a metered network // Checks if the device is on a metered network
if (isActiveNetworkMetered) { if (isActiveNetworkMetered) {

View File

@@ -15,8 +15,6 @@
*/ */
package im.vector.app.fdroid.features.settings.troubleshoot package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
@@ -30,7 +28,7 @@ class TestBatteryOptimization @Inject constructor(
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) { ) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(testParameters: TestParameters) {
if (context.isIgnoringBatteryOptimizations()) { if (context.isIgnoringBatteryOptimizations()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success) description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success)
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
@@ -39,7 +37,7 @@ class TestBatteryOptimization @Inject constructor(
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed) description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) {
override fun doFix() { override fun doFix() {
requestDisablingBatteryOptimization(context, activityResultLauncher) requestDisablingBatteryOptimization(context, testParameters.activityResultLauncher)
} }
} }
status = TestStatus.FAILED status = TestStatus.FAILED

View File

@@ -15,8 +15,6 @@
*/ */
package im.vector.app.gplay.features.settings.troubleshoot package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import im.vector.app.R import im.vector.app.R
@@ -36,7 +34,7 @@ class TestFirebaseToken @Inject constructor(
private val fcmHelper: FcmHelper, private val fcmHelper: FcmHelper,
) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) { ) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(testParameters: TestParameters) {
status = TestStatus.RUNNING status = TestStatus.RUNNING
try { try {
FirebaseMessaging.getInstance().token FirebaseMessaging.getInstance().token
@@ -53,7 +51,7 @@ class TestFirebaseToken @Inject constructor(
"ACCOUNT_MISSING" -> { "ACCOUNT_MISSING" -> {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() { override fun doFix() {
startAddGoogleAccountIntent(context, activityResultLauncher) startAddGoogleAccountIntent(context, testParameters.activityResultLauncher)
} }
} }
stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg) stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)

View File

@@ -15,8 +15,6 @@
*/ */
package im.vector.app.gplay.features.settings.troubleshoot package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
@@ -35,7 +33,7 @@ class TestPlayServices @Inject constructor(
) : ) :
TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) { TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(testParameters: TestParameters) {
val apiAvailability = GoogleApiAvailability.getInstance() val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode == ConnectionResult.SUCCESS) { if (resultCode == ConnectionResult.SUCCESS) {

View File

@@ -15,8 +15,6 @@
*/ */
package im.vector.app.gplay.features.settings.troubleshoot package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.work.WorkInfo import androidx.work.WorkInfo
@@ -42,7 +40,7 @@ class TestTokenRegistration @Inject constructor(
) : ) :
TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) { TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(testParameters: TestParameters) {
// Check if we have a registered pusher for this token // Check if we have a registered pusher for this token
val fcmToken = fcmHelper.getFcmToken() ?: run { val fcmToken = fcmHelper.getFcmToken() ?: run {
status = TestStatus.FAILED status = TestStatus.FAILED
@@ -66,9 +64,9 @@ class TestTokenRegistration @Inject constructor(
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo != null) { if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) { if (workInfo.state == WorkInfo.State.SUCCEEDED) {
manager?.retry(activityResultLauncher) manager?.retry(testParameters)
} else if (workInfo.state == WorkInfo.State.FAILED) { } else if (workInfo.state == WorkInfo.State.FAILED) {
manager?.retry(activityResultLauncher) manager?.retry(testParameters)
} }
} }
}) })

View File

@@ -41,6 +41,7 @@
<bool name="settings_labs_deferred_dm_default">true</bool> <bool name="settings_labs_deferred_dm_default">true</bool>
<bool name="settings_labs_thread_messages_default">false</bool> <bool name="settings_labs_thread_messages_default">false</bool>
<bool name="settings_labs_new_app_layout_default">true</bool> <bool name="settings_labs_new_app_layout_default">true</bool>
<bool name="settings_labs_new_session_manager_default">false</bool>
<bool name="settings_timeline_show_live_sender_info_visible">true</bool> <bool name="settings_timeline_show_live_sender_info_visible">true</bool>
<bool name="settings_timeline_show_live_sender_info_default">false</bool> <bool name="settings_timeline_show_live_sender_info_default">false</bool>
<bool name="settings_labs_rich_text_editor_visible">true</bool> <bool name="settings_labs_rich_text_editor_visible">true</bool>

View File

@@ -174,9 +174,6 @@ dependencies {
// Paging // Paging
implementation libs.androidx.pagingRuntimeKtx implementation libs.androidx.pagingRuntimeKtx
// Functional Programming
implementation libs.arrow.core
// Pref // Pref
api libs.androidx.preferenceKtx api libs.androidx.preferenceKtx

View File

@@ -17,6 +17,9 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- https://developer.android.com/develop/ui/views/notifications/notification-permission#exemptions -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Call feature --> <!-- Call feature -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" /> <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<!-- Commented because Google PlayStore does not like we add permission if we are not requiring it. And it was added for future use --> <!-- Commented because Google PlayStore does not like we add permission if we are not requiring it. And it was added for future use -->

View File

@@ -17,11 +17,11 @@
package im.vector.app package im.vector.app
import arrow.core.Option
import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.core.utils.BehaviorDataSource
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Optional
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class ActiveSessionDataSource @Inject constructor() : BehaviorDataSource<Option<Session>>() class ActiveSessionDataSource @Inject constructor() : BehaviorDataSource<Optional<Session>>()

View File

@@ -17,10 +17,10 @@
package im.vector.app package im.vector.app
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import arrow.core.Option
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.Optional
/** /**
* Gets info about the current space the user has navigated to, any space backstack they may have * Gets info about the current space the user has navigated to, any space backstack they may have
@@ -62,7 +62,7 @@ interface SpaceStateHandler : DefaultLifecycleObserver {
/** /**
* Gets a flow of the selected space for clients to react immediately to space changes. * Gets a flow of the selected space for clients to react immediately to space changes.
*/ */
fun getSelectedSpaceFlow(): Flow<Option<RoomSummary>> fun getSelectedSpaceFlow(): Flow<Optional<RoomSummary>>
/** /**
* Gets the id of the active space, or null if there is none. * Gets the id of the active space, or null if there is none.

View File

@@ -17,7 +17,6 @@
package im.vector.app package im.vector.app
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
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.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
@@ -42,6 +41,8 @@ import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.sync.SyncRequestState
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOption
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -59,7 +60,7 @@ class SpaceStateHandlerImpl @Inject constructor(
) : SpaceStateHandler { ) : SpaceStateHandler {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomSummary>>(Option.empty()) private val selectedSpaceDataSource = BehaviorDataSource<Optional<RoomSummary>>(Optional.empty())
private val selectedSpaceFlow = selectedSpaceDataSource.stream() private val selectedSpaceFlow = selectedSpaceDataSource.stream()
override fun getCurrentSpace(): RoomSummary? { override fun getCurrentSpace(): RoomSummary? {
@@ -98,11 +99,7 @@ class SpaceStateHandlerImpl @Inject constructor(
uiStateRepository.storeSelectedSpace(spaceToSet?.roomId, activeSession.sessionId) uiStateRepository.storeSelectedSpace(spaceToSet?.roomId, activeSession.sessionId)
} }
if (spaceToSet == null) { selectedSpaceDataSource.post(spaceToSet.toOption())
selectedSpaceDataSource.post(Option.empty())
} else {
selectedSpaceDataSource.post(Option.just(spaceToSet))
}
if (spaceId != null) { if (spaceId != null) {
activeSession.coroutineScope.launch(Dispatchers.IO) { activeSession.coroutineScope.launch(Dispatchers.IO) {

View File

@@ -19,19 +19,19 @@ package im.vector.app.core.animations
import android.animation.Animator import android.animation.Animator
open class SimpleAnimatorListener : Animator.AnimatorListener { open class SimpleAnimatorListener : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) { override fun onAnimationRepeat(animation: Animator) {
// No op // No op
} }
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator) {
// No op // No op
} }
override fun onAnimationCancel(animation: Animator?) { override fun onAnimationCancel(animation: Animator) {
// No op // No op
} }
override fun onAnimationStart(animation: Animator?) { override fun onAnimationStart(animation: Animator) {
// No op // No op
} }
} }

View File

@@ -17,12 +17,11 @@
package im.vector.app.core.di package im.vector.app.core.di
import android.content.Context import android.content.Context
import arrow.core.Option
import im.vector.app.ActiveSessionDataSource import im.vector.app.ActiveSessionDataSource
import im.vector.app.core.extensions.configureAndStart
import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
@@ -31,6 +30,8 @@ import im.vector.app.features.session.SessionListener
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOption
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject import javax.inject.Inject
@@ -50,6 +51,7 @@ class ActiveSessionHolder @Inject constructor(
private val sessionInitializer: SessionInitializer, private val sessionInitializer: SessionInitializer,
private val applicationContext: Context, private val applicationContext: Context,
private val authenticationService: AuthenticationService, private val authenticationService: AuthenticationService,
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
) { ) {
private var activeSessionReference: AtomicReference<Session?> = AtomicReference() private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
@@ -57,7 +59,7 @@ class ActiveSessionHolder @Inject constructor(
fun setActiveSession(session: Session) { fun setActiveSession(session: Session) {
Timber.w("setActiveSession of ${session.myUserId}") Timber.w("setActiveSession of ${session.myUserId}")
activeSessionReference.set(session) activeSessionReference.set(session)
activeSessionDataSource.post(Option.just(session)) activeSessionDataSource.post(session.toOption())
keyRequestHandler.start(session) keyRequestHandler.start(session)
incomingVerificationRequestHandler.start(session) incomingVerificationRequestHandler.start(session)
@@ -77,7 +79,7 @@ class ActiveSessionHolder @Inject constructor(
} }
activeSessionReference.set(null) activeSessionReference.set(null)
activeSessionDataSource.post(Option.empty()) activeSessionDataSource.post(Optional.empty())
keyRequestHandler.stop() keyRequestHandler.stop()
incomingVerificationRequestHandler.stop() incomingVerificationRequestHandler.stop()
@@ -109,7 +111,9 @@ class ActiveSessionHolder @Inject constructor(
} }
?: sessionInitializer.tryInitialize(readCurrentSession = { activeSessionReference.get() }) { session -> ?: sessionInitializer.tryInitialize(readCurrentSession = { activeSessionReference.get() }) { session ->
setActiveSession(session) setActiveSession(session)
session.configureAndStart(applicationContext, startSyncing = startSync) runBlocking {
configureAndStartSessionUseCase.execute(session, startSyncing = startSync)
}
} }
} }

View File

@@ -21,6 +21,7 @@ import android.os.Parcelable
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.core.platform.DefaultListUpdateCallback
import im.vector.app.core.platform.Restorable import im.vector.app.core.platform.Restorable
import im.vector.lib.core.utils.compat.getParcelableCompat
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
private const val LAYOUT_MANAGER_STATE = "LAYOUT_MANAGER_STATE" private const val LAYOUT_MANAGER_STATE = "LAYOUT_MANAGER_STATE"
@@ -44,7 +45,7 @@ class LayoutManagerStateRestorer(layoutManager: RecyclerView.LayoutManager) : Re
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle?) { override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
val parcelable = savedInstanceState?.getParcelable<Parcelable>(LAYOUT_MANAGER_STATE) val parcelable = savedInstanceState?.getParcelableCompat<Parcelable>(LAYOUT_MANAGER_STATE)
layoutManagerState.set(parcelable) layoutManagerState.set(parcelable)
} }

View File

@@ -25,6 +25,7 @@ import com.airbnb.mvrx.MavericksViewModelProvider
inline fun <reified VM : MavericksViewModel<S>, reified S : MavericksState> ComponentActivity.lazyViewModel(): Lazy<VM> { inline fun <reified VM : MavericksViewModel<S>, reified S : MavericksState> ComponentActivity.lazyViewModel(): Lazy<VM> {
return lazy(mode = LazyThreadSafetyMode.NONE) { return lazy(mode = LazyThreadSafetyMode.NONE) {
@Suppress("DEPRECATION")
MavericksViewModelProvider.get( MavericksViewModelProvider.get(
viewModelClass = VM::class.java, viewModelClass = VM::class.java,
stateClass = S::class.java, stateClass = S::class.java,

View File

@@ -24,20 +24,8 @@ import im.vector.app.core.services.VectorSyncAndroidService
import im.vector.app.features.session.VectorSessionStore import im.vector.app.features.session.VectorSessionStore
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.sync.FilterService
import timber.log.Timber import timber.log.Timber
fun Session.configureAndStart(context: Context, startSyncing: Boolean = true) {
Timber.i("Configure and start session for $myUserId. startSyncing: $startSyncing")
open()
filterService().setFilter(FilterService.FilterPreset.ElementFilter)
if (startSyncing) {
startSyncing(context)
}
pushersService().refreshPushers()
context.singletonEntryPoint().webRtcCallManager().checkForProtocolsSupportIfNeeded()
}
fun Session.startSyncing(context: Context) { fun Session.startSyncing(context: Context) {
val applicationContext = context.applicationContext val applicationContext = context.applicationContext
if (!syncService().hasAlreadySynced()) { if (!syncService().hasAlreadySynced()) {

View File

@@ -22,6 +22,7 @@ import android.animation.ValueAnimator
import android.view.View import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import im.vector.app.core.animations.SimpleAnimatorListener
fun ViewPager2.setCurrentItem( fun ViewPager2.setCurrentItem(
item: Int, item: Int,
@@ -45,19 +46,16 @@ fun ViewPager2.setCurrentItem(
previousValue = currentValue previousValue = currentValue
}.onFailure { animator.cancel() } }.onFailure { animator.cancel() }
} }
animator.addListener(object : Animator.AnimatorListener { animator.addListener(object : SimpleAnimatorListener() {
override fun onAnimationStart(animation: Animator?) { override fun onAnimationStart(animation: Animator) {
isUserInputEnabled = false isUserInputEnabled = false
beginFakeDrag() beginFakeDrag()
} }
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator) {
isUserInputEnabled = true isUserInputEnabled = true
endFakeDrag() endFakeDrag()
} }
override fun onAnimationCancel(animation: Animator?) = Unit
override fun onAnimationRepeat(animation: Animator?) = Unit
}) })
animator.interpolator = interpolator animator.interpolator = interpolator
animator.duration = duration animator.duration = duration

View File

@@ -24,7 +24,6 @@ import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import arrow.core.Try
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import okio.source import okio.source
@@ -35,25 +34,23 @@ import java.io.File
* Save a string to a file with Okio. * Save a string to a file with Okio.
*/ */
@WorkerThread @WorkerThread
fun writeToFile(str: String, file: File): Try<Unit> { @Throws
return Try<Unit> { fun writeToFile(str: String, file: File) {
file.sink().buffer().use { file.sink().buffer().use {
it.writeString(str, Charsets.UTF_8) it.writeString(str, Charsets.UTF_8)
} }
} }
}
/** /**
* Save a byte array to a file with Okio. * Save a byte array to a file with Okio.
*/ */
@WorkerThread @WorkerThread
fun writeToFile(data: ByteArray, file: File): Try<Unit> { @Throws
return Try<Unit> { fun writeToFile(data: ByteArray, file: File) {
file.sink().buffer().use { file.sink().buffer().use {
it.write(data) it.write(data)
} }
} }
}
fun addEntryToDownloadManager( fun addEntryToDownloadManager(
context: Context, context: Context,

View File

@@ -83,6 +83,7 @@ abstract class SimpleFragmentActivity : VectorBaseActivity<ActivityBinding>() {
// ignore // ignore
return return
} }
@Suppress("DEPRECATION")
super.onBackPressed() super.onBackPressed()
} }
} }

View File

@@ -505,6 +505,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private fun onBackPressed(fromToolbar: Boolean) { private fun onBackPressed(fromToolbar: Boolean) {
val handled = recursivelyDispatchOnBackPressed(supportFragmentManager, fromToolbar) val handled = recursivelyDispatchOnBackPressed(supportFragmentManager, fromToolbar)
if (!handled) { if (!handled) {
@Suppress("DEPRECATION")
super.onBackPressed() super.onBackPressed()
} }
} }

View File

@@ -23,6 +23,7 @@ import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.pushers.HttpPusher import org.matrix.android.sdk.api.session.pushers.HttpPusher
import org.matrix.android.sdk.api.session.pushers.Pusher
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
@@ -90,6 +91,18 @@ class PushersManager @Inject constructor(
) )
} }
fun getPusherForCurrentSession(): Pusher? {
val session = activeSessionHolder.getSafeActiveSession() ?: return null
val deviceId = session.sessionParams.deviceId
return session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId }
}
suspend fun togglePusherForCurrentSession(enable: Boolean) {
val session = activeSessionHolder.getSafeActiveSession() ?: return
val pusher = getPusherForCurrentSession() ?: return
session.pushersService().togglePusher(pusher, enable)
}
suspend fun unregisterEmailPusher(email: String) { suspend fun unregisterEmailPusher(email: String) {
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.pushersService().removeEmailPusher(email) currentSession.pushersService().removeEmailPusher(email)

View File

@@ -19,6 +19,7 @@ package im.vector.app.core.resources
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.annotation.NonNull import androidx.annotation.NonNull
import org.matrix.android.sdk.api.util.getPackageInfoCompat
import javax.inject.Inject import javax.inject.Inject
class VersionCodeProvider @Inject constructor(private val context: Context) { class VersionCodeProvider @Inject constructor(private val context: Context) {
@@ -28,7 +29,7 @@ class VersionCodeProvider @Inject constructor(private val context: Context) {
*/ */
@NonNull @NonNull
fun getVersionCode(): Long { fun getVersionCode(): Long {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode packageInfo.longVersionCode

View File

@@ -23,6 +23,7 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
class BluetoothHeadsetReceiver : BroadcastReceiver() { class BluetoothHeadsetReceiver : BroadcastReceiver() {
@@ -59,7 +60,7 @@ class BluetoothHeadsetReceiver : BroadcastReceiver() {
else -> return // ignore intermediate states else -> return // ignore intermediate states
} }
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) val device = intent.getParcelableExtraCompat<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
val deviceName = device?.name val deviceName = device?.name
when (device?.bluetoothClass?.deviceClass) { when (device?.bluetoothClass?.deviceClass) {
BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE, BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE,

View File

@@ -39,6 +39,8 @@ 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.popup.IncomingCallAlert import im.vector.app.features.popup.IncomingCallAlert
import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.PopupAlertManager
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import im.vector.lib.core.utils.compat.getSerializableExtraCompat
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.room.model.call.EndCallReason import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
@@ -71,7 +73,7 @@ class CallAndroidService : VectorAndroidService() {
private var mediaSession: MediaSessionCompat? = null private var mediaSession: MediaSessionCompat? = null
private val mediaSessionButtonCallback = object : MediaSessionCompat.Callback() { private val mediaSessionButtonCallback = object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean { override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
val keyEvent = mediaButtonEvent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT) ?: return false val keyEvent = mediaButtonEvent?.getParcelableExtraCompat<KeyEvent>(Intent.EXTRA_KEY_EVENT) ?: return false
if (keyEvent.keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { if (keyEvent.keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
callManager.headSetButtonTapped() callManager.headSetButtonTapped()
return true return true
@@ -158,7 +160,7 @@ class CallAndroidService : VectorAndroidService() {
val incomingCallAlert = IncomingCallAlert(callId, val incomingCallAlert = IncomingCallAlert(callId,
shouldBeDisplayedIn = { activity -> shouldBeDisplayedIn = { activity ->
if (activity is VectorCallActivity) { if (activity is VectorCallActivity) {
activity.intent.getParcelableExtra<CallArgs>(Mavericks.KEY_ARG)?.callId != call.callId activity.intent.getParcelableExtraCompat<CallArgs>(Mavericks.KEY_ARG)?.callId != call.callId
} else true } else true
} }
).apply { ).apply {
@@ -188,7 +190,7 @@ class CallAndroidService : VectorAndroidService() {
private fun handleCallTerminated(intent: Intent) { private fun handleCallTerminated(intent: Intent) {
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason val endCallReason = intent.getSerializableExtraCompat<EndCallReason>(EXTRA_END_CALL_REASON)
val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false) val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false)
alertManager.cancelAlert(callId) alertManager.cancelAlert(callId)
val terminatedCall = knownCalls.remove(callId) val terminatedCall = knownCalls.remove(callId)
@@ -202,7 +204,7 @@ class CallAndroidService : VectorAndroidService() {
startForeground(notificationId, notification) startForeground(notificationId, notification)
if (knownCalls.isEmpty()) { if (knownCalls.isEmpty()) {
Timber.tag(loggerTag.value).v("No more call, stop the service") Timber.tag(loggerTag.value).v("No more call, stop the service")
stopForeground(true) stopForegroundCompat()
mediaSession?.isActive = false mediaSession?.isActive = false
myStopSelf() myStopSelf()
} }

View File

@@ -18,6 +18,7 @@ package im.vector.app.core.services
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.IBinder import android.os.IBinder
import timber.log.Timber import timber.log.Timber
@@ -55,4 +56,13 @@ abstract class VectorAndroidService : Service() {
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
return null return null
} }
protected fun stopForegroundCompat() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
}
} }

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2022 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.session
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase
import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.sync.FilterService
import timber.log.Timber
import javax.inject.Inject
class ConfigureAndStartSessionUseCase @Inject constructor(
@ApplicationContext private val context: Context,
private val webRtcCallManager: WebRtcCallManager,
private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
) {
suspend fun execute(session: Session, startSyncing: Boolean = true) {
Timber.i("Configure and start session for ${session.myUserId}. startSyncing: $startSyncing")
session.open()
session.filterService().setFilter(FilterService.FilterPreset.ElementFilter)
if (startSyncing) {
session.startSyncing(context)
}
session.pushersService().refreshPushers()
webRtcCallManager.checkForProtocolsSupportIfNeeded()
updateMatrixClientInfoUseCase.execute(session)
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 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.session.clientinfo
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toModel
import javax.inject.Inject
/**
* This use case retrieves the current account data event containing extended client info
* for a given deviceId.
*/
class GetMatrixClientInfoUseCase @Inject constructor() {
fun execute(session: Session, deviceId: String): MatrixClientInfoContent? {
val type = MATRIX_CLIENT_INFO_KEY_PREFIX + deviceId
val content = session.accountDataService().getUserAccountDataEvent(type)?.content
return content.toModel()
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 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.session.clientinfo
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class MatrixClientInfoContent(
// app name
@Json(name = "name")
val name: String? = null,
// app version
@Json(name = "version")
val version: String? = null,
// app url (optional, applicable only for web)
@Json(name = "url")
val url: String? = null,
)

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2022 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.session.clientinfo
class NoDeviceIdError : IllegalStateException("device id is empty")

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2022 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.session.clientinfo
/**
* Prefix for the key account data event which holds client info.
*/
internal const val MATRIX_CLIENT_INFO_KEY_PREFIX = "io.element.matrix_client_information."

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2022 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.session.clientinfo
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toContent
import javax.inject.Inject
/**
* This use case sets the account data event containing extended client info.
*/
class SetMatrixClientInfoUseCase @Inject constructor() {
suspend fun execute(session: Session, clientInfo: MatrixClientInfoContent): Result<Unit> = runCatching {
val deviceId = session.sessionParams.deviceId.orEmpty()
if (deviceId.isNotEmpty()) {
val type = MATRIX_CLIENT_INFO_KEY_PREFIX + deviceId
session.accountDataService()
.updateUserAccountData(type, clientInfo.toContent())
} else {
throw NoDeviceIdError()
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2022 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.session.clientinfo
import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.BuildMeta
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import javax.inject.Inject
/**
* This use case updates if needed the account data event containing extended client info.
*/
class UpdateMatrixClientInfoUseCase @Inject constructor(
private val appNameProvider: AppNameProvider,
private val buildMeta: BuildMeta,
private val getMatrixClientInfoUseCase: GetMatrixClientInfoUseCase,
private val setMatrixClientInfoUseCase: SetMatrixClientInfoUseCase,
) {
suspend fun execute(session: Session) = runCatching {
val clientInfo = MatrixClientInfoContent(
name = appNameProvider.getAppName(),
version = buildMeta.versionName
)
val deviceId = session.sessionParams.deviceId.orEmpty()
if (deviceId.isNotEmpty()) {
val storedClientInfo = getMatrixClientInfoUseCase.execute(session, deviceId)
Timber.d("storedClientInfo=$storedClientInfo, current client info=$clientInfo")
if (clientInfo != storedClientInfo) {
Timber.d("client info need to be updated")
return setMatrixClientInfoUseCase.execute(session, clientInfo)
}
} else {
throw NoDeviceIdError()
}
}
}

View File

@@ -34,6 +34,7 @@ import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.app.R import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import org.matrix.android.sdk.api.util.getApplicationInfoCompat
/** /**
* Tells if the application ignores battery optimizations. * Tells if the application ignores battery optimizations.
@@ -63,7 +64,7 @@ fun Context.isAnimationEnabled(): Boolean {
*/ */
fun Context.getApplicationLabel(packageName: String): String { fun Context.getApplicationLabel(packageName: String): String {
return try { return try {
val ai = packageManager.getApplicationInfo(packageName, 0) val ai = packageManager.getApplicationInfoCompat(packageName, 0)
packageManager.getApplicationLabel(ai).toString() packageManager.getApplicationLabel(ai).toString()
} catch (e: PackageManager.NameNotFoundException) { } catch (e: PackageManager.NameNotFoundException) {
packageName packageName

View File

@@ -57,6 +57,7 @@ import im.vector.app.features.start.StartAppViewModel
import im.vector.app.features.start.StartAppViewState import im.vector.app.features.start.StartAppViewState
import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -181,7 +182,7 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
private fun handleAppStarted() { private fun handleAppStarted() {
if (intent.hasExtra(EXTRA_NEXT_INTENT)) { if (intent.hasExtra(EXTRA_NEXT_INTENT)) {
// Start the next Activity // Start the next Activity
val nextIntent = intent.getParcelableExtra<Intent>(EXTRA_NEXT_INTENT) val nextIntent = intent.getParcelableExtraCompat<Intent>(EXTRA_NEXT_INTENT)
startIntentAndFinish(nextIntent) startIntentAndFinish(nextIntent)
} else if (intent.hasExtra(EXTRA_INIT_SESSION)) { } else if (intent.hasExtra(EXTRA_INIT_SESSION)) {
setResult(RESULT_OK) setResult(RESULT_OK)
@@ -218,7 +219,7 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
} }
private fun parseArgs(): MainActivityArgs { private fun parseArgs(): MainActivityArgs {
val argsFromIntent: MainActivityArgs? = intent.getParcelableExtra(EXTRA_ARGS) val argsFromIntent: MainActivityArgs? = intent.getParcelableExtraCompat(EXTRA_ARGS)
Timber.w("Starting MainActivity with $argsFromIntent") Timber.w("Starting MainActivity with $argsFromIntent")
return MainActivityArgs( return MainActivityArgs(

View File

@@ -40,7 +40,6 @@ interface VectorFeatures {
* use [VectorPreferences.isNewAppLayoutEnabled] instead. * use [VectorPreferences.isNewAppLayoutEnabled] instead.
*/ */
fun isNewAppLayoutFeatureEnabled(): Boolean fun isNewAppLayoutFeatureEnabled(): Boolean
fun isNewDeviceManagementEnabled(): Boolean
fun isQrCodeLoginEnabled(): Boolean fun isQrCodeLoginEnabled(): Boolean
fun allowQrCodeLoginForAllServers(): Boolean fun allowQrCodeLoginForAllServers(): Boolean
fun allowReciprocateQrCodeLogin(): Boolean fun allowReciprocateQrCodeLogin(): Boolean
@@ -60,7 +59,6 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING
override fun forceUsageOfOpusEncoder(): Boolean = false override fun forceUsageOfOpusEncoder(): Boolean = false
override fun isNewAppLayoutFeatureEnabled(): Boolean = true override fun isNewAppLayoutFeatureEnabled(): Boolean = true
override fun isNewDeviceManagementEnabled(): Boolean = false
override fun isQrCodeLoginEnabled(): Boolean = false override fun isQrCodeLoginEnabled(): Boolean = false
override fun allowQrCodeLoginForAllServers(): Boolean = false override fun allowQrCodeLoginForAllServers(): Boolean = false
override fun allowReciprocateQrCodeLogin(): Boolean = false override fun allowReciprocateQrCodeLogin(): Boolean = false

View File

@@ -26,6 +26,8 @@ import im.vector.app.core.dialogs.PhotoOrVideoDialog
import im.vector.app.core.platform.Restorable import im.vector.app.core.platform.Restorable
import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.lib.core.utils.compat.getParcelableCompat
import im.vector.lib.core.utils.compat.getSerializableCompat
import im.vector.lib.multipicker.MultiPicker import im.vector.lib.multipicker.MultiPicker
import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import timber.log.Timber import timber.log.Timber
@@ -66,8 +68,8 @@ class AttachmentsHelper(
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle?) { override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
captureUri = savedInstanceState?.getParcelable(CAPTURE_PATH_KEY) as? Uri captureUri = savedInstanceState?.getParcelableCompat(CAPTURE_PATH_KEY)
pendingType = savedInstanceState?.getSerializable(PENDING_TYPE_KEY) as? AttachmentTypeSelectorView.Type pendingType = savedInstanceState?.getSerializableCompat(PENDING_TYPE_KEY)
} }
// Public Methods // Public Methods

View File

@@ -24,6 +24,8 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat
import im.vector.lib.core.utils.compat.getParcelableCompat
import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.content.ContentAttachmentData
@AndroidEntryPoint @AndroidEntryPoint
@@ -41,7 +43,7 @@ class AttachmentsPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
} }
fun getOutput(intent: Intent): List<ContentAttachmentData> { fun getOutput(intent: Intent): List<ContentAttachmentData> {
return intent.getParcelableArrayListExtra<ContentAttachmentData>(ATTACHMENTS_PREVIEW_RESULT).orEmpty() return intent.getParcelableArrayListExtraCompat<ContentAttachmentData>(ATTACHMENTS_PREVIEW_RESULT).orEmpty()
} }
fun getKeepOriginalSize(intent: Intent): Boolean { fun getKeepOriginalSize(intent: Intent): Boolean {
@@ -57,7 +59,7 @@ class AttachmentsPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
override fun initUiAndData() { override fun initUiAndData() {
if (isFirstCreation()) { if (isFirstCreation()) {
val fragmentArgs: AttachmentsPreviewArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS) ?: return val fragmentArgs: AttachmentsPreviewArgs = intent?.extras?.getParcelableCompat(EXTRA_FRAGMENT_ARGS) ?: return
addFragment(views.simpleFragmentContainer, AttachmentsPreviewFragment::class.java, fragmentArgs) addFragment(views.simpleFragmentContainer, AttachmentsPreviewFragment::class.java, fragmentArgs)
} }
} }

View File

@@ -69,6 +69,7 @@ import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import io.github.hyuwah.draggableviewlib.DraggableView import io.github.hyuwah.draggableviewlib.DraggableView
import io.github.hyuwah.draggableviewlib.setupDraggable import io.github.hyuwah.draggableviewlib.setupDraggable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -178,7 +179,7 @@ class VectorCallActivity :
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) } intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) }
?.let { intent.getParcelableExtra<CallArgs>(Mavericks.KEY_ARG) } ?.let { intent.getParcelableExtraCompat<CallArgs>(Mavericks.KEY_ARG) }
?.let { ?.let {
callViewModel.handle(VectorCallViewActions.SwitchCall(it)) callViewModel.handle(VectorCallViewActions.SwitchCall(it))
} }
@@ -193,6 +194,7 @@ class VectorCallActivity :
override fun onBackPressed() { override fun onBackPressed() {
if (!enterPictureInPictureIfRequired()) { if (!enterPictureInPictureIfRequired()) {
@Suppress("DEPRECATION")
super.onBackPressed() super.onBackPressed()
} }
} }
@@ -230,6 +232,7 @@ class VectorCallActivity :
} }
android.R.id.home -> { android.R.id.home -> {
// We check here as we want PiP in some cases // We check here as we want PiP in some cases
@Suppress("DEPRECATION")
onBackPressed() onBackPressed()
true true
} }

View File

@@ -38,6 +38,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityJitsiBinding import im.vector.app.databinding.ActivityJitsiBinding
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.jitsi.meet.sdk.JitsiMeet import org.jitsi.meet.sdk.JitsiMeet
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
@@ -200,7 +201,7 @@ class VectorJitsiActivity : VectorBaseActivity<ActivityJitsiBinding>(), JitsiMee
// Is it a switch to another conf? // Is it a switch to another conf?
intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) } intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) }
?.let { intent.getParcelableExtra<Args>(Mavericks.KEY_ARG) } ?.let { intent.getParcelableExtraCompat<Args>(Mavericks.KEY_ARG) }
?.let { ?.let {
jitsiViewModel.handle(JitsiCallViewActions.SwitchTo(it, true)) jitsiViewModel.handle(JitsiCallViewActions.SwitchTo(it, true))
} }

View File

@@ -28,6 +28,7 @@ import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityCallTransferBinding import im.vector.app.databinding.ActivityCallTransferBinding
import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import javax.inject.Inject import javax.inject.Inject
@@ -112,7 +113,7 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>() {
} }
fun getCallTransferResult(intent: Intent?): CallTransferResult? { fun getCallTransferResult(intent: Intent?): CallTransferResult? {
return intent?.extras?.getParcelable(EXTRA_TRANSFER_RESULT) return intent?.extras?.getParcelableCompat(EXTRA_TRANSFER_RESULT)
} }
} }
} }

View File

@@ -78,6 +78,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
sharedActionViewModel sharedActionViewModel
.stream() .stream()
.onEach { action -> .onEach { action ->
@Suppress("DEPRECATION")
when (action) { when (action) {
UserListSharedAction.Close -> finish() UserListSharedAction.Close -> finish()
UserListSharedAction.GoBack -> onBackPressed() UserListSharedAction.GoBack -> onBackPressed()

View File

@@ -52,6 +52,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
override fun onBackPressed() { override fun onBackPressed() {
hideWaitingView() hideWaitingView()
@Suppress("DEPRECATION")
super.onBackPressed() super.onBackPressed()
} }

View File

@@ -114,6 +114,7 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
finish() finish()
return return
} }
@Suppress("DEPRECATION")
super.onBackPressed() super.onBackPressed()
} }
} }

View File

@@ -183,6 +183,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
} }
.show() .show()
} else { } else {
@Suppress("DEPRECATION")
super.onBackPressed() super.onBackPressed()
} }
} }

View File

@@ -25,7 +25,6 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import arrow.core.Try
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -167,7 +166,7 @@ class KeysBackupSetupStep3Fragment :
private fun exportRecoveryKeyToFile(uri: Uri, data: String) { private fun exportRecoveryKeyToFile(uri: Uri, data: String) {
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
Try { try {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
requireContext().safeOpenOutputStream(uri) requireContext().safeOpenOutputStream(uri)
?.use { os -> ?.use { os ->
@@ -176,24 +175,19 @@ class KeysBackupSetupStep3Fragment :
} }
} }
?: throw IOException("Unable to write the file") ?: throw IOException("Unable to write the file")
}
.fold(
{ throwable ->
activity?.let {
MaterialAlertDialogBuilder(it)
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(throwable))
}
},
{
viewModel.copyHasBeenMade = true viewModel.copyHasBeenMade = true
activity?.let { activity?.let {
MaterialAlertDialogBuilder(it) MaterialAlertDialogBuilder(it)
.setTitle(R.string.dialog_title_success) .setTitle(R.string.dialog_title_success)
.setMessage(R.string.recovery_key_export_saved) .setMessage(R.string.recovery_key_export_saved)
} }
} catch (throwable: Throwable) {
activity?.let {
MaterialAlertDialogBuilder(it)
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(throwable))
}
} }
)
?.setCancelable(false) ?.setCancelable(false)
?.setPositiveButton(R.string.ok, null) ?.setPositiveButton(R.string.ok, null)
?.show() ?.show()

View File

@@ -26,6 +26,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.lib.core.utils.compat.getParcelableCompat
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@@ -147,7 +148,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
R.drawable.ic_shield_black, R.drawable.ic_shield_black,
shouldBeDisplayedIn = { activity -> shouldBeDisplayedIn = { activity ->
if (activity is RoomDetailActivity) { if (activity is RoomDetailActivity) {
activity.intent?.extras?.getParcelable<TimelineArgs>(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let { activity.intent?.extras?.getParcelableCompat<TimelineArgs>(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let {
it.roomId != pr.roomId it.roomId != pr.roomId
} ?: true } ?: true
} else true } else true

View File

@@ -27,6 +27,7 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import javax.inject.Inject import javax.inject.Inject
@@ -49,7 +50,7 @@ class VerificationQRWaitingFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupRecyclerView() setupRecyclerView()
(arguments?.getParcelable(Mavericks.KEY_ARG) as? Args)?.let { (arguments?.getParcelableCompat<Args>(Mavericks.KEY_ARG))?.let {
controller.update(it) controller.update(it)
} }
} }

View File

@@ -48,6 +48,7 @@ import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.ActivityHomeBinding import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
@@ -86,6 +87,7 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.usercode.UserCodeActivity import im.vector.app.features.usercode.UserCodeActivity
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -142,6 +144,7 @@ class HomeActivity :
@Inject lateinit var fcmHelper: FcmHelper @Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var nightlyProxy: NightlyProxy @Inject lateinit var nightlyProxy: NightlyProxy
@Inject lateinit var disclaimerDialog: DisclaimerDialog @Inject lateinit var disclaimerDialog: DisclaimerDialog
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager
private var isNewAppLayoutEnabled: Boolean = false // delete once old app layout is removed private var isNewAppLayoutEnabled: Boolean = false // delete once old app layout is removed
@@ -171,6 +174,10 @@ class HomeActivity :
} }
} }
private val postPermissionLauncher = registerForPermissionsResult { _, _ ->
// Nothing to do with the result.
}
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() { private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
if (f is MatrixToBottomSheet) { if (f is MatrixToBottomSheet) {
@@ -215,6 +222,7 @@ class HomeActivity :
) )
} }
} }
sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java] sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java]
roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java] roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java]
views.drawerLayout.addDrawerListener(drawerListener) views.drawerLayout.addDrawerListener(drawerListener)
@@ -245,7 +253,7 @@ class HomeActivity :
} }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
val args = intent.getParcelableExtra<HomeActivityArgs>(Mavericks.KEY_ARG) val args = intent.getParcelableExtraCompat<HomeActivityArgs>(Mavericks.KEY_ARG)
if (args?.clearNotification == true) { if (args?.clearNotification == true) {
notificationDrawerManager.clearAllEvents() notificationDrawerManager.clearAllEvents()
@@ -272,6 +280,7 @@ class HomeActivity :
} }
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn() HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn()
HomeActivityViewEvents.ShowNotificationDialog -> handleShowNotificationDialog()
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes() HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration() HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession) is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
@@ -287,6 +296,10 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
} }
private fun handleShowNotificationDialog() {
notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher)
}
private fun handleShowReleaseNotes() { private fun handleShowReleaseNotes() {
startActivity(Intent(this, ReleaseNotesActivity::class.java)) startActivity(Intent(this, ReleaseNotesActivity::class.java))
} }
@@ -327,7 +340,7 @@ class HomeActivity :
private fun migrateThreadsIfNeeded(checkSession: Boolean) { private fun migrateThreadsIfNeeded(checkSession: Boolean) {
if (checkSession) { if (checkSession) {
// We should check session to ensure we will only clear cache if needed // We should check session to ensure we will only clear cache if needed
val args = intent.getParcelableExtra<HomeActivityArgs>(Mavericks.KEY_ARG) val args = intent.getParcelableExtraCompat<HomeActivityArgs>(Mavericks.KEY_ARG)
if (args?.hasExistingSession == true) { if (args?.hasExistingSession == true) {
// existingSession --> Will be true only if we came from an existing active session // existingSession --> Will be true only if we came from an existing active session
Timber.i("----> Migrating threads from an existing session..") Timber.i("----> Migrating threads from an existing session..")
@@ -406,6 +419,14 @@ class HomeActivity :
} }
private fun renderState(state: HomeActivityViewState) { private fun renderState(state: HomeActivityViewState) {
lifecycleScope.launch {
if (state.areNotificationsSilenced) {
unifiedPushHelper.unregister(pushersManager)
} else {
unifiedPushHelper.register(this@HomeActivity)
}
}
when (val status = state.syncRequestState) { when (val status = state.syncRequestState) {
is SyncRequestState.InitialSyncProgressing -> { is SyncRequestState.InitialSyncProgressing -> {
val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep) val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep)
@@ -538,7 +559,7 @@ class HomeActivity :
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
val parcelableExtra = intent?.getParcelableExtra<HomeActivityArgs>(Mavericks.KEY_ARG) val parcelableExtra = intent?.getParcelableExtraCompat<HomeActivityArgs>(Mavericks.KEY_ARG)
if (parcelableExtra?.clearNotification == true) { if (parcelableExtra?.clearNotification == true) {
notificationDrawerManager.clearAllEvents() notificationDrawerManager.clearAllEvents()
} }
@@ -667,7 +688,10 @@ class HomeActivity :
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) { if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
views.drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
} else { } else {
validateBackPressed { super.onBackPressed() } validateBackPressed {
@Suppress("DEPRECATION")
super.onBackPressed()
}
} }
} }

View File

@@ -31,6 +31,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
object PromptToEnableSessionPush : HomeActivityViewEvents object PromptToEnableSessionPush : HomeActivityViewEvents
object ShowAnalyticsOptIn : HomeActivityViewEvents object ShowAnalyticsOptIn : HomeActivityViewEvents
object ShowNotificationDialog : HomeActivityViewEvents
object ShowReleaseNotes : HomeActivityViewEvents object ShowReleaseNotes : HomeActivityViewEvents
object NotifyUserForThreadsMigration : HomeActivityViewEvents object NotifyUserForThreadsMigration : HomeActivityViewEvents
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents

View File

@@ -16,6 +16,7 @@
package im.vector.app.features.home package im.vector.app.features.home
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
@@ -41,14 +42,17 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired
import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.raw.wellknown.withElementWellKnown
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.UserPasswordAuth
@@ -57,9 +61,11 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.pushrules.RuleIds
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
@@ -96,7 +102,7 @@ class HomeActivityViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<HomeActivityViewModel, HomeActivityViewState> by hiltMavericksViewModelFactory() { companion object : MavericksViewModelFactory<HomeActivityViewModel, HomeActivityViewState> by hiltMavericksViewModelFactory() {
override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? { override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
val activity: HomeActivity = viewModelContext.activity() val activity: HomeActivity = viewModelContext.activity()
val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG) val args: HomeActivityArgs? = activity.intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
return args?.let { HomeActivityViewState(authenticationDescription = it.authenticationDescription) } return args?.let { HomeActivityViewState(authenticationDescription = it.authenticationDescription) }
?: super.initialState(viewModelContext) ?: super.initialState(viewModelContext)
} }
@@ -115,6 +121,7 @@ class HomeActivityViewModel @AssistedInject constructor(
observeCrossSigningReset() observeCrossSigningReset()
observeAnalytics() observeAnalytics()
observeReleaseNotes() observeReleaseNotes()
observeLocalNotificationsSilenced()
initThreadsMigration() initThreadsMigration()
} }
@@ -136,12 +143,26 @@ class HomeActivityViewModel @AssistedInject constructor(
} }
} }
fun observeLocalNotificationsSilenced() {
val currentSession = activeSessionHolder.getActiveSession()
val deviceId = currentSession.cryptoService().getMyDevice().deviceId
viewModelScope.launch {
currentSession.accountDataService()
.getLiveUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
.asFlow()
.map { it.getOrNull()?.content?.toModel<LocalNotificationSettingsContent>()?.isSilenced ?: false }
.onEach { setState { copy(areNotificationsSilenced = it) } }
}
}
private fun observeAnalytics() { private fun observeAnalytics() {
if (analyticsConfig.isEnabled) { if (analyticsConfig.isEnabled) {
analyticsStore.didAskUserConsentFlow analyticsStore.didAskUserConsentFlow
.onEach { didAskUser -> .onEach { didAskUser ->
if (!didAskUser) { if (!didAskUser) {
_viewEvents.post(HomeActivityViewEvents.ShowAnalyticsOptIn) _viewEvents.post(HomeActivityViewEvents.ShowAnalyticsOptIn)
} else {
_viewEvents.post(HomeActivityViewEvents.ShowNotificationDialog)
} }
} }
.launchIn(viewModelScope) .launchIn(viewModelScope)
@@ -161,6 +182,8 @@ class HomeActivityViewModel @AssistedInject constructor(
// do nothing // do nothing
} }
} }
} else {
_viewEvents.post(HomeActivityViewEvents.ShowNotificationDialog)
} }
} }

View File

@@ -22,5 +22,6 @@ import org.matrix.android.sdk.api.session.sync.SyncRequestState
data class HomeActivityViewState( data class HomeActivityViewState(
val syncRequestState: SyncRequestState = SyncRequestState.Idle, val syncRequestState: SyncRequestState = SyncRequestState.Idle,
val authenticationDescription: AuthenticationDescription? = null val authenticationDescription: AuthenticationDescription? = null,
val areNotificationsSilenced: Boolean = false,
) : MavericksState ) : MavericksState

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2022 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.features.home
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import im.vector.app.R
import im.vector.app.core.utils.checkPermissions
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import javax.inject.Inject
class NotificationPermissionManager @Inject constructor(
private val sdkIntProvider: BuildVersionSdkIntProvider,
private val vectorPreferences: VectorPreferences,
) {
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
fun isPermissionGranted(activity: Activity): Boolean {
return if (sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
ContextCompat.checkSelfPermission(
activity,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
} else {
// No notification permission management before API 33.
true
}
}
fun eventuallyRequestPermission(
activity: Activity,
requestPermissionLauncher: ActivityResultLauncher<Array<String>>,
showRationale: Boolean = true,
ignorePreference: Boolean = false,
) {
if (!sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) return
if (!vectorPreferences.areNotificationEnabledForDevice() && !ignorePreference) return
checkPermissions(
listOf(Manifest.permission.POST_NOTIFICATIONS),
activity,
activityResultLauncher = requestPermissionLauncher,
if (showRationale) R.string.permissions_rationale_msg_notification else 0
)
}
@RequiresApi(33)
fun askPermission(requestPermissionLauncher: ActivityResultLauncher<Array<String>>) {
requestPermissionLauncher.launch(
arrayOf(Manifest.permission.POST_NOTIFICATIONS)
)
}
fun eventuallyRevokePermission(
activity: Activity,
) {
if (!sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) return
activity.revokeSelfPermissionOnKill(Manifest.permission.POST_NOTIFICATIONS)
}
}

View File

@@ -46,6 +46,7 @@ import im.vector.app.features.navigation.Navigator
import im.vector.app.features.room.RequireActiveMembershipAction import im.vector.app.features.room.RequireActiveMembershipAction
import im.vector.app.features.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewEvents
import im.vector.app.features.room.RequireActiveMembershipViewModel import im.vector.app.features.room.RequireActiveMembershipViewModel
import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import javax.inject.Inject import javax.inject.Inject
@@ -99,7 +100,7 @@ class RoomDetailActivity :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
waitingView = views.waitingView.waitingView waitingView = views.waitingView.waitingView
val timelineArgs: TimelineArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) ?: return val timelineArgs: TimelineArgs = intent?.extras?.getParcelableCompat(EXTRA_ROOM_DETAIL_ARGS) ?: return
intent.putExtra(Mavericks.KEY_ARG, timelineArgs) intent.putExtra(Mavericks.KEY_ARG, timelineArgs)
currentRoomId = timelineArgs.roomId currentRoomId = timelineArgs.roomId
@@ -177,6 +178,7 @@ class RoomDetailActivity :
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) { if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
views.drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
} else { } else {
@Suppress("DEPRECATION")
super.onBackPressed() super.onBackPressed()
} }
} }

View File

@@ -772,7 +772,7 @@ class TimelineFragment :
} }
// We use a custom layout for this menu item, so we need to set a ClickListener // We use a custom layout for this menu item, so we need to set a ClickListener
menu.findItem(R.id.open_matrix_apps)?.let { menuItem -> menu.findItem(R.id.open_matrix_apps)?.let { menuItem ->
menuItem.actionView.debouncedClicks { menuItem.actionView?.debouncedClicks {
handleMenuItemSelected(menuItem) handleMenuItemSelected(menuItem)
} }
} }
@@ -783,7 +783,7 @@ class TimelineFragment :
// Custom thread notification menu item // Custom thread notification menu item
menu.findItem(R.id.menu_timeline_thread_list)?.let { menuItem -> menu.findItem(R.id.menu_timeline_thread_list)?.let { menuItem ->
menuItem.actionView.debouncedClicks { menuItem.actionView?.debouncedClicks {
handleMenuItemSelected(menuItem) handleMenuItemSelected(menuItem)
} }
} }
@@ -812,16 +812,16 @@ class TimelineFragment :
// icon should be default color no badge // icon should be default color no badge
val actionView = matrixAppsMenuItem.actionView val actionView = matrixAppsMenuItem.actionView
actionView actionView
.findViewById<ImageView>(R.id.action_view_icon_image) ?.findViewById<ImageView>(R.id.action_view_icon_image)
.setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary)) ?.setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary))
actionView.findViewById<TextView>(R.id.cart_badge).isVisible = false actionView?.findViewById<TextView>(R.id.cart_badge)?.isVisible = false
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
} else { } else {
val actionView = matrixAppsMenuItem.actionView val actionView = matrixAppsMenuItem.actionView
actionView actionView
.findViewById<ImageView>(R.id.action_view_icon_image) ?.findViewById<ImageView>(R.id.action_view_icon_image)
.setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary)) ?.setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary))
actionView.findViewById<TextView>(R.id.cart_badge).setTextOrHide("$widgetsCount") actionView?.findViewById<TextView>(R.id.cart_badge)?.setTextOrHide("$widgetsCount")
@Suppress("AlwaysShowAction") @Suppress("AlwaysShowAction")
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
} }
@@ -893,7 +893,7 @@ class TimelineFragment :
*/ */
private fun updateMenuThreadNotificationBadge(menu: Menu, state: RoomDetailViewState) { private fun updateMenuThreadNotificationBadge(menu: Menu, state: RoomDetailViewState) {
val menuThreadList = menu.findItem(R.id.menu_timeline_thread_list).actionView val menuThreadList = menu.findItem(R.id.menu_timeline_thread_list).actionView
val badgeFrameLayout = menuThreadList.findViewById<FrameLayout>(R.id.threadNotificationBadgeFrameLayout) val badgeFrameLayout = menuThreadList?.findViewById<FrameLayout>(R.id.threadNotificationBadgeFrameLayout) ?: return
val badgeTextView = menuThreadList.findViewById<TextView>(R.id.threadNotificationBadgeTextView) val badgeTextView = menuThreadList.findViewById<TextView>(R.id.threadNotificationBadgeTextView)
val unreadThreadMessages = state.threadNotificationBadgeState.numberOfLocalUnreadThreads val unreadThreadMessages = state.threadNotificationBadgeState.numberOfLocalUnreadThreads

Some files were not shown because too many files have changed in this diff Show More