mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 18:36:21 +03:00
Merge branch 'develop' into feature/bca/rust_flavor
This commit is contained in:
commit
76fa1bfee5
78 changed files with 1216 additions and 156 deletions
16
CHANGES.md
16
CHANGES.md
|
@ -1,3 +1,19 @@
|
|||
Changes in Element v1.5.14 (2022-12-20)
|
||||
=======================================
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- ActiveSessionHolder is not supposed to start syncing. Instead, the MainActivity does it, if necessary. Fixes a race condition when clearing cache.
|
||||
|
||||
|
||||
Changes in Element v1.5.13 (2022-12-19)
|
||||
=======================================
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Add `largeHeap=true` in the manifest since we are seeing more crashes (OOM) when handling sync response.
|
||||
|
||||
|
||||
Changes in Element v1.5.12 (2022-12-15)
|
||||
=======================================
|
||||
|
||||
|
|
1
changelog.d/2965.bugfix
Normal file
1
changelog.d/2965.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Do not show typing notification of ignored users.
|
1
changelog.d/7475.bugfix
Normal file
1
changelog.d/7475.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Push Notifications, Threads] - quick reply to threaded notification now sent to thread except main timeline
|
1
changelog.d/7746.feature
Normal file
1
changelog.d/7746.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Rich text editor] Add support for links
|
1
changelog.d/7784.bugfix
Normal file
1
changelog.d/7784.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Session manager] Other sessions list: filter option is displayed when selection mode is enabled
|
1
changelog.d/7786.bugfix
Normal file
1
changelog.d/7786.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Session manager] Other sessions: Filter bottom sheet cut in landscape mode
|
1
changelog.d/7790.bugfix
Normal file
1
changelog.d/7790.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Automatically show keyboard after learn more bottom sheet is dismissed
|
1
changelog.d/7792.bugfix
Normal file
1
changelog.d/7792.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Session Manager] Other sessions list: cannot select/deselect session by a long press when in select mode
|
1
changelog.d/7794.bugfix
Normal file
1
changelog.d/7794.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix current session ip address visibility
|
1
changelog.d/7795.feature
Normal file
1
changelog.d/7795.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Session manager] Security recommendations cards: whole view should be tappable
|
1
changelog.d/7798.bugfix
Normal file
1
changelog.d/7798.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Device Manager UI review fixes
|
1
changelog.d/7821.misc
Normal file
1
changelog.d/7821.misc
Normal file
|
@ -0,0 +1 @@
|
|||
[Voice Broadcast] Replace the player timeline
|
1
changelog.d/7836.misc
Normal file
1
changelog.d/7836.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Increase session manager test coverage
|
|
@ -83,7 +83,7 @@ ext.libs = [
|
|||
'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
|
||||
'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.1"
|
||||
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.3"
|
||||
],
|
||||
dagger : [
|
||||
'dagger' : "com.google.dagger:dagger:$dagger",
|
||||
|
@ -98,7 +98,7 @@ ext.libs = [
|
|||
],
|
||||
element : [
|
||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||
'wysiwyg' : "io.element.android:wysiwyg:0.9.0"
|
||||
'wysiwyg' : "io.element.android:wysiwyg:0.10.0"
|
||||
],
|
||||
squareup : [
|
||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||
|
@ -129,7 +129,7 @@ ext.libs = [
|
|||
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
|
||||
],
|
||||
maplibre : [
|
||||
'androidSdk' : "org.maplibre.gl:android-sdk:9.5.2",
|
||||
'androidSdk' : "org.maplibre.gl:android-sdk:9.6.0",
|
||||
'pluginAnnotation' : "org.maplibre.gl:android-plugin-annotation-v9:1.0.0"
|
||||
],
|
||||
mockk : [
|
||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40105130.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40105130.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Thread are now enabled by default.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/en-US/changelogs/40105140.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40105140.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Thread are now enabled by default.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
|
@ -420,6 +420,7 @@
|
|||
<string name="action_got_it">Got it</string>
|
||||
<string name="action_select_all">Select all</string>
|
||||
<string name="action_deselect_all">Deselect all</string>
|
||||
<string name="action_stop">Yes, Stop</string>
|
||||
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
|
||||
|
@ -3128,6 +3129,8 @@
|
|||
<string name="error_voice_broadcast_already_in_progress_message">You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.</string>
|
||||
<!-- Examples of usage: 6h 15min 30sec left / 15min 30sec left / 30sec left -->
|
||||
<string name="voice_broadcast_recording_time_left">%1$s left</string>
|
||||
<string name="stop_voice_broadcast_dialog_title">Stop live broadcasting?</string>
|
||||
<string name="stop_voice_broadcast_content">Are you sure you want to stop your live broadcast? This will end the broadcast and the full recording will be available in the room.</string>
|
||||
|
||||
<string name="upgrade_room_for_restricted">Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
||||
<string name="upgrade_room_for_restricted_no_param">Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
||||
|
@ -3343,7 +3346,7 @@
|
|||
<item quantity="one">Consider signing out from old sessions (%1$d day or more) that you don’t use anymore.</item>
|
||||
<item quantity="other">Consider signing out from old sessions (%1$d days or more) that you don’t use anymore.</item>
|
||||
</plurals>
|
||||
<string name="device_manager_current_session_title">Current Session</string>
|
||||
<string name="device_manager_current_session_title">Current session</string>
|
||||
<string name="device_manager_session_title">Session</string>
|
||||
<string name="device_manager_device_title">Device</string>
|
||||
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
|
||||
|
@ -3484,13 +3487,19 @@
|
|||
<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>
|
||||
|
||||
<!-- WYSIWYG Composer -->
|
||||
<!-- Rich text editor -->
|
||||
<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>
|
||||
<string name="rich_text_editor_link">Set link</string>
|
||||
<string name="rich_text_editor_full_screen_toggle">Toggle full screen mode</string>
|
||||
|
||||
<string name="set_link_text">Text</string>
|
||||
<string name="set_link_link">Link</string>
|
||||
<string name="set_link_create">Create a link</string>
|
||||
<string name="set_link_edit">Edit link</string>
|
||||
|
||||
<!-- ReplyTo events -->
|
||||
<string name="message_reply_to_prefix">In reply to</string>
|
||||
<string name="message_reply_to_sender_sent_file">sent a file.</string>
|
||||
|
|
|
@ -63,7 +63,7 @@ android {
|
|||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.5.14\""
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.5.16\""
|
||||
|
||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
|
|||
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
|
||||
|
@ -30,8 +31,15 @@ internal class RoomTypingUsersHandler @Inject constructor(
|
|||
|
||||
// TODO This could be handled outside of the Realm transaction. Use the new aggregator?
|
||||
fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) {
|
||||
val typingUserIds = ephemeralResult?.typingUserIds
|
||||
if (typingUserIds.isNullOrEmpty()) {
|
||||
typingUsersTracker.setTypingUsersFromRoom(roomId, emptyList())
|
||||
return
|
||||
}
|
||||
// Filter ignored users and current user
|
||||
val filteredUserIds = realm.where(IgnoredUserEntity::class.java).findAll().map { it.userId } + userId
|
||||
val roomMemberHelper = RoomMemberHelper(realm, roomId)
|
||||
val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId }.orEmpty()
|
||||
val typingIds = typingUserIds.filter { it !in filteredUserIds }
|
||||
val senderInfo = typingIds.map { userId ->
|
||||
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(userId)
|
||||
SenderInfo(
|
||||
|
|
|
@ -3013,7 +3013,11 @@
|
|||
"begging",
|
||||
"mercy",
|
||||
"puppy eyes",
|
||||
"face"
|
||||
"face",
|
||||
"cry",
|
||||
"tears",
|
||||
"sad",
|
||||
"grievance"
|
||||
]
|
||||
},
|
||||
"face-holding-back-tears": {
|
||||
|
@ -3060,9 +3064,7 @@
|
|||
"fearful",
|
||||
"scared",
|
||||
"terrified",
|
||||
"nervous",
|
||||
"oops",
|
||||
"huh"
|
||||
"nervous"
|
||||
]
|
||||
},
|
||||
"anxious-face-with-sweat": {
|
||||
|
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 5
|
|||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
||||
// When creating a hotfix, you should decrease the value, since the current value
|
||||
// is the value for the next regular release.
|
||||
ext.versionPatch = 14
|
||||
ext.versionPatch = 16
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:resizeableActivity="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.app.core.di
|
|||
|
||||
import android.content.Context
|
||||
import im.vector.app.ActiveSessionDataSource
|
||||
import im.vector.app.core.extensions.startSyncing
|
||||
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
|
||||
import im.vector.app.core.services.GuardServiceStarter
|
||||
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
|
||||
|
@ -72,7 +71,7 @@ class ActiveSessionHolder @Inject constructor(
|
|||
|
||||
suspend fun clearActiveSession() {
|
||||
// Do some cleanup first
|
||||
getSafeActiveSession(startSync = false)?.let {
|
||||
getSafeActiveSession()?.let {
|
||||
Timber.w("clearActiveSession of ${it.myUserId}")
|
||||
it.callSignalingService().removeCallListener(callManager)
|
||||
it.removeListener(sessionListener)
|
||||
|
@ -93,8 +92,8 @@ class ActiveSessionHolder @Inject constructor(
|
|||
return activeSessionReference.get() != null || authenticationService.hasAuthenticatedSessions()
|
||||
}
|
||||
|
||||
fun getSafeActiveSession(startSync: Boolean = true): Session? {
|
||||
return runBlocking { getOrInitializeSession(startSync = startSync) }
|
||||
fun getSafeActiveSession(): Session? {
|
||||
return runBlocking { getOrInitializeSession() }
|
||||
}
|
||||
|
||||
fun getActiveSession(): Session {
|
||||
|
@ -102,16 +101,11 @@ class ActiveSessionHolder @Inject constructor(
|
|||
?: throw IllegalStateException("You should authenticate before using this")
|
||||
}
|
||||
|
||||
suspend fun getOrInitializeSession(startSync: Boolean): Session? {
|
||||
suspend fun getOrInitializeSession(): Session? {
|
||||
return activeSessionReference.get()
|
||||
?.also {
|
||||
if (startSync && !it.syncService().isSyncThreadAlive()) {
|
||||
it.startSyncing(applicationContext)
|
||||
}
|
||||
}
|
||||
?: sessionInitializer.tryInitialize(readCurrentSession = { activeSessionReference.get() }) { session ->
|
||||
setActiveSession(session)
|
||||
configureAndStartSessionUseCase.execute(session, startSyncing = startSync)
|
||||
configureAndStartSessionUseCase.execute(session, startSyncing = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import im.vector.app.features.home.UserColorAccountDataViewModel
|
|||
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel
|
||||
import im.vector.app.features.home.room.detail.TimelineViewModel
|
||||
import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel
|
||||
import im.vector.app.features.home.room.detail.composer.link.SetLinkViewModel
|
||||
import im.vector.app.features.home.room.detail.search.SearchViewModel
|
||||
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsViewModel
|
||||
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryViewModel
|
||||
|
@ -695,4 +696,9 @@ interface MavericksViewModelModule {
|
|||
fun vectorSettingsNotificationPreferenceViewModelFactory(
|
||||
factory: VectorSettingsNotificationPreferenceViewModel.Factory
|
||||
): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(SetLinkViewModel::class)
|
||||
fun setLinkViewModelFactory(factory: SetLinkViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.app.core.platform
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActivityEntryPoint
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Add Mavericks capabilities, handle DI and bindings.
|
||||
*/
|
||||
abstract class VectorBaseDialogFragment<VB : ViewBinding> : DialogFragment(), MavericksView {
|
||||
/* ==========================================================================================
|
||||
* Analytics
|
||||
* ========================================================================================== */
|
||||
|
||||
protected var analyticsScreenName: MobileScreen.ScreenName? = null
|
||||
|
||||
protected lateinit var analyticsTracker: AnalyticsTracker
|
||||
|
||||
/* ==========================================================================================
|
||||
* View
|
||||
* ========================================================================================== */
|
||||
|
||||
private var _binding: VB? = null
|
||||
|
||||
// This property is only valid between onCreateView and onDestroyView.
|
||||
protected val views: VB
|
||||
get() = _binding!!
|
||||
|
||||
abstract fun getBinding(inflater: LayoutInflater, container: ViewGroup?): VB
|
||||
|
||||
/* ==========================================================================================
|
||||
* View model
|
||||
* ========================================================================================== */
|
||||
|
||||
private lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
|
||||
protected val activityViewModelProvider
|
||||
get() = ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
|
||||
protected val fragmentViewModelProvider
|
||||
get() = ViewModelProvider(this, viewModelFactory)
|
||||
|
||||
val vectorBaseActivity: VectorBaseActivity<*> by lazy {
|
||||
activity as VectorBaseActivity<*>
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setStyle(STYLE_NORMAL, ThemeUtils.getApplicationThemeRes(requireContext()))
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
_binding = getBinding(inflater, container)
|
||||
return views.root
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDestroyView() {
|
||||
_binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
|
||||
viewModelFactory = activityEntryPoint.viewModelFactory()
|
||||
val singletonEntryPoint = context.singletonEntryPoint()
|
||||
analyticsTracker = singletonEntryPoint.analyticsTracker()
|
||||
super.onAttach(context)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.i("onResume BottomSheet ${javaClass.simpleName}")
|
||||
analyticsScreenName?.let {
|
||||
analyticsTracker.screen(MobileScreen(screenName = it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// This ensures that invalidate() is called for static screens that don't
|
||||
// subscribe to a ViewModel.
|
||||
postInvalidate()
|
||||
requireDialog().window?.setWindowAnimations(R.style.Animation_AppCompat_Dialog)
|
||||
}
|
||||
|
||||
protected fun setArguments(args: Parcelable? = null) {
|
||||
arguments = args.toMvRxBundle()
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Views
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun View.debouncedClicks(onClicked: () -> Unit) {
|
||||
clicks()
|
||||
.onEach { onClicked() }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* ViewEvents
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.stream()
|
||||
.onEach {
|
||||
observer(it)
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
}
|
|
@ -118,7 +118,7 @@ class VectorPushHandler @Inject constructor(
|
|||
Timber.tag(loggerTag.value).d("## handleInternal()")
|
||||
}
|
||||
|
||||
val session = activeSessionHolder.getOrInitializeSession(startSync = false)
|
||||
val session = activeSessionHolder.getOrInitializeSession()
|
||||
|
||||
if (session == null) {
|
||||
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
|
||||
|
|
|
@ -174,12 +174,15 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
|
|||
private fun handleAppStarted() {
|
||||
if (intent.hasExtra(EXTRA_NEXT_INTENT)) {
|
||||
// Start the next Activity
|
||||
startSyncing()
|
||||
val nextIntent = intent.getParcelableExtraCompat<Intent>(EXTRA_NEXT_INTENT)
|
||||
startIntentAndFinish(nextIntent)
|
||||
} else if (intent.hasExtra(EXTRA_INIT_SESSION)) {
|
||||
startSyncing()
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
} else if (intent.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) {
|
||||
startSyncing()
|
||||
val roomId = intent.getStringExtra(EXTRA_ROOM_ID)
|
||||
if (roomId?.isNotEmpty() == true) {
|
||||
navigator.openRoom(this, roomId, trigger = ViewRoom.Trigger.Shortcut)
|
||||
|
@ -194,11 +197,16 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
|
|||
if (args.clearCache || args.clearCredentials) {
|
||||
doCleanUp()
|
||||
} else {
|
||||
startSyncing()
|
||||
startNextActivityAndFinish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startSyncing() {
|
||||
activeSessionHolder.getSafeActiveSession()?.startSyncing(this)
|
||||
}
|
||||
|
||||
private fun clearNotifications() {
|
||||
// Dismiss all notifications
|
||||
notificationDrawerManager.clearAllEvents()
|
||||
|
|
|
@ -127,6 +127,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
object Pause : Recording()
|
||||
object Resume : Recording()
|
||||
object Stop : Recording()
|
||||
object StopConfirmed : Recording()
|
||||
}
|
||||
|
||||
sealed class Listening : VoiceBroadcastAction() {
|
||||
|
|
|
@ -71,6 +71,8 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
|||
|
||||
object DisplayEnableIntegrationsWarning : RoomDetailViewEvents()
|
||||
|
||||
object DisplayPromptToStopVoiceBroadcast : RoomDetailViewEvents()
|
||||
|
||||
data class OpenStickerPicker(val widget: Widget) : RoomDetailViewEvents()
|
||||
|
||||
object OpenIntegrationManager : RoomDetailViewEvents()
|
||||
|
|
|
@ -413,6 +413,7 @@ class TimelineFragment :
|
|||
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
|
||||
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
|
||||
RoomDetailViewEvents.OpenElementCallWidget -> handleOpenElementCallWidget()
|
||||
RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast -> displayPromptToStopVoiceBroadcast()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2005,6 +2006,20 @@ class TimelineFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun displayPromptToStopVoiceBroadcast() {
|
||||
ConfirmationDialogBuilder
|
||||
.show(
|
||||
activity = requireActivity(),
|
||||
askForReason = false,
|
||||
confirmationRes = R.string.stop_voice_broadcast_content,
|
||||
positiveRes = R.string.action_stop,
|
||||
reasonHintRes = 0,
|
||||
titleRes = R.string.stop_voice_broadcast_dialog_title
|
||||
) {
|
||||
timelineViewModel.handle(RoomDetailAction.VoiceBroadcastAction.Recording.StopConfirmed)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTapToReturnToCall() {
|
||||
callManager.getCurrentCall()?.let { call ->
|
||||
VectorCallActivity.newIntent(
|
||||
|
|
|
@ -634,7 +634,8 @@ class TimelineViewModel @AssistedInject constructor(
|
|||
}
|
||||
VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
|
||||
VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
|
||||
VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
|
||||
VoiceBroadcastAction.Recording.Stop -> _viewEvents.post(RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast)
|
||||
VoiceBroadcastAction.Recording.StopConfirmed -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
|
||||
is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(action.voiceBroadcast)
|
||||
VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
|
||||
VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
|
||||
|
|
|
@ -80,6 +80,9 @@ import im.vector.app.features.home.room.detail.AutoCompleter
|
|||
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
|
||||
import im.vector.app.features.home.room.detail.TimelineViewModel
|
||||
import im.vector.app.features.home.room.detail.composer.link.SetLinkFragment
|
||||
import im.vector.app.features.home.room.detail.composer.link.SetLinkSharedAction
|
||||
import im.vector.app.features.home.room.detail.composer.link.SetLinkSharedActionViewModel
|
||||
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
|
||||
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
|
||||
|
@ -147,6 +150,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
|||
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||
private val attachmentViewModel: AttachmentTypeSelectorViewModel by fragmentViewModel()
|
||||
private val attachmentActionsViewModel: AttachmentTypeSelectorSharedActionViewModel by viewModels()
|
||||
private val setLinkActionsViewModel: SetLinkSharedActionViewModel by viewModels()
|
||||
|
||||
private val composer: MessageComposerView get() {
|
||||
return if (vectorPreferences.isRichTextEditorEnabled()) {
|
||||
|
@ -212,6 +216,14 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
|||
.onEach { onTypeSelected(it.attachmentType) }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
setLinkActionsViewModel.stream()
|
||||
.onEach { when (it) {
|
||||
is SetLinkSharedAction.Insert -> views.richTextComposerLayout.insertLink(it.link, it.text)
|
||||
is SetLinkSharedAction.Set -> views.richTextComposerLayout.setLink(it.link)
|
||||
SetLinkSharedAction.Remove -> views.richTextComposerLayout.removeLink()
|
||||
} }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
messageComposerViewModel.stateFlow.map { it.isFullScreen }
|
||||
.distinctUntilChanged()
|
||||
.onEach { isFullScreen ->
|
||||
|
@ -385,6 +397,10 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
|||
override fun onFullScreenModeChanged() = withState(messageComposerViewModel) { state ->
|
||||
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(!state.isFullScreen))
|
||||
}
|
||||
|
||||
override fun onSetLink(isTextSupported: Boolean, initialLink: String?) {
|
||||
SetLinkFragment.show(isTextSupported, initialLink, childFragmentManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,4 +45,5 @@ interface Callback : ComposerEditText.Callback {
|
|||
fun onAddAttachment()
|
||||
fun onExpandOrCompactChange()
|
||||
fun onFullScreenModeChanged()
|
||||
fun onSetLink(isTextSupported: Boolean, initialLink: String?)
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ import im.vector.app.databinding.ComposerRichTextLayoutBinding
|
|||
import im.vector.app.databinding.ViewRichTextMenuButtonBinding
|
||||
import io.element.android.wysiwyg.EditorEditText
|
||||
import io.element.android.wysiwyg.inputhandlers.models.InlineFormat
|
||||
import io.element.android.wysiwyg.inputhandlers.models.LinkAction
|
||||
import io.element.android.wysiwyg.utils.RustErrorCollector
|
||||
import uniffi.wysiwyg_composer.ActionState
|
||||
import uniffi.wysiwyg_composer.ComposerAction
|
||||
|
@ -231,7 +232,24 @@ internal class RichTextComposerLayout @JvmOverloads constructor(
|
|||
addRichTextMenuItem(R.drawable.ic_composer_strikethrough, R.string.rich_text_editor_format_strikethrough, ComposerAction.STRIKE_THROUGH) {
|
||||
views.richTextComposerEditText.toggleInlineFormat(InlineFormat.StrikeThrough)
|
||||
}
|
||||
addRichTextMenuItem(R.drawable.ic_composer_link, R.string.rich_text_editor_link, ComposerAction.LINK) {
|
||||
views.richTextComposerEditText.getLinkAction()?.let {
|
||||
when (it) {
|
||||
LinkAction.InsertLink -> callback?.onSetLink(isTextSupported = true, initialLink = null)
|
||||
is LinkAction.SetLink -> callback?.onSetLink(isTextSupported = false, initialLink = it.currentLink)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setLink(link: String?) =
|
||||
views.richTextComposerEditText.setLink(link)
|
||||
|
||||
fun insertLink(link: String, text: String) =
|
||||
views.richTextComposerEditText.insertLink(link, text)
|
||||
|
||||
fun removeLink() =
|
||||
views.richTextComposerEditText.removeLink()
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun disallowParentInterceptTouchEvent(view: View) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.room.detail.composer.link
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class SetLinkAction : VectorViewModelAction {
|
||||
data class LinkChanged(
|
||||
val newLink: String
|
||||
) : SetLinkAction()
|
||||
|
||||
data class Save(
|
||||
val link: String,
|
||||
val text: String,
|
||||
) : SetLinkAction()
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.composer.link
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isGone
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseDialogFragment
|
||||
import im.vector.app.databinding.FragmentSetLinkBinding
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SetLinkFragment :
|
||||
VectorBaseDialogFragment<FragmentSetLinkBinding>() {
|
||||
|
||||
@Parcelize
|
||||
data class Args(
|
||||
val isTextSupported: Boolean,
|
||||
val initialLink: String?,
|
||||
) : Parcelable
|
||||
|
||||
private val viewModel: SetLinkViewModel by fragmentViewModel()
|
||||
private val sharedActionViewModel: SetLinkSharedActionViewModel by viewModels(
|
||||
ownerProducer = { requireParentFragment() }
|
||||
)
|
||||
private val args: Args by args()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSetLinkBinding {
|
||||
return FragmentSetLinkBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun show(isTextSupported: Boolean, initialLink: String?, fragmentManager: FragmentManager) =
|
||||
SetLinkFragment().apply {
|
||||
setArguments(Args(isTextSupported, initialLink))
|
||||
}.show(fragmentManager, "SetLinkBottomSheet")
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
views.link.setText(args.initialLink)
|
||||
views.link.textChanges()
|
||||
.onEach {
|
||||
viewModel.handle(SetLinkAction.LinkChanged(it.toString()))
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.save.debouncedClicks {
|
||||
viewModel.handle(
|
||||
SetLinkAction.Save(
|
||||
link = views.link.text.toString(),
|
||||
text = views.text.text.toString(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
views.cancel.debouncedClicks(::onCancel)
|
||||
views.remove.debouncedClicks(::onRemove)
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SetLinkViewEvents.SavedLinkAndText -> handleInsert(link = it.link, text = it.text)
|
||||
is SetLinkViewEvents.SavedLink -> handleSet(link = it.link)
|
||||
}
|
||||
}
|
||||
|
||||
views.toolbar.setNavigationOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { viewState ->
|
||||
views.toolbar.title = getString(
|
||||
if (viewState.initialLink != null) {
|
||||
R.string.set_link_edit
|
||||
} else {
|
||||
R.string.set_link_create
|
||||
}
|
||||
)
|
||||
|
||||
views.remove.isGone = !viewState.removeVisible
|
||||
views.save.isEnabled = viewState.saveEnabled
|
||||
views.textLayout.isGone = !viewState.isTextSupported
|
||||
}
|
||||
|
||||
private fun handleInsert(link: String, text: String) {
|
||||
sharedActionViewModel.post(SetLinkSharedAction.Insert(text, link))
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun handleSet(link: String) {
|
||||
sharedActionViewModel.post(SetLinkSharedAction.Set(link))
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun onRemove() {
|
||||
sharedActionViewModel.post(SetLinkSharedAction.Remove)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun onCancel() = dismiss()
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.room.detail.composer.link
|
||||
|
||||
import im.vector.app.core.platform.VectorSharedAction
|
||||
import im.vector.app.core.platform.VectorSharedActionViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetLinkSharedActionViewModel @Inject constructor() :
|
||||
VectorSharedActionViewModel<SetLinkSharedAction>()
|
||||
|
||||
sealed interface SetLinkSharedAction : VectorSharedAction {
|
||||
data class Set(
|
||||
val link: String,
|
||||
) : SetLinkSharedAction
|
||||
|
||||
data class Insert(
|
||||
val text: String,
|
||||
val link: String,
|
||||
) : SetLinkSharedAction
|
||||
|
||||
object Remove : SetLinkSharedAction
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.composer.link
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class SetLinkViewEvents : VectorViewEvents {
|
||||
|
||||
data class SavedLink(
|
||||
val link: String,
|
||||
) : SetLinkViewEvents()
|
||||
|
||||
data class SavedLinkAndText(
|
||||
val link: String,
|
||||
val text: String,
|
||||
) : SetLinkViewEvents()
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.composer.link
|
||||
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
|
||||
class SetLinkViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: SetLinkViewState,
|
||||
) : VectorViewModel<SetLinkViewState, SetLinkAction, SetLinkViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<SetLinkViewModel, SetLinkViewState> {
|
||||
override fun create(initialState: SetLinkViewState): SetLinkViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<SetLinkViewModel, SetLinkViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
override fun handle(action: SetLinkAction) = when (action) {
|
||||
is SetLinkAction.LinkChanged -> handleLinkChanged(action.newLink)
|
||||
is SetLinkAction.Save -> handleSave(action.link, action.text)
|
||||
}
|
||||
|
||||
private fun handleLinkChanged(newLink: String) = setState {
|
||||
copy(saveEnabled = newLink != initialLink.orEmpty())
|
||||
}
|
||||
|
||||
private fun handleSave(
|
||||
link: String,
|
||||
text: String
|
||||
) = if (initialState.isTextSupported) {
|
||||
_viewEvents.post(SetLinkViewEvents.SavedLinkAndText(link, text))
|
||||
} else {
|
||||
_viewEvents.post(SetLinkViewEvents.SavedLink(link))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.composer.link
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
|
||||
data class SetLinkViewState(
|
||||
val isTextSupported: Boolean,
|
||||
val initialLink: String?,
|
||||
val saveEnabled: Boolean,
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: SetLinkFragment.Args) : this(
|
||||
isTextSupported = args.isTextSupported,
|
||||
initialLink = args.initialLink,
|
||||
saveEnabled = false,
|
||||
)
|
||||
|
||||
val removeVisible = initialLink != null
|
||||
}
|
|
@ -122,10 +122,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||
|
||||
private fun bindSeekBar(holder: Holder) {
|
||||
with(holder) {
|
||||
durationView.text = formatPlaybackTime(duration)
|
||||
remainingTimeView.text = formatRemainingTime(duration)
|
||||
elapsedTimeView.text = formatPlaybackTime(0)
|
||||
seekBar.max = duration
|
||||
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
remainingTimeView.text = formatRemainingTime(duration - progress)
|
||||
elapsedTimeView.text = formatPlaybackTime(progress)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
isUserSeeking = true
|
||||
|
@ -156,6 +160,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||
}
|
||||
|
||||
private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
|
||||
private fun formatRemainingTime(time: Int) = if (time < 1000) formatPlaybackTime(time) else String.format("-%s", formatPlaybackTime(time))
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
super.unbind(holder)
|
||||
|
@ -177,7 +182,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||
val fastBackwardButton by bind<ImageButton>(R.id.fastBackwardButton)
|
||||
val fastForwardButton by bind<ImageButton>(R.id.fastForwardButton)
|
||||
val seekBar by bind<SeekBar>(R.id.seekBar)
|
||||
val durationView by bind<TextView>(R.id.playbackDuration)
|
||||
val remainingTimeView by bind<TextView>(R.id.remainingTime)
|
||||
val elapsedTimeView by bind<TextView>(R.id.elapsedTime)
|
||||
val broadcasterNameMetadata by bind<VoiceBroadcastMetadataView>(R.id.broadcasterNameMetadata)
|
||||
val voiceBroadcastMetadata by bind<VoiceBroadcastMetadataView>(R.id.voiceBroadcastMetadata)
|
||||
val listenersCountMetadata by bind<VoiceBroadcastMetadataView>(R.id.listenersCountMetadata)
|
||||
|
|
|
@ -118,6 +118,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||
private fun handleSmartReply(intent: Intent, context: Context) {
|
||||
val message = getReplyMessage(intent)
|
||||
val roomId = intent.getStringExtra(KEY_ROOM_ID)
|
||||
val threadId = intent.getStringExtra(KEY_THREAD_ID)
|
||||
|
||||
if (message.isNullOrBlank() || roomId.isNullOrBlank()) {
|
||||
// ignore this event
|
||||
|
@ -126,13 +127,20 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||
}
|
||||
activeSessionHolder.getActiveSession().let { session ->
|
||||
session.getRoom(roomId)?.let { room ->
|
||||
sendMatrixEvent(message, session, room, context)
|
||||
sendMatrixEvent(message, threadId, session, room, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMatrixEvent(message: String, session: Session, room: Room, context: Context?) {
|
||||
private fun sendMatrixEvent(message: String, threadId: String?, session: Session, room: Room, context: Context?) {
|
||||
if (threadId != null) {
|
||||
room.relationService().replyInThread(
|
||||
rootThreadEventId = threadId,
|
||||
replyInThreadText = message,
|
||||
)
|
||||
} else {
|
||||
room.sendService().sendTextMessage(message)
|
||||
}
|
||||
|
||||
// Create a new event to be displayed in the notification drawer, right now
|
||||
|
||||
|
@ -148,7 +156,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||
body = message,
|
||||
imageUriString = null,
|
||||
roomId = room.roomId,
|
||||
threadId = null, // needs to be changed: https://github.com/vector-im/element-android/issues/7475
|
||||
threadId = threadId,
|
||||
roomName = room.roomSummary()?.displayName ?: room.roomId,
|
||||
roomIsDirect = room.roomSummary()?.isDirect == true,
|
||||
outGoingMessage = true,
|
||||
|
@ -223,6 +231,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||
|
||||
companion object {
|
||||
const val KEY_ROOM_ID = "roomID"
|
||||
const val KEY_THREAD_ID = "threadID"
|
||||
const val KEY_TEXT_REPLY = "key_text_reply"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -657,7 +657,7 @@ class NotificationUtils @Inject constructor(
|
|||
|
||||
// Quick reply
|
||||
if (!roomInfo.hasSmartReplyError) {
|
||||
buildQuickReplyIntent(roomInfo.roomId, senderDisplayNameForReplyCompat)?.let { replyPendingIntent ->
|
||||
buildQuickReplyIntent(roomInfo.roomId, threadId, senderDisplayNameForReplyCompat)?.let { replyPendingIntent ->
|
||||
val remoteInput = RemoteInput.Builder(NotificationBroadcastReceiver.KEY_TEXT_REPLY)
|
||||
.setLabel(stringProvider.getString(R.string.action_quick_reply))
|
||||
.build()
|
||||
|
@ -892,13 +892,17 @@ class NotificationUtils @Inject constructor(
|
|||
However, for Android devices running Marshmallow and below (API level 23 and below),
|
||||
it will be more appropriate to use an activity. Since you have to provide your own UI.
|
||||
*/
|
||||
private fun buildQuickReplyIntent(roomId: String, senderName: String?): PendingIntent? {
|
||||
private fun buildQuickReplyIntent(roomId: String, threadId: String?, senderName: String?): PendingIntent? {
|
||||
val intent: Intent
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
intent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||
intent.action = actionIds.smartReply
|
||||
intent.data = createIgnoredUri(roomId)
|
||||
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
||||
threadId?.let {
|
||||
intent.putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, it)
|
||||
}
|
||||
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
clock.epochMillis().toInt(),
|
||||
|
|
|
@ -223,7 +223,6 @@ class VectorSettingsDevicesFragment :
|
|||
override fun onViewAllClicked() {
|
||||
viewNavigator.navigateToOtherSessions(
|
||||
requireActivity(),
|
||||
R.string.device_manager_header_section_security_recommendations_title,
|
||||
DeviceManagerFilterType.UNVERIFIED,
|
||||
excludeCurrentDevice = true
|
||||
)
|
||||
|
@ -233,7 +232,6 @@ class VectorSettingsDevicesFragment :
|
|||
override fun onViewAllClicked() {
|
||||
viewNavigator.navigateToOtherSessions(
|
||||
requireActivity(),
|
||||
R.string.device_manager_header_section_security_recommendations_title,
|
||||
DeviceManagerFilterType.INACTIVE,
|
||||
excludeCurrentDevice = true
|
||||
)
|
||||
|
@ -447,7 +445,6 @@ class VectorSettingsDevicesFragment :
|
|||
override fun onViewAllOtherSessionsClicked() {
|
||||
viewNavigator.navigateToOtherSessions(
|
||||
context = requireActivity(),
|
||||
titleResourceId = R.string.device_manager_sessions_other_title,
|
||||
defaultFilter = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
excludeCurrentDevice = true
|
||||
)
|
||||
|
|
|
@ -31,12 +31,11 @@ class VectorSettingsDevicesViewNavigator @Inject constructor() {
|
|||
|
||||
fun navigateToOtherSessions(
|
||||
context: Context,
|
||||
titleResourceId: Int,
|
||||
defaultFilter: DeviceManagerFilterType,
|
||||
excludeCurrentDevice: Boolean,
|
||||
) {
|
||||
context.startActivity(
|
||||
OtherSessionsActivity.newIntent(context, titleResourceId, defaultFilter, excludeCurrentDevice)
|
||||
OtherSessionsActivity.newIntent(context, defaultFilter, excludeCurrentDevice)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
|
|||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
|
||||
private const val EXTRA_TOP_MARGIN_DP = 48
|
||||
private const val EXTRA_TOP_MARGIN_DP = 32
|
||||
|
||||
@EpoxyModelClass
|
||||
abstract class SessionDetailsHeaderItem : VectorEpoxyModel<SessionDetailsHeaderItem.Holder>(R.layout.item_session_details_header) {
|
||||
|
|
|
@ -53,6 +53,9 @@ class SecurityRecommendationView @JvmOverloads constructor(
|
|||
setImage(it)
|
||||
}
|
||||
|
||||
setOnClickListener {
|
||||
callback?.onViewAllClicked()
|
||||
}
|
||||
views.recommendationViewAllButton.setOnClickListener {
|
||||
callback?.onViewAllClicked()
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ class SessionInfoView @JvmOverloads constructor(
|
|||
renderDeviceLastSeenDetails(
|
||||
sessionInfoViewState.deviceFullInfo.isInactive,
|
||||
sessionInfoViewState.deviceFullInfo.deviceInfo,
|
||||
sessionInfoViewState.isLastSeenDetailsVisible,
|
||||
sessionInfoViewState.isLastActivityVisible,
|
||||
sessionInfoViewState.isShowingIpAddress,
|
||||
dateFormatter,
|
||||
drawableProvider,
|
||||
|
@ -197,7 +197,7 @@ class SessionInfoView @JvmOverloads constructor(
|
|||
} else {
|
||||
views.sessionInfoLastActivityTextView.isGone = true
|
||||
}
|
||||
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible && isShowingIpAddress })
|
||||
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isShowingIpAddress })
|
||||
}
|
||||
|
||||
private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {
|
||||
|
|
|
@ -24,6 +24,6 @@ data class SessionInfoViewState(
|
|||
val isVerifyButtonVisible: Boolean = true,
|
||||
val isDetailsButtonVisible: Boolean = true,
|
||||
val isLearnMoreLinkVisible: Boolean = false,
|
||||
val isLastSeenDetailsVisible: Boolean = false,
|
||||
val isLastActivityVisible: Boolean = false,
|
||||
val isShowingIpAddress: Boolean = false,
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.settings.devices.v2.more
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
|
@ -42,6 +43,8 @@ class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSh
|
|||
|
||||
override val showExpanded = true
|
||||
|
||||
var onDismiss: (() -> Unit)? = null
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSessionLearnMoreBinding {
|
||||
return BottomSheetSessionLearnMoreBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
@ -57,6 +60,11 @@ class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSh
|
|||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
onDismiss?.invoke()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { viewState ->
|
||||
super.invalidate()
|
||||
views.bottomSheetSessionLearnMoreTitle.text = viewState.title
|
||||
|
@ -65,11 +73,12 @@ class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSh
|
|||
|
||||
companion object {
|
||||
|
||||
fun show(fragmentManager: FragmentManager, args: Args) {
|
||||
fun show(fragmentManager: FragmentManager, args: Args): SessionLearnMoreBottomSheet {
|
||||
val bottomSheet = SessionLearnMoreBottomSheet()
|
||||
bottomSheet.isCancelable = true
|
||||
bottomSheet.setArguments(args)
|
||||
bottomSheet.show(fragmentManager, "SessionLearnMoreBottomSheet")
|
||||
return bottomSheet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
|
@ -48,13 +47,11 @@ class OtherSessionsActivity : SimpleFragmentActivity() {
|
|||
companion object {
|
||||
fun newIntent(
|
||||
context: Context,
|
||||
@StringRes
|
||||
titleResourceId: Int,
|
||||
defaultFilter: DeviceManagerFilterType,
|
||||
excludeCurrentDevice: Boolean,
|
||||
): Intent {
|
||||
return Intent(context, OtherSessionsActivity::class.java).apply {
|
||||
putExtra(Mavericks.KEY_ARG, OtherSessionsArgs(titleResourceId, defaultFilter, excludeCurrentDevice))
|
||||
putExtra(Mavericks.KEY_ARG, OtherSessionsArgs(defaultFilter, excludeCurrentDevice))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,14 +17,11 @@
|
|||
package im.vector.app.features.settings.devices.v2.othersessions
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class OtherSessionsArgs(
|
||||
@StringRes
|
||||
val titleResourceId: Int,
|
||||
val defaultFilter: DeviceManagerFilterType,
|
||||
val excludeCurrentDevice: Boolean,
|
||||
) : Parcelable
|
||||
|
|
|
@ -182,7 +182,9 @@ class OtherSessionsFragment :
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupToolbar(views.otherSessionsToolbar).setTitle(args.titleResourceId).allowBack()
|
||||
setupToolbar(views.otherSessionsToolbar)
|
||||
.setTitle(R.string.device_manager_sessions_other_title)
|
||||
.allowBack()
|
||||
observeViewEvents()
|
||||
initFilterView()
|
||||
}
|
||||
|
@ -225,6 +227,7 @@ class OtherSessionsFragment :
|
|||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
updateLoading(state.isLoading)
|
||||
updateFilterView(state.isSelectModeEnabled)
|
||||
if (state.devices is Success) {
|
||||
val devices = state.devices.invoke()
|
||||
renderDevices(devices, state.currentFilter, state.isShowingIpAddress)
|
||||
|
@ -240,13 +243,17 @@ class OtherSessionsFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateFilterView(isSelectModeEnabled: Boolean) {
|
||||
views.otherSessionsFilterFrameLayout.isVisible = isSelectModeEnabled.not()
|
||||
}
|
||||
|
||||
private fun updateToolbar(devices: List<DeviceFullInfo>, isSelectModeEnabled: Boolean) {
|
||||
invalidateOptionsMenu()
|
||||
val title = if (isSelectModeEnabled) {
|
||||
val selection = devices.count { it.isSelected }
|
||||
stringProvider.getQuantityString(R.plurals.x_selected, selection, selection)
|
||||
} else {
|
||||
getString(args.titleResourceId)
|
||||
getString(R.string.device_manager_sessions_other_title)
|
||||
}
|
||||
toolbar?.title = title
|
||||
}
|
||||
|
@ -341,6 +348,8 @@ class OtherSessionsFragment :
|
|||
override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state ->
|
||||
if (!state.isSelectModeEnabled) {
|
||||
enableSelectMode(true, deviceId)
|
||||
} else {
|
||||
viewModel.handle(OtherSessionsAction.ToggleSelectionForDevice(deviceId))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -224,7 +224,7 @@ class SessionOverviewFragment :
|
|||
isVerifyButtonVisible = isCurrentSession || viewState.isCurrentSessionTrusted,
|
||||
isDetailsButtonVisible = false,
|
||||
isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default,
|
||||
isLastSeenDetailsVisible = !isCurrentSession,
|
||||
isLastActivityVisible = !isCurrentSession,
|
||||
isShowingIpAddress = viewState.isShowingIpAddress,
|
||||
)
|
||||
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
|
@ -62,12 +63,24 @@ class RenameSessionFragment :
|
|||
}
|
||||
|
||||
private fun initEditText() {
|
||||
views.renameSessionEditText.showKeyboard(andRequestFocus = true)
|
||||
showKeyboard()
|
||||
views.renameSessionEditText.doOnTextChanged { text, _, _, _ ->
|
||||
viewModel.handle(RenameSessionAction.EditLocally(text.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showKeyboard() {
|
||||
val focusChangeListener = object : ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) {
|
||||
views.renameSessionEditText.showKeyboard(andRequestFocus = true)
|
||||
}
|
||||
views.renameSessionEditText.viewTreeObserver.removeOnWindowFocusChangeListener(this)
|
||||
}
|
||||
}
|
||||
views.renameSessionEditText.viewTreeObserver.addOnWindowFocusChangeListener(focusChangeListener)
|
||||
}
|
||||
|
||||
private fun initSaveButton() {
|
||||
views.renameSessionSave.debouncedClicks {
|
||||
viewModel.handle(RenameSessionAction.SaveModifications)
|
||||
|
@ -89,7 +102,9 @@ class RenameSessionFragment :
|
|||
title = getString(R.string.device_manager_learn_more_session_rename_title),
|
||||
description = getString(R.string.device_manager_learn_more_session_rename),
|
||||
)
|
||||
SessionLearnMoreBottomSheet.show(childFragmentManager, args)
|
||||
SessionLearnMoreBottomSheet
|
||||
.show(childFragmentManager, args)
|
||||
.onDismiss = { showKeyboard() }
|
||||
}
|
||||
|
||||
private fun observeViewEvents() {
|
||||
|
|
|
@ -63,7 +63,7 @@ class StartAppViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun eagerlyInitializeSession() {
|
||||
sessionHolder.getOrInitializeSession(startSync = true)
|
||||
sessionHolder.getOrInitializeSession()
|
||||
}
|
||||
|
||||
private fun handleLongProcessing() {
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable
|
|||
import android.util.TypedValue
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
|
@ -113,19 +114,16 @@ object ThemeUtils {
|
|||
*/
|
||||
fun setApplicationTheme(context: Context, aTheme: String) {
|
||||
currentTheme.set(aTheme)
|
||||
context.setTheme(
|
||||
when (aTheme) {
|
||||
SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.Theme_Vector_Dark else R.style.Theme_Vector_Light
|
||||
THEME_DARK_VALUE -> R.style.Theme_Vector_Dark
|
||||
THEME_BLACK_VALUE -> R.style.Theme_Vector_Black
|
||||
else -> R.style.Theme_Vector_Light
|
||||
}
|
||||
)
|
||||
context.setTheme(themeToRes(context, aTheme))
|
||||
|
||||
// Clear the cache
|
||||
mColorByAttr.clear()
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
fun getApplicationThemeRes(context: Context) =
|
||||
themeToRes(context, currentTheme.get())
|
||||
|
||||
/**
|
||||
* Set the activity theme according to the selected one. Default is Light, so if this is the current
|
||||
* theme, the theme is not changed.
|
||||
|
@ -200,4 +198,13 @@ object ThemeUtils {
|
|||
DrawableCompat.setTint(tinted, color)
|
||||
return tinted
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
private fun themeToRes(context: Context, theme: String): Int =
|
||||
when (theme) {
|
||||
SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.Theme_Vector_Dark else R.style.Theme_Vector_Light
|
||||
THEME_DARK_VALUE -> R.style.Theme_Vector_Dark
|
||||
THEME_BLACK_VALUE -> R.style.Theme_Vector_Black
|
||||
else -> R.style.Theme_Vector_Light
|
||||
}
|
||||
}
|
||||
|
|
12
vector/src/main/res/drawable/ic_composer_link.xml
Normal file
12
vector/src/main/res/drawable/ic_composer_link.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="44dp"
|
||||
android:height="44dp"
|
||||
android:viewportWidth="44"
|
||||
android:viewportHeight="44">
|
||||
<path
|
||||
android:pathData="M22.566,16.151L23.101,15.616C24.577,14.14 26.956,14.126 28.415,15.585C29.874,17.044 29.86,19.423 28.383,20.899L25.844,23.438C24.368,24.915 21.989,24.929 20.53,23.47M21.434,27.849L20.899,28.383C19.423,29.86 17.044,29.874 15.585,28.415C14.126,26.956 14.14,24.577 15.616,23.101L18.156,20.562C19.632,19.086 22.011,19.071 23.47,20.53"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#8D97A5"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -2,9 +2,7 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingBottom="32dp">
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:layout_width="36dp"
|
||||
|
@ -18,14 +16,22 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:text="@string/device_manager_filter_bottom_sheet_title" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingBottom="32dp"
|
||||
android:scrollbarStyle="outsideOverlay">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/filterOptionsRadioGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layoutDirection="rtl"
|
||||
android:paddingTop="24dp"
|
||||
android:showDividers="none">
|
||||
|
||||
<RadioButton
|
||||
|
@ -33,25 +39,34 @@
|
|||
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:button="@null"
|
||||
android:checked="true"
|
||||
android:drawableEnd="?android:attr/listChoiceIndicatorSingle"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/device_manager_filter_option_all_sessions" />
|
||||
android:text="@string/device_manager_filter_option_all_sessions"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/filterOptionVerifiedRadioButton"
|
||||
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:button="@null"
|
||||
android:drawableEnd="?android:attr/listChoiceIndicatorSingle"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/device_manager_filter_option_verified" />
|
||||
android:text="@string/device_manager_filter_option_verified"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filterOptionVerifiedTextView"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/device_manager_filter_option_verified_description" />
|
||||
|
||||
<RadioButton
|
||||
|
@ -59,16 +74,20 @@
|
|||
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:button="@null"
|
||||
android:drawableEnd="?android:attr/listChoiceIndicatorSingle"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/device_manager_filter_option_unverified" />
|
||||
android:text="@string/device_manager_filter_option_unverified"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filterOptionUnverifiedTextView"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/device_manager_filter_option_unverified_description" />
|
||||
|
||||
<RadioButton
|
||||
|
@ -76,17 +95,23 @@
|
|||
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:button="@null"
|
||||
android:drawableEnd="?android:attr/listChoiceIndicatorSingle"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/device_manager_filter_option_inactive" />
|
||||
android:text="@string/device_manager_filter_option_inactive"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filterOptionInactiveTextView"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end" />
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_collapseMode="parallax"
|
||||
app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
|
||||
app:sessionsListHeaderHasLearnMoreLink="false"
|
||||
|
@ -81,7 +81,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingTop="20dp"
|
||||
android:visibility="gone"
|
||||
app:layout_collapseMode="parallax"
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/device_manager_session_overview_signout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
|
|
117
vector/src/main/res/layout/fragment_set_link.xml
Normal file
117
vector/src/main/res/layout/fragment_set_link.xml
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_x_18dp"
|
||||
app:title="@string/set_link_create" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/save"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_min="100dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textLayout"
|
||||
style="@style/Widget.Vector.TextInputLayout.Form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="@string/set_link_text">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/linkLayout"
|
||||
style="@style/Widget.Vector.TextInputLayout.Form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="@string/set_link_link">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/link"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/save"
|
||||
style="@style/Widget.Vector.Button.CallToAction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:height="56dp"
|
||||
android:text="@string/action_save"
|
||||
android:textAllCaps="false"
|
||||
app:iconGravity="textStart"
|
||||
app:layout_constraintBottom_toTopOf="@id/remove"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/remove"
|
||||
style="@style/Widget.Vector.Button.Destructive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:height="56dp"
|
||||
android:text="@string/action_remove"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="gone"
|
||||
app:iconGravity="textStart"
|
||||
app:layout_constraintBottom_toTopOf="@id/cancel"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel"
|
||||
style="@style/Widget.Vector.Button.Outlined"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:height="56dp"
|
||||
android:text="@string/action_cancel"
|
||||
android:textAllCaps="false"
|
||||
app:iconGravity="textStart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -75,7 +75,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="16dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderCurrentSession" />
|
||||
|
|
|
@ -5,9 +5,15 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/startGuideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="8dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/otherSessionItemBackground"
|
||||
android:layout_width="0dp"
|
||||
|
@ -29,7 +35,7 @@
|
|||
android:contentDescription="@string/a11y_device_manager_device_type_mobile"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/otherSessionItemBackground"
|
||||
app:layout_constraintStart_toStartOf="@id/otherSessionItemBackground"
|
||||
app:layout_constraintStart_toStartOf="@id/startGuideline"
|
||||
app:layout_constraintTop_toTopOf="@id/otherSessionItemBackground"
|
||||
tools:src="@drawable/ic_device_type_mobile" />
|
||||
|
||||
|
@ -52,8 +58,8 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -89,7 +95,7 @@
|
|||
android:id="@+id/otherSessionSeparator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?vctr_content_quinary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
android:id="@+id/sessionDetailsContentTitle"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
app:layout_constraintBottom_toTopOf="@id/sessionDetailsContentDivider"
|
||||
app:layout_constraintEnd_toStartOf="@id/sessionDetailsContentDescription"
|
||||
|
@ -22,14 +22,14 @@
|
|||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:gravity="end"
|
||||
app:layout_constraintBottom_toTopOf="@id/sessionDetailsContentDivider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/sessionDetailsContentTitle"
|
||||
app:layout_constraintTop_toTopOf="@id/sessionDetailsContentTop"
|
||||
tools:text="Element Web: Firefox" />
|
||||
tools:text="app.element.io: Firefox on macOS" />
|
||||
|
||||
<View
|
||||
android:id="@+id/sessionDetailsContentDivider"
|
||||
|
|
|
@ -140,27 +140,40 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:progressDrawable="@drawable/bg_seek_bar"
|
||||
android:thumbTint="?vctr_content_secondary"
|
||||
android:thumbOffset="3dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/playbackDuration"
|
||||
android:thumbTint="?vctr_content_secondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/controllerButtonsFlow"
|
||||
tools:progress="0" />
|
||||
tools:progress="50" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playbackDuration"
|
||||
android:id="@+id/elapsedTime"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="-3dp"
|
||||
android:textColor="?vctr_content_tertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/seekBar"
|
||||
tools:ignore="NegativeMargin"
|
||||
tools:text="0:11" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/remainingTime"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-3dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:textColor="?vctr_content_tertiary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/seekBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/seekBar"
|
||||
tools:text="0:23" />
|
||||
app:layout_constraintTop_toBottomOf="@id/seekBar"
|
||||
tools:ignore="NegativeMargin"
|
||||
tools:text="-0:12" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/otherSessionsRecyclerView"
|
||||
|
@ -19,8 +20,9 @@
|
|||
style="@style/Widget.Vector.Button.Text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="72dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:padding="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintStart_toStartOf="@id/otherSessionsRecyclerView"
|
||||
app:layout_constraintTop_toBottomOf="@id/otherSessionsRecyclerView"
|
||||
tools:text="@string/device_manager_other_sessions_view_all" />
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_current_session"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_current_session"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingBottom="16dp">
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sessionInfoDeviceTypeImageView"
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="18.5dp"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sessions_list_header_title"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -63,6 +63,11 @@ class DeleteMatrixClientInfoUseCaseTest {
|
|||
// Given
|
||||
val error = Exception()
|
||||
givenSetMatrixClientInfoFails(error)
|
||||
val expectedClientInfoToBeSet = MatrixClientInfoContent(
|
||||
name = "",
|
||||
version = "",
|
||||
url = "",
|
||||
)
|
||||
|
||||
// When
|
||||
val result = deleteMatrixClientInfoUseCase.execute()
|
||||
|
@ -70,6 +75,12 @@ class DeleteMatrixClientInfoUseCaseTest {
|
|||
// Then
|
||||
result.isFailure shouldBe true
|
||||
result.exceptionOrNull() shouldBeEqualTo error
|
||||
coVerify {
|
||||
fakeSetMatrixClientInfoUseCase.execute(
|
||||
fakeActiveSessionHolder.fakeSession,
|
||||
expectedClientInfoToBeSet
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun givenSetMatrixClientInfoSucceeds() {
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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.room.detail.composer.link
|
||||
|
||||
import com.airbnb.mvrx.test.MavericksTestRule
|
||||
import im.vector.app.test.test
|
||||
import im.vector.app.test.testDispatcher
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class SetLinkViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
|
||||
|
||||
companion object {
|
||||
const val link = "https://matrix.org"
|
||||
const val newLink = "https://matrix.org/new"
|
||||
const val text = "Matrix"
|
||||
}
|
||||
|
||||
private val fragmentArgs = SetLinkFragment.Args(
|
||||
isTextSupported = true,
|
||||
initialLink = link
|
||||
)
|
||||
|
||||
private fun createViewModel(
|
||||
args: SetLinkFragment.Args
|
||||
) = SetLinkViewModel(
|
||||
initialState = SetLinkViewState(args),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given no initial link, then remove button is hidden`() {
|
||||
val viewModel = createViewModel(
|
||||
fragmentArgs
|
||||
.copy(initialLink = null)
|
||||
)
|
||||
|
||||
val viewModelTest = viewModel.test()
|
||||
|
||||
viewModelTest
|
||||
.assertLatestState { !it.removeVisible }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no initial link, when link changed, then remove button is still hidden`() {
|
||||
val viewModel = createViewModel(
|
||||
fragmentArgs.copy(initialLink = null)
|
||||
)
|
||||
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(SetLinkAction.LinkChanged(newLink))
|
||||
|
||||
viewModelTest
|
||||
.assertLatestState { !it.removeVisible }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when link is unchanged, it disables the save button`() {
|
||||
val viewModel = createViewModel(
|
||||
fragmentArgs
|
||||
.copy(initialLink = link)
|
||||
)
|
||||
|
||||
val viewModelTest = viewModel.test()
|
||||
|
||||
viewModelTest
|
||||
.assertLatestState { !it.saveEnabled }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when link is changed, it enables the save button`() {
|
||||
val viewModel = createViewModel(
|
||||
fragmentArgs.copy(initialLink = link)
|
||||
)
|
||||
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(SetLinkAction.LinkChanged(newLink))
|
||||
|
||||
viewModelTest
|
||||
.assertLatestState { it.saveEnabled }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no initial link, when link is changed to empty, it disables the save button`() {
|
||||
val viewModel = createViewModel(
|
||||
fragmentArgs.copy(initialLink = null)
|
||||
)
|
||||
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(SetLinkAction.LinkChanged(""))
|
||||
|
||||
viewModelTest
|
||||
.assertLatestState {
|
||||
!it.saveEnabled
|
||||
}
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given text is supported, when saved, it emits the right event`() {
|
||||
val viewModel = createViewModel(
|
||||
fragmentArgs.copy(isTextSupported = true)
|
||||
)
|
||||
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(
|
||||
SetLinkAction.Save(link = newLink, text = text)
|
||||
)
|
||||
|
||||
viewModelTest
|
||||
.assertEvent {
|
||||
it == SetLinkViewEvents.SavedLinkAndText(
|
||||
link = newLink,
|
||||
text = text,
|
||||
)
|
||||
}
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given text is not supported, when saved, it emits the right event`() {
|
||||
val viewModel = createViewModel(
|
||||
fragmentArgs.copy(isTextSupported = false)
|
||||
)
|
||||
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(
|
||||
SetLinkAction.Save(link = newLink, text = text)
|
||||
)
|
||||
|
||||
viewModelTest
|
||||
.assertEvent {
|
||||
it == SetLinkViewEvents.SavedLink(link = newLink)
|
||||
}
|
||||
.finish()
|
||||
}
|
||||
}
|
|
@ -53,6 +53,8 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
|||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||
|
||||
private const val A_CURRENT_DEVICE_ID = "current-device-id"
|
||||
|
@ -76,6 +78,10 @@ class DevicesViewModelTest {
|
|||
private val fakeVectorPreferences = FakeVectorPreferences()
|
||||
private val toggleIpAddressVisibilityUseCase = mockk<ToggleIpAddressVisibilityUseCase>()
|
||||
|
||||
private val verifiedTransaction = mockk<VerificationTransaction>().apply {
|
||||
every { state } returns VerificationTxState.Verified
|
||||
}
|
||||
|
||||
private fun createViewModel(): DevicesViewModel {
|
||||
return DevicesViewModel(
|
||||
initialState = DevicesViewState(),
|
||||
|
@ -374,6 +380,18 @@ class DevicesViewModelTest {
|
|||
viewModelTest.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the view model when a verified transaction is updated then device list is refreshed`() {
|
||||
// Given
|
||||
val viewModel = createViewModel()
|
||||
|
||||
// When
|
||||
viewModel.transactionUpdated(verifiedTransaction)
|
||||
|
||||
// Then
|
||||
verify { viewModel.refreshDeviceList() }
|
||||
}
|
||||
|
||||
private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
|
||||
val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>()
|
||||
every { currentSessionCrossSigningInfo.deviceId } returns A_CURRENT_DEVICE_ID
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.settings.devices.v2
|
||||
|
||||
import im.vector.app.test.fakes.FakeVectorPreferences
|
||||
import org.junit.Test
|
||||
|
||||
class ToggleIpAddressVisibilityUseCaseTest {
|
||||
|
||||
private val fakeVectorPreferences = FakeVectorPreferences()
|
||||
|
||||
private val toggleIpAddressVisibilityUseCase = ToggleIpAddressVisibilityUseCase(
|
||||
vectorPreferences = fakeVectorPreferences.instance,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given ip addresses are currently visible then then visibility is set as false`() {
|
||||
// Given
|
||||
fakeVectorPreferences.givenShowIpAddressInSessionManagerScreens(true)
|
||||
|
||||
// When
|
||||
toggleIpAddressVisibilityUseCase.execute()
|
||||
|
||||
// Then
|
||||
fakeVectorPreferences.verifySetIpAddressVisibilityInDeviceManagerScreens(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given ip addresses are currently not visible then then visibility is set as true`() {
|
||||
// Given
|
||||
fakeVectorPreferences.givenShowIpAddressInSessionManagerScreens(false)
|
||||
|
||||
// When
|
||||
toggleIpAddressVisibilityUseCase.execute()
|
||||
|
||||
// Then
|
||||
fakeVectorPreferences.verifySetIpAddressVisibilityInDeviceManagerScreens(true)
|
||||
}
|
||||
}
|
|
@ -31,7 +31,6 @@ import org.junit.Before
|
|||
import org.junit.Test
|
||||
|
||||
private const val A_SESSION_ID = "session_id"
|
||||
private const val A_TITLE_RESOURCE_ID = 1234
|
||||
private val A_DEFAULT_FILTER = DeviceManagerFilterType.INACTIVE
|
||||
|
||||
class VectorSettingsDevicesViewNavigatorTest {
|
||||
|
@ -67,11 +66,11 @@ class VectorSettingsDevicesViewNavigatorTest {
|
|||
@Test
|
||||
fun `given an intent when navigating to other sessions list then it starts the correct activity`() {
|
||||
// Given
|
||||
val intent = givenIntentForOtherSessions(A_TITLE_RESOURCE_ID, A_DEFAULT_FILTER, true)
|
||||
val intent = givenIntentForOtherSessions(A_DEFAULT_FILTER, true)
|
||||
context.givenStartActivity(intent)
|
||||
|
||||
// When
|
||||
vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance, A_TITLE_RESOURCE_ID, A_DEFAULT_FILTER, true)
|
||||
vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance, A_DEFAULT_FILTER, true)
|
||||
|
||||
// Then
|
||||
context.verifyStartActivity(intent)
|
||||
|
@ -96,9 +95,9 @@ class VectorSettingsDevicesViewNavigatorTest {
|
|||
return intent
|
||||
}
|
||||
|
||||
private fun givenIntentForOtherSessions(titleResourceId: Int, defaultFilter: DeviceManagerFilterType, excludeCurrentDevice: Boolean): Intent {
|
||||
private fun givenIntentForOtherSessions(defaultFilter: DeviceManagerFilterType, excludeCurrentDevice: Boolean): Intent {
|
||||
val intent = mockk<Intent>()
|
||||
every { OtherSessionsActivity.newIntent(context.instance, titleResourceId, defaultFilter, excludeCurrentDevice) } returns intent
|
||||
every { OtherSessionsActivity.newIntent(context.instance, defaultFilter, excludeCurrentDevice) } returns intent
|
||||
return intent
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||
|
||||
private const val A_TITLE_RES_ID = 1
|
||||
private const val A_DEVICE_ID_1 = "device-id-1"
|
||||
private const val A_DEVICE_ID_2 = "device-id-2"
|
||||
private const val A_PASSWORD = "password"
|
||||
|
@ -59,7 +58,6 @@ class OtherSessionsViewModelTest {
|
|||
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
|
||||
|
||||
private val defaultArgs = OtherSessionsArgs(
|
||||
titleResourceId = A_TITLE_RES_ID,
|
||||
defaultFilter = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
excludeCurrentDevice = false,
|
||||
)
|
||||
|
|
|
@ -77,4 +77,12 @@ class FakeVectorPreferences {
|
|||
fun givenIsBackgroundSyncEnabled(isEnabled: Boolean) {
|
||||
every { instance.isBackgroundSyncEnabled() } returns isEnabled
|
||||
}
|
||||
|
||||
fun givenShowIpAddressInSessionManagerScreens(show: Boolean) {
|
||||
every { instance.showIpAddressInSessionManagerScreens() } returns show
|
||||
}
|
||||
|
||||
fun verifySetIpAddressVisibilityInDeviceManagerScreens(isVisible: Boolean) {
|
||||
verify { instance.setIpAddressVisibilityInDeviceManagerScreens(isVisible) }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue