Merge pull request #2417 from vector-im/feature/bca/quick_invite_dm_tab
Feature/bca/quick invite dm tab
This commit is contained in:
commit
67057bfac4
|
@ -39,7 +39,7 @@ We do not forget all translators, for their work of translating Element into man
|
|||
|
||||
Feel free to add your name below, when you contribute to the project!
|
||||
|
||||
Name | Matrix ID | GitHub
|
||||
--------|---------------------|--------------------------------------
|
||||
gjpower | @gjpower:matrix.org | [gjpower](https://github.com/gjpower)
|
||||
|
||||
Name | Matrix ID | GitHub
|
||||
----------|-----------------------------|--------------------------------------
|
||||
gjpower | @gjpower:matrix.org | [gjpower](https://github.com/gjpower)
|
||||
TR_SLimey | @tr_slimey:an-atom-in.space | [TR-SLimey](https://github.com/TR-SLimey)
|
||||
|
|
|
@ -2,7 +2,9 @@ Changes in Element 1.0.11 (2020-XX-XX)
|
|||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
- Create DMs with users by scanning their QR code (#2025)
|
||||
- Add Invite friends quick invite actions (#2348)
|
||||
- Add friend by scanning QR code, show your code to friends (#2025)
|
||||
|
||||
Improvements 🙌:
|
||||
- New room creation tile with quick action (#2346)
|
||||
|
@ -12,6 +14,7 @@ Improvements 🙌:
|
|||
- Handle events of type "m.room.server_acl" (#890)
|
||||
- Room creation form: add advanced section to disable federation (#1314)
|
||||
- Move "Enable Encryption" from room setting screen to room profile screen (#2394)
|
||||
- Improve Invite user screen (seamless search for matrix ID)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix crash on AttachmentViewer (#2365)
|
||||
|
|
|
@ -27,7 +27,7 @@ interface LoginWizard {
|
|||
* @param password the password field
|
||||
* @param deviceName the initial device name
|
||||
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||
* @return return a [Cancelable]
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun login(login: String,
|
||||
password: String,
|
||||
|
|
|
@ -1204,7 +1204,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
|
||||
|
||||
val targetDevices = otherDevices ?: cryptoStore.getUserDevices(otherUserId)
|
||||
?.values?.map { it.deviceId } ?: emptyList()
|
||||
?.values?.map { it.deviceId }.orEmpty()
|
||||
|
||||
val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
|||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
||||
.findAll()
|
||||
?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
|
||||
?: emptyList()
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ internal class RoomTypingUsersHandler @Inject constructor(@UserId private val us
|
|||
|
||||
fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) {
|
||||
val roomMemberHelper = RoomMemberHelper(realm, roomId)
|
||||
val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId } ?: emptyList()
|
||||
val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId }.orEmpty()
|
||||
val senderInfo = typingIds.map { userId ->
|
||||
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(userId)
|
||||
SenderInfo(
|
||||
|
|
|
@ -37,6 +37,6 @@ internal class DefaultTypingUsersTracker @Inject constructor() : TypingUsersTrac
|
|||
}
|
||||
|
||||
override fun getTypingUsers(roomId: String): List<SenderInfo> {
|
||||
return typingUsers[roomId] ?: emptyList()
|
||||
return typingUsers[roomId].orEmpty()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
|||
): LiveData<List<Widget>> {
|
||||
val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS)
|
||||
return Transformations.map(widgetsAccountData) {
|
||||
it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes) ?: emptyList()
|
||||
it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes).orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -310,7 +310,10 @@ class UiAllScreensSanityTest {
|
|||
clickOn(R.id.createChatRoomButton)
|
||||
|
||||
withIdlingResource(activityIdlingResource(CreateDirectRoomActivity::class.java)) {
|
||||
assertDisplayed(R.id.addByMatrixId)
|
||||
onView(withId(R.id.userListRecyclerView))
|
||||
.perform(waitForView(withText(R.string.qr_code)))
|
||||
onView(withId(R.id.userListRecyclerView))
|
||||
.perform(waitForView(withText(R.string.invite_friends)))
|
||||
}
|
||||
|
||||
closeSoftKeyboard()
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
android:id="@+id/debug_qr_code"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
tools:src="@drawable/ic_qr_code_add" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
<activity android:name=".features.widgets.WidgetActivity" />
|
||||
<activity android:name=".features.pin.PinActivity" />
|
||||
<activity android:name=".features.home.room.detail.search.SearchActivity" />
|
||||
<activity android:name=".features.usercode.UserCodeActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
|
|
|
@ -111,8 +111,8 @@ import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
|
|||
import im.vector.app.features.share.IncomingShareFragment
|
||||
import im.vector.app.features.signout.soft.SoftLogoutFragment
|
||||
import im.vector.app.features.terms.ReviewTermsFragment
|
||||
import im.vector.app.features.userdirectory.KnownUsersFragment
|
||||
import im.vector.app.features.userdirectory.UserDirectoryFragment
|
||||
import im.vector.app.features.usercode.ShowUserCodeFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragment
|
||||
import im.vector.app.features.widgets.WidgetFragment
|
||||
|
||||
@Module
|
||||
|
@ -255,13 +255,8 @@ interface FragmentModule {
|
|||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(UserDirectoryFragment::class)
|
||||
fun bindUserDirectoryFragment(fragment: UserDirectoryFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(KnownUsersFragment::class)
|
||||
fun bindKnownUsersFragment(fragment: KnownUsersFragment): Fragment
|
||||
@FragmentKey(UserListFragment::class)
|
||||
fun bindUserListFragment(fragment: UserListFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
@ -582,4 +577,9 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(SearchFragment::class)
|
||||
fun bindSearchFragment(fragment: SearchFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(ShowUserCodeFragment::class)
|
||||
fun bindShowUserCodeFragment(fragment: ShowUserCodeFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import im.vector.app.features.invite.InviteUsersToRoomActivity
|
|||
import im.vector.app.features.invite.VectorInviteView
|
||||
import im.vector.app.features.link.LinkHandlerActivity
|
||||
import im.vector.app.features.login.LoginActivity
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||
import im.vector.app.features.media.BigImageViewerActivity
|
||||
import im.vector.app.features.media.VectorAttachmentViewerActivity
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
|
@ -72,6 +73,7 @@ import im.vector.app.features.share.IncomingShareActivity
|
|||
import im.vector.app.features.signout.soft.SoftLogoutActivity
|
||||
import im.vector.app.features.terms.ReviewTermsActivity
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import im.vector.app.features.usercode.UserCodeActivity
|
||||
import im.vector.app.features.widgets.WidgetActivity
|
||||
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
||||
import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
|
||||
|
@ -140,6 +142,7 @@ interface ScreenComponent {
|
|||
fun inject(activity: VectorAttachmentViewerActivity)
|
||||
fun inject(activity: VectorJitsiActivity)
|
||||
fun inject(activity: SearchActivity)
|
||||
fun inject(activity: UserCodeActivity)
|
||||
|
||||
/* ==========================================================================================
|
||||
* BottomSheets
|
||||
|
@ -158,6 +161,7 @@ interface ScreenComponent {
|
|||
fun inject(bottomSheet: RoomWidgetsBottomSheet)
|
||||
fun inject(bottomSheet: CallControlsBottomSheet)
|
||||
fun inject(bottomSheet: SignOutBottomSheetDialogFragment)
|
||||
fun inject(bottomSheet: MatrixToBottomSheet)
|
||||
|
||||
/* ==========================================================================================
|
||||
* Others
|
||||
|
|
|
@ -35,7 +35,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
|||
import im.vector.app.features.reactions.EmojiChooserViewModel
|
||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
|
||||
@Module
|
||||
interface ViewModelModule {
|
||||
|
@ -87,8 +87,8 @@ interface ViewModelModule {
|
|||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(UserDirectorySharedActionViewModel::class)
|
||||
fun bindUserDirectorySharedActionViewModel(viewModel: UserDirectorySharedActionViewModel): ViewModel
|
||||
@ViewModelKey(UserListSharedActionViewModel::class)
|
||||
fun bindUserListSharedActionViewModel(viewModel: UserListSharedActionViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.epoxy
|
||||
|
||||
import android.widget.CompoundButton
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
import im.vector.app.R
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_checkbox)
|
||||
abstract class CheckBoxItem : VectorEpoxyModel<CheckBoxItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var checked: Boolean = false
|
||||
|
||||
@EpoxyAttribute lateinit var title: String
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.checkbox.isChecked = checked
|
||||
holder.checkbox.text = title
|
||||
holder.checkbox.setOnCheckedChangeListener(checkChangeListener)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val checkbox by bind<MaterialCheckBox>(R.id.checkbox)
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ import androidx.annotation.DrawableRes
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
|
||||
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
|
||||
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_search,
|
||||
@DrawableRes clearIconRes: Int = R.drawable.ic_x_gray) {
|
||||
addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
|
|
|
@ -587,6 +587,16 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
}
|
||||
}
|
||||
|
||||
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply {
|
||||
withActionTitle?.let {
|
||||
setAction(withActionTitle, { action?.invoke() })
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* User Consent
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -29,6 +29,7 @@ import android.os.Build
|
|||
import android.os.Environment
|
||||
import android.provider.Browser
|
||||
import android.provider.MediaStore
|
||||
import android.provider.Settings
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
|
@ -448,6 +449,19 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID
|
|||
}
|
||||
}
|
||||
|
||||
fun openAppSettingsPage(activity: Activity) {
|
||||
try {
|
||||
activity.startActivity(
|
||||
Intent().apply {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
data = Uri.fromParts("package", activity.packageName, null)
|
||||
})
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user to select a location and a file name to write in
|
||||
*/
|
||||
|
|
|
@ -30,6 +30,7 @@ import androidx.core.app.ActivityCompat
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import timber.log.Timber
|
||||
|
||||
// Android M permission request code management
|
||||
|
@ -284,6 +285,12 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
|||
return isPermissionGranted
|
||||
}
|
||||
|
||||
fun VectorBaseActivity.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) {
|
||||
showSnackbar(getString(rationaleMessage), R.string.settings) {
|
||||
openAppSettingsPage(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used in [.checkPermissions] to populate the list of the
|
||||
* permissions to be granted (permissionsListToBeGrantedOut) and the list of the permissions already denied (permissionAlreadyDeniedListOut).
|
||||
|
|
|
@ -136,13 +136,19 @@ fun startSharePlainTextIntent(fragment: Fragment,
|
|||
activityResultLauncher: ActivityResultLauncher<Intent>?,
|
||||
chooserTitle: String?,
|
||||
text: String,
|
||||
subject: String? = null) {
|
||||
subject: String? = null,
|
||||
extraTitle: String? = null) {
|
||||
val share = Intent(Intent.ACTION_SEND)
|
||||
share.type = "text/plain"
|
||||
share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
|
||||
// Add data to the intent, the receiving app will decide what to do with it.
|
||||
share.putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
share.putExtra(Intent.EXTRA_TEXT, text)
|
||||
|
||||
extraTitle?.let {
|
||||
share.putExtra(Intent.EXTRA_TITLE, it)
|
||||
}
|
||||
|
||||
val intent = Intent.createChooser(share, chooserTitle)
|
||||
try {
|
||||
if (activityResultLauncher != null) {
|
||||
|
|
|
@ -30,10 +30,10 @@ import im.vector.app.core.extensions.configureWith
|
|||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.features.userdirectory.PendingInvitee
|
||||
import im.vector.app.features.userdirectory.UserDirectoryAction
|
||||
import im.vector.app.features.userdirectory.UserDirectorySharedAction
|
||||
import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserDirectoryViewModel
|
||||
import im.vector.app.features.userdirectory.UserListAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_contacts_book.*
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
|
@ -46,16 +46,16 @@ class ContactsBookFragment @Inject constructor(
|
|||
) : VectorBaseFragment(), ContactsBookController.Callback {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_contacts_book
|
||||
private val viewModel: UserDirectoryViewModel by activityViewModel()
|
||||
private val viewModel: UserListViewModel by activityViewModel()
|
||||
|
||||
// Use activityViewModel to avoid loading several times the data
|
||||
private val contactsBookViewModel: ContactsBookViewModel by activityViewModel()
|
||||
|
||||
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||
sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||
setupRecyclerView()
|
||||
setupFilterView()
|
||||
setupConsentView()
|
||||
|
@ -110,7 +110,7 @@ class ContactsBookFragment @Inject constructor(
|
|||
|
||||
private fun setupCloseView() {
|
||||
phoneBookClose.debouncedClicks {
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,13 +122,13 @@ class ContactsBookFragment @Inject constructor(
|
|||
|
||||
override fun onMatrixIdClick(matrixId: String) {
|
||||
view?.hideKeyboard()
|
||||
viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
|
||||
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
||||
}
|
||||
|
||||
override fun onThreePidClick(threePid: ThreePid) {
|
||||
view?.hideKeyboard()
|
||||
viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
|
||||
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,28 +37,31 @@ import im.vector.app.core.extensions.exhaustive
|
|||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.core.platform.WaitingViewData
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
|
||||
import im.vector.app.core.utils.allGranted
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||
import im.vector.app.features.userdirectory.KnownUsersFragment
|
||||
import im.vector.app.features.userdirectory.KnownUsersFragmentArgs
|
||||
import im.vector.app.features.userdirectory.UserDirectoryFragment
|
||||
import im.vector.app.features.userdirectory.UserDirectorySharedAction
|
||||
import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserDirectoryViewModel
|
||||
import im.vector.app.features.userdirectory.UserListFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
||||
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewState
|
||||
import kotlinx.android.synthetic.main.activity.*
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||
import java.net.HttpURLConnection
|
||||
import javax.inject.Inject
|
||||
|
||||
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||
class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory {
|
||||
|
||||
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
||||
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||
@Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory
|
||||
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
||||
@Inject lateinit var userListViewModelFactory: UserListViewModel.Factory
|
||||
@Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory
|
||||
@Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
|
@ -68,31 +71,34 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun create(initialState: UserListViewState): UserListViewModel {
|
||||
return userListViewModelFactory.create(initialState)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
toolbar.visibility = View.GONE
|
||||
sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||
|
||||
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
when (sharedAction) {
|
||||
UserDirectorySharedAction.OpenUsersDirectory ->
|
||||
addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
|
||||
UserDirectorySharedAction.Close -> finish()
|
||||
UserDirectorySharedAction.GoBack -> onBackPressed()
|
||||
is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
|
||||
UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook()
|
||||
.subscribe { action ->
|
||||
when (action) {
|
||||
UserListSharedAction.Close -> finish()
|
||||
UserListSharedAction.GoBack -> onBackPressed()
|
||||
is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(action)
|
||||
UserListSharedAction.OpenPhoneBook -> openPhoneBook()
|
||||
UserListSharedAction.AddByQrCode -> openAddByQrCode()
|
||||
}.exhaustive
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
if (isFirstCreation()) {
|
||||
addFragment(
|
||||
R.id.container,
|
||||
KnownUsersFragment::class.java,
|
||||
KnownUsersFragmentArgs(
|
||||
UserListFragment::class.java,
|
||||
UserListFragmentArgs(
|
||||
title = getString(R.string.fab_menu_create_chat),
|
||||
menuResId = R.menu.vector_create_direct_room,
|
||||
isCreatingRoom = true
|
||||
menuResId = R.menu.vector_create_direct_room
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -101,6 +107,12 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun openAddByQrCode() {
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA, 0)) {
|
||||
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPhoneBook() {
|
||||
// Check permission first
|
||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
|
||||
|
@ -116,15 +128,23 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||
if (allGranted(grantResults)) {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||
} else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
||||
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
||||
}
|
||||
} else {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||
} else if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) {
|
||||
private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
|
||||
if (action.itemId == R.id.action_create_direct_room) {
|
||||
viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(
|
||||
action.invitees,
|
||||
action.existingDmRoomId
|
||||
null
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +198,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun getIntent(context: Context): Intent {
|
||||
return Intent(context, CreateDirectRoomActivity::class.java)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2020 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.createdirect
|
||||
|
||||
import android.widget.Toast
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultMetadataType
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.features.userdirectory.PendingInvitee
|
||||
import kotlinx.android.synthetic.main.fragment_qr_code_scanner.*
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import javax.inject.Inject
|
||||
|
||||
class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
|
||||
|
||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_qr_code_scanner
|
||||
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
if (allGranted) {
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
// Start camera on resume
|
||||
scannerView.startCamera()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
view?.hideKeyboard()
|
||||
// Register ourselves as a handler for scan results.
|
||||
scannerView.setResultHandler(this)
|
||||
// Start camera on resume
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// Unregister ourselves as a handler for scan results.
|
||||
scannerView.setResultHandler(null)
|
||||
// Stop camera on pause
|
||||
scannerView.stopCamera()
|
||||
}
|
||||
|
||||
// Copied from https://github.com/markusfisch/BinaryEye/blob/
|
||||
// 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
|
||||
private fun getRawBytes(result: Result): ByteArray? {
|
||||
val metadata = result.resultMetadata ?: return null
|
||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
||||
var bytes = ByteArray(0)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (seg in segments as Iterable<ByteArray>) {
|
||||
bytes += seg
|
||||
}
|
||||
// byte segments can never be shorter than the text.
|
||||
// Zxing cuts off content prefixes like "WIFI:"
|
||||
return if (bytes.size >= result.text.length) bytes else null
|
||||
}
|
||||
|
||||
private fun addByQrCode(value: String) {
|
||||
val mxid = (PermalinkParser.parse(value) as? PermalinkData.UserLink)?.userId
|
||||
|
||||
if (mxid === null) {
|
||||
Toast.makeText(requireContext(), R.string.invalid_qr_code_uri, Toast.LENGTH_SHORT).show()
|
||||
requireActivity().finish()
|
||||
} else {
|
||||
val existingDm = viewModel.session.getExistingDirectRoomWithUser(mxid)
|
||||
// The following assumes MXIDs are case insensitive
|
||||
if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) {
|
||||
Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
|
||||
requireActivity().finish()
|
||||
} else {
|
||||
// Try to get user from known users and fall back to creating a User object from MXID
|
||||
val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null)
|
||||
|
||||
viewModel.handle(
|
||||
CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)), existingDm)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleResult(result: Result?) {
|
||||
if (result === null) {
|
||||
Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
|
||||
requireActivity().finish()
|
||||
} else {
|
||||
val rawBytes = getRawBytes(result)
|
||||
val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
|
||||
val value = rawBytesStr ?: result.text
|
||||
addByQrCode(value)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ import org.matrix.android.sdk.rx.rx
|
|||
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: CreateDirectRoomViewState,
|
||||
private val rawService: RawService,
|
||||
private val session: Session)
|
||||
val session: Session)
|
||||
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
|
|
|
@ -18,15 +18,19 @@ package im.vector.app.features.home
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.observeK
|
||||
import im.vector.app.core.extensions.replaceChildFragment
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||
import im.vector.app.features.grouplist.GroupListFragment
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
import im.vector.app.features.usercode.UserCodeActivity
|
||||
import im.vector.app.features.workers.signout.SignOutUiWorker
|
||||
import kotlinx.android.synthetic.main.fragment_home_drawer.*
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -75,6 +79,32 @@ class HomeDrawerFragment @Inject constructor(
|
|||
SignOutUiWorker(requireActivity()).perform()
|
||||
}
|
||||
|
||||
homeDrawerQRCodeButton.debouncedClicks {
|
||||
UserCodeActivity.newIntent(requireContext(), sharedActionViewModel.session.myUserId).let {
|
||||
val options =
|
||||
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
requireActivity(),
|
||||
homeDrawerHeaderAvatarView,
|
||||
ViewCompat.getTransitionName(homeDrawerHeaderAvatarView) ?: ""
|
||||
)
|
||||
startActivity(it, options.toBundle())
|
||||
}
|
||||
}
|
||||
|
||||
homeDrawerInviteFriendButton.debouncedClicks {
|
||||
session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
|
||||
val text = getString(R.string.invite_friends_text, permalink)
|
||||
|
||||
startSharePlainTextIntent(
|
||||
fragment = this,
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = getString(R.string.invite_friends),
|
||||
text = text,
|
||||
extraTitle = getString(R.string.invite_friends_rich_title)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Debug menu
|
||||
homeDrawerHeaderDebugView.isVisible = BuildConfig.DEBUG && vectorPreferences.developerMode()
|
||||
homeDrawerHeaderDebugView.debouncedClicks {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.home
|
||||
|
||||
import im.vector.app.core.platform.VectorSharedActionViewModel
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<HomeActivitySharedAction>()
|
||||
class HomeSharedActionViewModel @Inject constructor(val session: Session) : VectorSharedActionViewModel<HomeActivitySharedAction>()
|
||||
|
|
|
@ -22,7 +22,7 @@ import com.airbnb.epoxy.EpoxyModelClass
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.features.home.room.list.widget.FabMenuView
|
||||
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_room_filter_footer)
|
||||
abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.Holder>() {
|
||||
|
@ -46,7 +46,7 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.
|
|||
val openRoomDirectory by bind<Button>(R.id.roomFilterFooterOpenRoomDirectory)
|
||||
}
|
||||
|
||||
interface FilteredRoomFooterItemListener : FabMenuView.Listener {
|
||||
interface FilteredRoomFooterItemListener : NotifsFabMenuView.Listener {
|
||||
fun createRoom(initialName: String)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
|
|||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.app.features.home.room.list.widget.FabMenuView
|
||||
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||
|
@ -66,8 +66,7 @@ class RoomListFragment @Inject constructor(
|
|||
val roomListViewModelFactory: RoomListViewModel.Factory,
|
||||
private val notificationDrawerManager: NotificationDrawerManager,
|
||||
private val sharedViewPool: RecyclerView.RecycledViewPool
|
||||
|
||||
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
|
||||
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, NotifsFabMenuView.Listener {
|
||||
|
||||
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
|
||||
|
|
|
@ -22,15 +22,15 @@ import androidx.constraintlayout.motion.widget.MotionLayout
|
|||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import im.vector.app.R
|
||||
import kotlinx.android.synthetic.main.motion_fab_menu_merge.view.*
|
||||
import kotlinx.android.synthetic.main.motion_notifs_fab_menu_merge.view.*
|
||||
|
||||
class FabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) {
|
||||
class NotifsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.motion_fab_menu_merge, this)
|
||||
inflate(context, R.layout.motion_notifs_fab_menu_merge, this)
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
|
@ -28,7 +28,7 @@ import im.vector.app.core.platform.EmptyViewEvents
|
|||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
||||
import im.vector.app.features.userdirectory.KnownUsersFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragment
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
|
@ -50,7 +50,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor(
|
|||
companion object : MvRxViewModelFactory<HomeServerCapabilitiesViewModel, HomeServerCapabilitiesViewState> {
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel? {
|
||||
val fragment: KnownUsersFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
val fragment: UserListFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.homeServerCapabilitiesViewModelFactory.create(state)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.viewModel
|
||||
|
@ -29,7 +30,6 @@ import im.vector.app.core.di.ScreenComponent
|
|||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.core.platform.WaitingViewData
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
|
||||
|
@ -39,12 +39,12 @@ import im.vector.app.core.utils.checkPermissions
|
|||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||
import im.vector.app.features.userdirectory.KnownUsersFragment
|
||||
import im.vector.app.features.userdirectory.KnownUsersFragmentArgs
|
||||
import im.vector.app.features.userdirectory.UserDirectoryFragment
|
||||
import im.vector.app.features.userdirectory.UserDirectorySharedAction
|
||||
import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserDirectoryViewModel
|
||||
import im.vector.app.features.userdirectory.UserListFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
||||
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewState
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.activity.*
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
|
@ -54,11 +54,11 @@ import javax.inject.Inject
|
|||
@Parcelize
|
||||
data class InviteUsersToRoomArgs(val roomId: String) : Parcelable
|
||||
|
||||
class InviteUsersToRoomActivity : SimpleFragmentActivity() {
|
||||
class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory {
|
||||
|
||||
private val viewModel: InviteUsersToRoomViewModel by viewModel()
|
||||
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||
@Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory
|
||||
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
||||
@Inject lateinit var userListViewModelFactory: UserListViewModel.Factory
|
||||
@Inject lateinit var inviteUsersToRoomViewModelFactory: InviteUsersToRoomViewModel.Factory
|
||||
@Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
|
@ -68,32 +68,40 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
|
|||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun create(initialState: UserListViewState): UserListViewModel {
|
||||
return userListViewModelFactory.create(initialState)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
toolbar.visibility = View.GONE
|
||||
sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||
|
||||
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
when (sharedAction) {
|
||||
UserDirectorySharedAction.OpenUsersDirectory ->
|
||||
addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
|
||||
UserDirectorySharedAction.Close -> finish()
|
||||
UserDirectorySharedAction.GoBack -> onBackPressed()
|
||||
is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
|
||||
UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook()
|
||||
}.exhaustive
|
||||
UserListSharedAction.Close -> finish()
|
||||
UserListSharedAction.GoBack -> onBackPressed()
|
||||
is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
|
||||
UserListSharedAction.OpenPhoneBook -> openPhoneBook()
|
||||
// not exhaustive because it's a sharedAction
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
if (isFirstCreation()) {
|
||||
val args: InviteUsersToRoomArgs? = intent.extras?.getParcelable(MvRx.KEY_ARG)
|
||||
addFragment(
|
||||
R.id.container,
|
||||
KnownUsersFragment::class.java,
|
||||
KnownUsersFragmentArgs(
|
||||
UserListFragment::class.java,
|
||||
UserListFragmentArgs(
|
||||
title = getString(R.string.invite_users_to_room_title),
|
||||
menuResId = R.menu.vector_invite_users_to_room,
|
||||
excludedUserIds = viewModel.getUserIdsOfRoomMembers()
|
||||
excludedUserIds = viewModel.getUserIdsOfRoomMembers(),
|
||||
existingRoomId = args?.roomId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -101,6 +109,12 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
|
|||
viewModel.observeViewEvents { renderInviteEvents(it) }
|
||||
}
|
||||
|
||||
private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
|
||||
if (action.itemId == R.id.action_invite_users_to_room_invite) {
|
||||
viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees))
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPhoneBook() {
|
||||
// Check permission first
|
||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
|
||||
|
@ -117,12 +131,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
|
|||
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) {
|
||||
if (action.itemId == R.id.action_invite_users_to_room_invite) {
|
||||
viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees))
|
||||
} else {
|
||||
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.matrixto
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ScreenComponent
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import kotlinx.android.synthetic.main.bottom_sheet_matrix_to_card.*
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottomSheetDialogFragment() {
|
||||
|
||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||
|
||||
interface InteractionListener {
|
||||
fun didTapStartMessage(matrixItem: MatrixItem)
|
||||
}
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
private var interactionListener: InteractionListener? = null
|
||||
|
||||
override fun getLayoutResId() = R.layout.bottom_sheet_matrix_to_card
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
matrixToCardSendMessageButton.debouncedClicks {
|
||||
interactionListener?.didTapStartMessage(matrixItem)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
matrixToCardNameText.setTextOrHide(matrixItem.displayName)
|
||||
matrixToCardUserIdText.setTextOrHide(matrixItem.id)
|
||||
avatarRenderer.render(matrixItem, matrixToCardAvatar)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(matrixItem: MatrixItem, listener: InteractionListener?): MatrixToBottomSheet {
|
||||
return MatrixToBottomSheet(matrixItem).apply {
|
||||
interactionListener = listener
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -204,9 +204,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
Timber.w("Try to join an already joining room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
val viaServers = state.roomDirectoryData.homeServer?.let {
|
||||
listOf(it)
|
||||
} ?: emptyList()
|
||||
val viaServers = state.roomDirectoryData.homeServer
|
||||
?.let { listOf(it) }
|
||||
.orEmpty()
|
||||
session.joinRoom(action.roomId, viaServers = viaServers, callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
||||
|
|
|
@ -62,7 +62,7 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
|
|||
holder.avatarView.isInvisible = directoryAvatarUrl.isNullOrBlank() && includeAllNetworks
|
||||
|
||||
holder.nameView.text = directoryName
|
||||
holder.descritionView.setTextOrHide(directoryDescription)
|
||||
holder.descriptionView.setTextOrHide(directoryDescription)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
|
@ -70,6 +70,6 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
|
|||
|
||||
val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar)
|
||||
val nameView by bind<TextView>(R.id.itemRoomDirectoryName)
|
||||
val descritionView by bind<TextView>(R.id.itemRoomDirectoryDescription)
|
||||
val descriptionView by bind<TextView>(R.id.itemRoomDirectoryDescription)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,17 @@ class RoomMemberProfileController @Inject constructor(
|
|||
divider = false,
|
||||
action = { callback?.onIgnoreClicked() }
|
||||
)
|
||||
if (!state.isMine) {
|
||||
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
||||
|
||||
buildProfileAction(
|
||||
id = "direct",
|
||||
editable = false,
|
||||
title = stringProvider.getString(R.string.room_member_open_or_create_dm),
|
||||
dividerColor = dividerColor,
|
||||
action = { callback?.onOpenDmClicked() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRoomMemberActions(state: RoomMemberProfileViewState) {
|
||||
|
|
|
@ -294,12 +294,20 @@ class RoomMemberProfileFragment @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleShareRoomMemberProfile(permalink: String) {
|
||||
startSharePlainTextIntent(
|
||||
fragment = this,
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = null,
|
||||
text = permalink
|
||||
)
|
||||
val view = layoutInflater.inflate(R.layout.dialog_share_qr_code, null)
|
||||
val qrCode = view.findViewById<im.vector.app.core.ui.views.QrCodeImageView>(R.id.itemShareQrCodeImage)
|
||||
qrCode.setData(permalink)
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setView(view)
|
||||
.setNeutralButton(R.string.ok, null)
|
||||
.setPositiveButton(R.string.share_by_text) { _, _ ->
|
||||
startSharePlainTextIntent(
|
||||
fragment = this,
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = null,
|
||||
text = permalink
|
||||
)
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun onAvatarClicked(view: View, userMatrixItem: MatrixItem) {
|
||||
|
|
|
@ -16,15 +16,13 @@
|
|||
|
||||
package im.vector.app.features.settings
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.preference.Preference
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.preference.VectorPreference
|
||||
import im.vector.app.core.utils.copyToClipboard
|
||||
import im.vector.app.core.utils.displayInWebView
|
||||
import im.vector.app.core.utils.openAppSettingsPage
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import im.vector.app.features.version.VersionProvider
|
||||
import im.vector.app.openOssLicensesMenuActivity
|
||||
|
@ -42,18 +40,7 @@ class VectorSettingsHelpAboutFragment @Inject constructor(
|
|||
// preference to start the App info screen, to facilitate App permissions access
|
||||
findPreference<VectorPreference>(APP_INFO_LINK_PREFERENCE_KEY)!!
|
||||
.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
activity?.let {
|
||||
val intent = Intent().apply {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
val uri = Uri.fromParts("package", requireContext().packageName, null)
|
||||
|
||||
data = uri
|
||||
}
|
||||
it.applicationContext.startActivity(intent)
|
||||
}
|
||||
|
||||
activity?.let { openAppSettingsPage(it) }
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.BinaryBitmap
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.LuminanceSource
|
||||
import com.google.zxing.MultiFormatReader
|
||||
import com.google.zxing.RGBLuminanceSource
|
||||
import com.google.zxing.ReaderException
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
|
||||
// Some helper code from BinaryEye
|
||||
object QRCodeBitmapDecodeHelper {
|
||||
|
||||
private val multiFormatReader = MultiFormatReader()
|
||||
private val decoderHints = mapOf(DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE))
|
||||
|
||||
fun decodeQRFromBitmap(bitmap: Bitmap): Result? =
|
||||
decode(bitmap, false) ?: decode(bitmap, true)
|
||||
|
||||
private fun decode(bitmap: Bitmap, invert: Boolean = false): Result? {
|
||||
val pixels = IntArray(bitmap.width * bitmap.height)
|
||||
return decode(pixels, bitmap, invert)
|
||||
}
|
||||
|
||||
private fun decode(
|
||||
pixels: IntArray,
|
||||
bitmap: Bitmap,
|
||||
invert: Boolean = false
|
||||
): Result? {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
if (bitmap.config != Bitmap.Config.ARGB_8888) {
|
||||
bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
} else {
|
||||
bitmap
|
||||
}.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
return decodeLuminanceSource(
|
||||
RGBLuminanceSource(width, height, pixels),
|
||||
invert
|
||||
)
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(
|
||||
source: LuminanceSource,
|
||||
invert: Boolean
|
||||
): Result? {
|
||||
return decodeLuminanceSource(
|
||||
if (invert) {
|
||||
source.invert()
|
||||
} else {
|
||||
source
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? {
|
||||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
return try {
|
||||
multiFormatReader.decode(bitmap, decoderHints)
|
||||
} catch (e: ReaderException) {
|
||||
null
|
||||
} finally {
|
||||
multiFormatReader.reset()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultMetadataType
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.lib.multipicker.MultiPicker
|
||||
import im.vector.lib.multipicker.utils.ImageUtils
|
||||
import kotlinx.android.synthetic.main.fragment_qr_code_scanner_with_button.*
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class ScanUserCodeFragment @Inject constructor()
|
||||
: VectorBaseFragment(),
|
||||
ZXingScannerView.ResultHandler {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_qr_code_scanner_with_button
|
||||
|
||||
val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
|
||||
|
||||
var autoFocus = true
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
userCodeMyCodeButton.debouncedClicks {
|
||||
sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
|
||||
}
|
||||
|
||||
userCodeOpenGalleryButton.debouncedClicks {
|
||||
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
|
||||
}
|
||||
}
|
||||
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
if (allGranted) {
|
||||
startCamera()
|
||||
} else {
|
||||
// For now just go back
|
||||
sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
|
||||
}
|
||||
}
|
||||
|
||||
private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||
MultiPicker
|
||||
.get(MultiPicker.IMAGE)
|
||||
.getSelectedFiles(requireActivity(), activityResult.data)
|
||||
.firstOrNull()
|
||||
?.contentUri
|
||||
?.let { uri ->
|
||||
// try to see if it is a valid matrix code
|
||||
val bitmap = ImageUtils.getBitmap(requireContext(), uri)
|
||||
?: return@let Unit.also {
|
||||
Toast.makeText(requireContext(), getString(R.string.qr_code_not_scanned), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
handleResult(tryOrNull { QRCodeBitmapDecodeHelper.decodeQRFromBitmap(bitmap) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
userCodeScannerView.startCamera()
|
||||
userCodeScannerView.setAutoFocus(autoFocus)
|
||||
userCodeScannerView.debouncedClicks {
|
||||
this.autoFocus = !autoFocus
|
||||
userCodeScannerView.setAutoFocus(autoFocus)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// Register ourselves as a handler for scan results.
|
||||
userCodeScannerView.setResultHandler(this)
|
||||
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)) {
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
userCodeScannerView.setResultHandler(null)
|
||||
// Stop camera on pause
|
||||
userCodeScannerView.stopCamera()
|
||||
}
|
||||
|
||||
override fun handleResult(result: Result?) {
|
||||
if (result === null) {
|
||||
Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
|
||||
requireActivity().finish()
|
||||
} else {
|
||||
val rawBytes = getRawBytes(result)
|
||||
val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
|
||||
val value = rawBytesStr ?: result.text
|
||||
sharedViewModel.handle(UserCodeActions.DecodedQRCode(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/markusfisch/BinaryEye/blob/
|
||||
// 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
|
||||
private fun getRawBytes(result: Result): ByteArray? {
|
||||
val metadata = result.resultMetadata ?: return null
|
||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
||||
var bytes = ByteArray(0)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (seg in segments as Iterable<ByteArray>) {
|
||||
bytes += seg
|
||||
}
|
||||
// byte segments can never be shorter than the text.
|
||||
// Zxing cuts off content prefixes like "WIFI:"
|
||||
return if (bytes.size >= result.text.length) bytes else null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import kotlinx.android.synthetic.main.fragment_user_code_show.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ShowUserCodeFragment @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer
|
||||
) : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_user_code_show
|
||||
|
||||
val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
|
||||
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
if (allGranted) {
|
||||
doOpenQRCodeScanner()
|
||||
} else {
|
||||
sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
showUserCodeClose.debouncedClicks {
|
||||
sharedViewModel.handle(UserCodeActions.DismissAction)
|
||||
}
|
||||
showUserCodeScanButton.debouncedClicks {
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
|
||||
doOpenQRCodeScanner()
|
||||
}
|
||||
}
|
||||
showUserCodeShareButton.debouncedClicks {
|
||||
sharedViewModel.handle(UserCodeActions.ShareByText)
|
||||
}
|
||||
|
||||
sharedViewModel.observeViewEvents {
|
||||
if (it is UserCodeShareViewEvents.SharePlainText) {
|
||||
startSharePlainTextIntent(
|
||||
fragment = this,
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = it.title,
|
||||
text = it.text,
|
||||
extraTitle = it.richPlainText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doOpenQRCodeScanner() {
|
||||
sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SCAN))
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(sharedViewModel) { state ->
|
||||
state.matrixItem?.let { avatarRenderer.render(it, showUserCodeAvatar) }
|
||||
state.shareLink?.let { showUserCodeQRImage.setData(it) }
|
||||
showUserCodeCardNameText.setTextOrHide(state.matrixItem?.displayName)
|
||||
showUserCodeCardUserIdText.setTextOrHide(state.matrixItem?.id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
sealed class UserCodeActions : VectorViewModelAction {
|
||||
object DismissAction : UserCodeActions()
|
||||
data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions()
|
||||
data class DecodedQRCode(val code: String) : UserCodeActions()
|
||||
data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions()
|
||||
object CameraPermissionNotGranted : UserCodeActions()
|
||||
object ShareByText : UserCodeActions()
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ScreenComponent
|
||||
import im.vector.app.core.extensions.commitTransaction
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.activity_simple.*
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class UserCodeActivity
|
||||
: VectorBaseActivity(), UserCodeSharedViewModel.Factory, MatrixToBottomSheet.InteractionListener {
|
||||
|
||||
@Inject lateinit var viewModelFactory: UserCodeSharedViewModel.Factory
|
||||
|
||||
val sharedViewModel: UserCodeSharedViewModel by viewModel()
|
||||
|
||||
@Parcelize
|
||||
data class Args(
|
||||
val userId: String
|
||||
) : Parcelable
|
||||
|
||||
override fun getLayoutRes() = R.layout.activity_simple
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (isFirstCreation()) {
|
||||
// should be there early for shared element transition
|
||||
showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
|
||||
}
|
||||
|
||||
sharedViewModel.selectSubscribe(this, UserCodeState::mode) { mode ->
|
||||
when (mode) {
|
||||
UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
|
||||
UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY)
|
||||
is UserCodeState.Mode.RESULT -> {
|
||||
showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
|
||||
MatrixToBottomSheet.create(mode.matrixItem, this).show(supportFragmentManager, "MatrixToBottomSheet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sharedViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
|
||||
UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true
|
||||
UserCodeShareViewEvents.HideWaitingScreen -> simpleActivityWaitingView.isVisible = false
|
||||
is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
|
||||
is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
|
||||
UserCodeShareViewEvents.CameraPermissionNotGranted -> onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFragment(fragmentClass: KClass<out Fragment>, bundle: Bundle) {
|
||||
if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
||||
replace(R.id.simpleFragmentContainer,
|
||||
fragmentClass.java,
|
||||
bundle,
|
||||
fragmentClass.simpleName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun didTapStartMessage(matrixItem: MatrixItem) {
|
||||
sharedViewModel.handle(UserCodeActions.StartChattingWithUser(matrixItem))
|
||||
}
|
||||
|
||||
override fun onBackPressed() = withState(sharedViewModel) {
|
||||
when (it.mode) {
|
||||
UserCodeState.Mode.SHOW -> super.onBackPressed()
|
||||
is UserCodeState.Mode.RESULT,
|
||||
UserCodeState.Mode.SCAN -> sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
override fun create(initialState: UserCodeState) =
|
||||
viewModelFactory.create(initialState)
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context, userId: String): Intent {
|
||||
return Intent(context, UserCodeActivity::class.java).apply {
|
||||
putExtra(MvRx.KEY_ARG, Args(userId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class UserCodeShareViewEvents : VectorViewEvents {
|
||||
object Dismiss : UserCodeShareViewEvents()
|
||||
object ShowWaitingScreen : UserCodeShareViewEvents()
|
||||
object HideWaitingScreen : UserCodeShareViewEvents()
|
||||
data class ToastMessage(val message: String) : UserCodeShareViewEvents()
|
||||
data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents()
|
||||
object CameraPermissionNotGranted : UserCodeShareViewEvents()
|
||||
data class SharePlainText(val text: String, val title: String, val richPlainText: String) : UserCodeShareViewEvents()
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
|
||||
class UserCodeSharedViewModel @AssistedInject constructor(
|
||||
@Assisted val initialState: UserCodeState,
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider,
|
||||
private val rawService: RawService) : VectorViewModel<UserCodeState, UserCodeActions, UserCodeShareViewEvents>(initialState) {
|
||||
|
||||
companion object : MvRxViewModelFactory<UserCodeSharedViewModel, UserCodeState> {
|
||||
override fun create(viewModelContext: ViewModelContext, state: UserCodeState): UserCodeSharedViewModel? {
|
||||
val factory = when (viewModelContext) {
|
||||
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||
}
|
||||
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val user = session.getUser(initialState.userId)
|
||||
setState {
|
||||
copy(
|
||||
matrixItem = user?.toMatrixItem(),
|
||||
shareLink = session.permalinkService().createPermalink(initialState.userId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: UserCodeState): UserCodeSharedViewModel
|
||||
}
|
||||
|
||||
override fun handle(action: UserCodeActions) {
|
||||
when (action) {
|
||||
UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss)
|
||||
is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) }
|
||||
is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
|
||||
is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
|
||||
UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted)
|
||||
UserCodeActions.ShareByText -> handleShareByText()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShareByText() {
|
||||
session.permalinkService().createPermalink(session.myUserId)?.let { permalink ->
|
||||
val text = stringProvider.getString(R.string.invite_friends_text, permalink)
|
||||
_viewEvents.post(UserCodeShareViewEvents.SharePlainText(
|
||||
text,
|
||||
stringProvider.getString(R.string.invite_friends),
|
||||
stringProvider.getString(R.string.invite_friends_rich_title)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStartChatting(withUser: UserCodeActions.StartChattingWithUser) {
|
||||
val mxId = withUser.matrixItem.id
|
||||
val existing = session.getExistingDirectRoomWithUser(mxId)
|
||||
setState {
|
||||
copy(mode = UserCodeState.Mode.SHOW)
|
||||
}
|
||||
if (existing != null) {
|
||||
// navigate to this room
|
||||
_viewEvents.post(UserCodeShareViewEvents.NavigateToRoom(existing))
|
||||
} else {
|
||||
// we should create the room then navigate
|
||||
_viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
|
||||
?.isE2EByDefault()
|
||||
?: true
|
||||
|
||||
val roomParams = CreateRoomParams()
|
||||
.apply {
|
||||
invitedUserIds.add(mxId)
|
||||
setDirectMessage()
|
||||
enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
|
||||
}
|
||||
|
||||
val roomId =
|
||||
try {
|
||||
awaitCallback<String> { session.createRoom(roomParams, it) }
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.invite_users_to_room_failure)))
|
||||
return@launch
|
||||
} finally {
|
||||
_viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen)
|
||||
}
|
||||
_viewEvents.post(UserCodeShareViewEvents.NavigateToRoom(roomId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleQrCodeDecoded(action: UserCodeActions.DecodedQRCode) {
|
||||
val linkedId = PermalinkParser.parse(action.code)
|
||||
if (linkedId is PermalinkData.FallbackLink) {
|
||||
_viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_a_valid_qr_code)))
|
||||
return
|
||||
}
|
||||
_viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
when (linkedId) {
|
||||
is PermalinkData.RoomLink -> TODO()
|
||||
is PermalinkData.UserLink -> {
|
||||
val user = session.getUser(linkedId.userId) ?: awaitCallback<List<User>> {
|
||||
session.searchUsersDirectory(linkedId.userId, 10, emptySet(), it)
|
||||
}.firstOrNull { it.userId == linkedId.userId }
|
||||
// Create raw Uxid in case the user is not searchable
|
||||
?: User(linkedId.userId, null, null)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
mode = UserCodeState.Mode.RESULT(user.toMatrixItem())
|
||||
)
|
||||
}
|
||||
}
|
||||
is PermalinkData.GroupLink -> TODO()
|
||||
is PermalinkData.FallbackLink -> TODO()
|
||||
}
|
||||
_viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.usercode
|
||||
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
data class UserCodeState(
|
||||
val userId: String,
|
||||
val matrixItem: MatrixItem? = null,
|
||||
val shareLink: String? = null,
|
||||
val mode: Mode = Mode.SHOW
|
||||
) : MvRxState {
|
||||
sealed class Mode {
|
||||
object SHOW : Mode()
|
||||
object SCAN : Mode()
|
||||
data class RESULT(val matrixItem: MatrixItem) : Mode()
|
||||
}
|
||||
|
||||
constructor(args: UserCodeActivity.Args) : this(
|
||||
userId = args.userId
|
||||
)
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.utils.DebouncedClickListener
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_contact_action)
|
||||
abstract class ActionItem : VectorEpoxyModel<ActionItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute var title: CharSequence? = null
|
||||
@EpoxyAttribute @DrawableRes var actionIconRes: Int? = null
|
||||
@EpoxyAttribute var clickAction: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.view.setOnClickListener(clickAction?.let { DebouncedClickListener(it) })
|
||||
// If name is empty, use userId as name and force it being centered
|
||||
holder.actionTitleText.setTextOrHide(title)
|
||||
if (actionIconRes != null) {
|
||||
holder.actionTitleImageView.setImageResource(actionIconRes!!)
|
||||
} else {
|
||||
holder.actionTitleImageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val actionTitleText by bind<TextView>(R.id.actionTitleText)
|
||||
val actionTitleImageView by bind<ImageView>(R.id.actionIconImageView)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_contact_detail)
|
||||
abstract class ContactDetailItem : VectorEpoxyModel<ContactDetailItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var threePid: String
|
||||
@EpoxyAttribute var matrixId: String? = null
|
||||
@EpoxyAttribute var clickListener: ClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.view.onClick(clickListener)
|
||||
holder.nameView.text = threePid
|
||||
holder.matrixIdView.setTextOrHide(matrixId)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val nameView by bind<TextView>(R.id.contactDetailName)
|
||||
val matrixIdView by bind<TextView>(R.id.contactDetailMatrixId)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.contacts.MappedContact
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_contact_main)
|
||||
abstract class ContactItem : VectorEpoxyModel<ContactItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var mappedContact: MappedContact
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
// If name is empty, use userId as name and force it being centered
|
||||
holder.nameView.text = mappedContact.displayName
|
||||
avatarRenderer.render(mappedContact, holder.avatarImageView)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val nameView by bind<TextView>(R.id.contactDisplayName)
|
||||
val avatarImageView by bind<ImageView>(R.id.contactAvatar)
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.epoxy.noResultItem
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||
|
||||
private var state: UserDirectoryViewState? = null
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
init {
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun setData(state: UserDirectoryViewState) {
|
||||
this.state = state
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val currentState = state ?: return
|
||||
val hasSearch = currentState.directorySearchTerm.isNotBlank()
|
||||
when (val asyncUsers = currentState.directoryUsers) {
|
||||
is Uninitialized -> renderEmptyState(false)
|
||||
is Loading -> renderLoading()
|
||||
is Success -> renderSuccess(
|
||||
computeUsersList(asyncUsers(), currentState.directorySearchTerm),
|
||||
currentState.getSelectedMatrixId(),
|
||||
hasSearch
|
||||
)
|
||||
is Fail -> renderFailure(asyncUsers.error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eventually add the searched terms, if it is a userId, and if not already present in the result
|
||||
*/
|
||||
private fun computeUsersList(directoryUsers: List<User>, searchTerms: String): List<User> {
|
||||
return directoryUsers +
|
||||
searchTerms
|
||||
.takeIf { terms -> MatrixPatterns.isUserId(terms) && !directoryUsers.any { it.userId == terms } }
|
||||
?.let { listOf(User(it)) }
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
private fun renderLoading() {
|
||||
loadingItem {
|
||||
id("loading")
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderFailure(failure: Throwable) {
|
||||
errorWithRetryItem {
|
||||
id("error")
|
||||
text(errorFormatter.toHumanReadable(failure))
|
||||
listener { callback?.retryDirectoryUsersRequest() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSuccess(users: List<User>,
|
||||
selectedUsers: List<String>,
|
||||
hasSearch: Boolean) {
|
||||
if (users.isEmpty()) {
|
||||
renderEmptyState(hasSearch)
|
||||
} else {
|
||||
renderUsers(users, selectedUsers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderUsers(users: List<User>, selectedUsers: List<String>) {
|
||||
for (user in users) {
|
||||
if (user.userId == session.myUserId) {
|
||||
continue
|
||||
}
|
||||
val isSelected = selectedUsers.contains(user.userId)
|
||||
userDirectoryUserItem {
|
||||
id(user.userId)
|
||||
selected(isSelected)
|
||||
matrixItem(user.toMatrixItem())
|
||||
avatarRenderer(avatarRenderer)
|
||||
clickListener { _ ->
|
||||
callback?.onItemClick(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderEmptyState(hasSearch: Boolean) {
|
||||
val noResultRes = if (hasSearch) {
|
||||
R.string.no_result_placeholder
|
||||
} else {
|
||||
R.string.direct_room_start_search
|
||||
}
|
||||
noResultItem {
|
||||
id("noResult")
|
||||
text(stringProvider.getString(noResultRes))
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onItemClick(user: User)
|
||||
fun retryDirectoryUsersRequest()
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.EmptyItem_
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.epoxy.noResultItem
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.createUIHandler
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class KnownUsersController @Inject constructor(private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider) : PagedListEpoxyController<User>(
|
||||
modelBuildingHandler = createUIHandler()
|
||||
) {
|
||||
|
||||
private var selectedUsers: List<String> = emptyList()
|
||||
private var users: Async<List<User>> = Uninitialized
|
||||
private var isFiltering: Boolean = false
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
init {
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun setData(state: UserDirectoryViewState) {
|
||||
this.isFiltering = !state.filterKnownUsersValue.isEmpty()
|
||||
val newSelection = state.getSelectedMatrixId()
|
||||
this.users = state.knownUsers
|
||||
if (newSelection != selectedUsers) {
|
||||
this.selectedUsers = newSelection
|
||||
requestForcedModelBuild()
|
||||
}
|
||||
submitList(state.knownUsers())
|
||||
}
|
||||
|
||||
override fun buildItemModel(currentPosition: Int, item: User?): EpoxyModel<*> {
|
||||
return if (item == null) {
|
||||
EmptyItem_().id(currentPosition)
|
||||
} else {
|
||||
val isSelected = selectedUsers.contains(item.userId)
|
||||
UserDirectoryUserItem_()
|
||||
.id(item.userId)
|
||||
.selected(isSelected)
|
||||
.matrixItem(item.toMatrixItem())
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.clickListener { _ ->
|
||||
callback?.onItemClick(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||
if (users is Incomplete) {
|
||||
renderLoading()
|
||||
} else if (models.isEmpty()) {
|
||||
renderEmptyState()
|
||||
} else {
|
||||
var lastFirstLetter: String? = null
|
||||
for (model in models) {
|
||||
if (model is UserDirectoryUserItem) {
|
||||
if (model.matrixItem.id == session.myUserId) continue
|
||||
val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
|
||||
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
||||
lastFirstLetter = currentFirstLetter
|
||||
|
||||
UserDirectoryLetterHeaderItem_()
|
||||
.id(currentFirstLetter)
|
||||
.letter(currentFirstLetter)
|
||||
.addIf(showLetter, this)
|
||||
|
||||
model.addTo(this)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderLoading() {
|
||||
loadingItem {
|
||||
id("loading")
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderEmptyState() {
|
||||
noResultItem {
|
||||
id("noResult")
|
||||
text(stringProvider.getString(R.string.direct_room_no_known_users))
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onItemClick(user: User)
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.setupAsSearch
|
||||
import im.vector.app.core.extensions.showKeyboard
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import kotlinx.android.synthetic.main.fragment_user_directory.*
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserDirectoryFragment @Inject constructor(
|
||||
private val directRoomController: DirectoryUsersController
|
||||
) : VectorBaseFragment(), DirectoryUsersController.Callback {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_user_directory
|
||||
private val viewModel: UserDirectoryViewModel by activityViewModel()
|
||||
|
||||
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||
setupRecyclerView()
|
||||
setupSearchByMatrixIdView()
|
||||
setupCloseView()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
userDirectoryRecyclerView.cleanup()
|
||||
directRoomController.callback = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
directRoomController.callback = this
|
||||
userDirectoryRecyclerView.configureWith(directRoomController)
|
||||
}
|
||||
|
||||
private fun setupSearchByMatrixIdView() {
|
||||
userDirectorySearchById.setupAsSearch(searchIconRes = 0)
|
||||
userDirectorySearchById
|
||||
.textChanges()
|
||||
.subscribe {
|
||||
viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(it.toString()))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
userDirectorySearchById.showKeyboard(andRequestFocus = true)
|
||||
}
|
||||
|
||||
private fun setupCloseView() {
|
||||
userDirectoryClose.debouncedClicks {
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
directRoomController.setData(it)
|
||||
}
|
||||
|
||||
override fun onItemClick(user: User) {
|
||||
view?.hideKeyboard()
|
||||
viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||
}
|
||||
|
||||
override fun retryDirectoryUsersRequest() {
|
||||
val currentSearch = userDirectorySearchById.text.toString()
|
||||
viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(currentSearch))
|
||||
}
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.toggle
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.createdirect.CreateDirectRoomActivity
|
||||
import im.vector.app.features.invite.InviteUsersToRoomActivity
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private typealias KnowUsersFilter = String
|
||||
private typealias DirectoryUsersSearch = String
|
||||
|
||||
class UserDirectoryViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: UserDirectoryViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<UserDirectoryViewState, UserDirectoryAction, UserDirectoryViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: UserDirectoryViewState): UserDirectoryViewModel
|
||||
}
|
||||
|
||||
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
||||
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||
|
||||
companion object : MvRxViewModelFactory<UserDirectoryViewModel, UserDirectoryViewState> {
|
||||
|
||||
override fun create(viewModelContext: ViewModelContext, state: UserDirectoryViewState): UserDirectoryViewModel? {
|
||||
return when (viewModelContext) {
|
||||
is FragmentViewModelContext -> (viewModelContext.fragment() as KnownUsersFragment).userDirectoryViewModelFactory.create(state)
|
||||
is ActivityViewModelContext -> {
|
||||
when (viewModelContext.activity<FragmentActivity>()) {
|
||||
is CreateDirectRoomActivity -> viewModelContext.activity<CreateDirectRoomActivity>().userDirectoryViewModelFactory.create(state)
|
||||
is InviteUsersToRoomActivity -> viewModelContext.activity<InviteUsersToRoomActivity>().userDirectoryViewModelFactory.create(state)
|
||||
else -> error("Wrong activity or fragment")
|
||||
}
|
||||
}
|
||||
else -> error("Wrong activity or fragment")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
observeKnownUsers()
|
||||
observeDirectoryUsers()
|
||||
}
|
||||
|
||||
override fun handle(action: UserDirectoryAction) {
|
||||
when (action) {
|
||||
is UserDirectoryAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value))
|
||||
is UserDirectoryAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty())
|
||||
is UserDirectoryAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value)
|
||||
is UserDirectoryAction.SelectPendingInvitee -> handleSelectUser(action)
|
||||
is UserDirectoryAction.RemovePendingInvitee -> handleRemoveSelectedUser(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemovePendingInvitee) = withState { state ->
|
||||
val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee)
|
||||
setState {
|
||||
copy(
|
||||
pendingInvitees = selectedUsers,
|
||||
existingDmRoomId = getExistingDmRoomId(selectedUsers)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSelectUser(action: UserDirectoryAction.SelectPendingInvitee) = withState { state ->
|
||||
// Reset the filter asap
|
||||
directoryUsersSearch.accept("")
|
||||
val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee)
|
||||
setState {
|
||||
copy(
|
||||
pendingInvitees = selectedUsers,
|
||||
existingDmRoomId = getExistingDmRoomId(selectedUsers)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExistingDmRoomId(selectedUsers: Set<PendingInvitee>): String? {
|
||||
return selectedUsers
|
||||
.takeIf { it.size == 1 }
|
||||
?.filterIsInstance(PendingInvitee.UserPendingInvitee::class.java)
|
||||
?.firstOrNull()
|
||||
?.let { invitee -> session.getExistingDirectRoomWithUser(invitee.user.userId) }
|
||||
}
|
||||
|
||||
private fun observeDirectoryUsers() = withState { state ->
|
||||
directoryUsersSearch
|
||||
.debounce(300, TimeUnit.MILLISECONDS)
|
||||
.switchMapSingle { search ->
|
||||
val stream = if (search.isBlank()) {
|
||||
Single.just(emptyList())
|
||||
} else {
|
||||
session.rx()
|
||||
.searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet())
|
||||
.map { users ->
|
||||
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
||||
}
|
||||
}
|
||||
stream.toAsync {
|
||||
copy(directoryUsers = it, directorySearchTerm = search)
|
||||
}
|
||||
}
|
||||
.subscribe()
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeKnownUsers() = withState { state ->
|
||||
knownUsersFilter
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap {
|
||||
session.rx().livePagedUsers(it.orNull(), state.excludedUserIds)
|
||||
}
|
||||
.execute { async ->
|
||||
copy(
|
||||
knownUsers = async,
|
||||
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,10 +18,10 @@ package im.vector.app.features.userdirectory
|
|||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class UserDirectoryAction : VectorViewModelAction {
|
||||
data class FilterKnownUsers(val value: String) : UserDirectoryAction()
|
||||
data class SearchDirectoryUsers(val value: String) : UserDirectoryAction()
|
||||
object ClearFilterKnownUsers : UserDirectoryAction()
|
||||
data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction()
|
||||
data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction()
|
||||
sealed class UserListAction : VectorViewModelAction {
|
||||
data class SearchUsers(val value: String) : UserListAction()
|
||||
object ClearSearchUsers : UserListAction()
|
||||
data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction()
|
||||
data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction()
|
||||
object ComputeMatrixToLinkForSharing : UserListAction()
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.epoxy.noResultItem
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserListController @Inject constructor(private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||
|
||||
private var state: UserListViewState? = null
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
fun setData(state: UserListViewState) {
|
||||
this.state = state
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val currentState = state ?: return
|
||||
|
||||
// Build generic items
|
||||
if (currentState.searchTerm.isBlank()) {
|
||||
// For now we remove this option if in invite to existing room flow (and not create DM)
|
||||
if (currentState.pendingInvitees.isEmpty()
|
||||
// For now we remove this option if in invite to existing room flow (and not create DM)
|
||||
&& currentState.existingRoomId == null) {
|
||||
actionItem {
|
||||
id(R.drawable.ic_share)
|
||||
title(stringProvider.getString(R.string.invite_friends))
|
||||
actionIconRes(R.drawable.ic_share)
|
||||
clickAction(View.OnClickListener {
|
||||
callback?.onInviteFriendClick()
|
||||
})
|
||||
}
|
||||
}
|
||||
actionItem {
|
||||
id(R.drawable.ic_baseline_perm_contact_calendar_24)
|
||||
title(stringProvider.getString(R.string.contacts_book_title))
|
||||
actionIconRes(R.drawable.ic_baseline_perm_contact_calendar_24)
|
||||
clickAction(View.OnClickListener {
|
||||
callback?.onContactBookClick()
|
||||
})
|
||||
}
|
||||
if (currentState.pendingInvitees.isEmpty()
|
||||
// For now we remove this option if in invite to existing room flow (and not create DM)
|
||||
&& currentState.existingRoomId == null) {
|
||||
actionItem {
|
||||
id(R.drawable.ic_qr_code_add)
|
||||
title(stringProvider.getString(R.string.qr_code))
|
||||
actionIconRes(R.drawable.ic_qr_code_add)
|
||||
clickAction(View.OnClickListener {
|
||||
callback?.onUseQRCode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (currentState.knownUsers) {
|
||||
is Uninitialized -> renderEmptyState()
|
||||
is Loading -> renderLoading()
|
||||
is Fail -> renderFailure(currentState.knownUsers.error)
|
||||
is Success -> buildKnownUsers(currentState, currentState.getSelectedMatrixId())
|
||||
}
|
||||
|
||||
when (val asyncUsers = currentState.directoryUsers) {
|
||||
is Uninitialized -> {
|
||||
}
|
||||
is Loading -> renderLoading()
|
||||
is Fail -> renderFailure(asyncUsers.error)
|
||||
is Success -> buildDirectoryUsers(
|
||||
asyncUsers(),
|
||||
currentState.getSelectedMatrixId(),
|
||||
currentState.searchTerm,
|
||||
// to avoid showing twice same user in known and suggestions
|
||||
currentState.knownUsers.invoke()?.map { it.userId }.orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildKnownUsers(currentState: UserListViewState, selectedUsers: List<String>) {
|
||||
currentState.knownUsers()?.let { userList ->
|
||||
userListHeaderItem {
|
||||
id("known_header")
|
||||
header(stringProvider.getString(R.string.direct_room_user_list_known_title))
|
||||
}
|
||||
|
||||
if (userList.isEmpty()) {
|
||||
renderEmptyState()
|
||||
return
|
||||
}
|
||||
userList.forEach { item ->
|
||||
val isSelected = selectedUsers.contains(item.userId)
|
||||
userDirectoryUserItem {
|
||||
id(item.userId)
|
||||
selected(isSelected)
|
||||
matrixItem(item.toMatrixItem())
|
||||
avatarRenderer(avatarRenderer)
|
||||
clickListener { _ ->
|
||||
callback?.onItemClick(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDirectoryUsers(directoryUsers: List<User>, selectedUsers: List<String>, searchTerms: String, ignoreIds: List<String>) {
|
||||
val toDisplay = directoryUsers.filter { !ignoreIds.contains(it.userId) }
|
||||
if (toDisplay.isEmpty() && searchTerms.isBlank()) {
|
||||
return
|
||||
}
|
||||
userListHeaderItem {
|
||||
id("suggestions")
|
||||
header(stringProvider.getString(R.string.direct_room_user_list_suggestions_title))
|
||||
}
|
||||
if (toDisplay.isEmpty()) {
|
||||
renderEmptyState()
|
||||
} else {
|
||||
toDisplay.forEach { user ->
|
||||
if (user.userId != session.myUserId) {
|
||||
val isSelected = selectedUsers.contains(user.userId)
|
||||
userDirectoryUserItem {
|
||||
id(user.userId)
|
||||
selected(isSelected)
|
||||
matrixItem(user.toMatrixItem())
|
||||
avatarRenderer(avatarRenderer)
|
||||
clickListener { _ ->
|
||||
callback?.onItemClick(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderLoading() {
|
||||
loadingItem {
|
||||
id("loading")
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderEmptyState() {
|
||||
noResultItem {
|
||||
id("noResult")
|
||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderFailure(failure: Throwable) {
|
||||
errorWithRetryItem {
|
||||
id("error")
|
||||
text(errorFormatter.toHumanReadable(failure))
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onInviteFriendClick()
|
||||
fun onContactBookClick()
|
||||
fun onUseQRCode()
|
||||
fun onItemClick(user: User)
|
||||
fun onMatrixIdClick(matrixId: String)
|
||||
fun onThreePidClick(threePid: ThreePid)
|
||||
}
|
||||
}
|
|
@ -36,53 +36,64 @@ import im.vector.app.core.extensions.hideKeyboard
|
|||
import im.vector.app.core.extensions.setupAsSearch
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_known_users.*
|
||||
import kotlinx.android.synthetic.main.fragment_user_list.*
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import javax.inject.Inject
|
||||
|
||||
class KnownUsersFragment @Inject constructor(
|
||||
val userDirectoryViewModelFactory: UserDirectoryViewModel.Factory,
|
||||
private val knownUsersController: KnownUsersController,
|
||||
class UserListFragment @Inject constructor(
|
||||
private val userListController: UserListController,
|
||||
private val dimensionConverter: DimensionConverter,
|
||||
val homeServerCapabilitiesViewModelFactory: HomeServerCapabilitiesViewModel.Factory
|
||||
) : VectorBaseFragment(), KnownUsersController.Callback {
|
||||
) : VectorBaseFragment(), UserListController.Callback {
|
||||
|
||||
private val args: KnownUsersFragmentArgs by args()
|
||||
private val args: UserListFragmentArgs by args()
|
||||
private val viewModel: UserListViewModel by activityViewModel()
|
||||
private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel()
|
||||
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_known_users
|
||||
override fun getLayoutResId() = R.layout.fragment_user_list
|
||||
|
||||
override fun getMenuRes() = args.menuResId
|
||||
|
||||
private val viewModel: UserDirectoryViewModel by activityViewModel()
|
||||
private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel()
|
||||
|
||||
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||
sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||
userListTitle.text = args.title
|
||||
vectorBaseActivity.setSupportActionBar(userListToolbar)
|
||||
|
||||
knownUsersTitle.text = args.title
|
||||
vectorBaseActivity.setSupportActionBar(knownUsersToolbar)
|
||||
setupRecyclerView()
|
||||
setupFilterView()
|
||||
setupAddByMatrixIdView()
|
||||
setupAddFromPhoneBookView()
|
||||
setupSearchView()
|
||||
setupCloseView()
|
||||
|
||||
homeServerCapabilitiesViewModel.subscribe {
|
||||
knownUsersE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
|
||||
userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
|
||||
}
|
||||
|
||||
viewModel.selectSubscribe(this, UserDirectoryViewState::pendingInvitees) {
|
||||
viewModel.selectSubscribe(this, UserListViewState::pendingInvitees) {
|
||||
renderSelectedUsers(it)
|
||||
}
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is UserListViewEvents.OpenShareMatrixToLing -> {
|
||||
val text = getString(R.string.invite_friends_text, it.link)
|
||||
startSharePlainTextIntent(
|
||||
fragment = this,
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = getString(R.string.invite_friends),
|
||||
text = text,
|
||||
extraTitle = getString(R.string.invite_friends_rich_title)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
knownUsersController.callback = null
|
||||
knownUsersRecyclerView.cleanup()
|
||||
userListRecyclerView.cleanup()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
@ -91,69 +102,52 @@ class KnownUsersFragment @Inject constructor(
|
|||
val showMenuItem = it.pendingInvitees.isNotEmpty()
|
||||
menu.forEach { menuItem ->
|
||||
menuItem.isVisible = showMenuItem
|
||||
if (args.isCreatingRoom) {
|
||||
menuItem.setTitle(if (it.existingDmRoomId != null) R.string.action_open else R.string.create_room_action_create)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) {
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(
|
||||
item.itemId,
|
||||
it.pendingInvitees,
|
||||
it.existingDmRoomId
|
||||
))
|
||||
sharedActionViewModel.post(UserListSharedAction.OnMenuItemSelected(item.itemId, it.pendingInvitees))
|
||||
return@withState true
|
||||
}
|
||||
|
||||
private fun setupAddByMatrixIdView() {
|
||||
addByMatrixId.debouncedClicks {
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.OpenUsersDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAddFromPhoneBookView() {
|
||||
addFromPhoneBook.debouncedClicks {
|
||||
// TODO handle Permission first
|
||||
sharedActionViewModel.post(UserDirectorySharedAction.OpenPhoneBook)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
knownUsersController.callback = this
|
||||
userListController.callback = this
|
||||
// Don't activate animation as we might have way to much item animation when filtering
|
||||
knownUsersRecyclerView.configureWith(knownUsersController, disableItemAnimation = true)
|
||||
userListRecyclerView.configureWith(userListController, disableItemAnimation = true)
|
||||
}
|
||||
|
||||
private fun setupFilterView() {
|
||||
knownUsersFilter
|
||||
private fun setupSearchView() {
|
||||
withState(viewModel) {
|
||||
userListSearch.hint = getString(R.string.user_directory_search_hint)
|
||||
}
|
||||
userListSearch
|
||||
.textChanges()
|
||||
.startWith(knownUsersFilter.text)
|
||||
.startWith(userListSearch.text)
|
||||
.subscribe { text ->
|
||||
val filterValue = text.trim()
|
||||
val action = if (filterValue.isBlank()) {
|
||||
UserDirectoryAction.ClearFilterKnownUsers
|
||||
val searchValue = text.trim()
|
||||
val action = if (searchValue.isBlank()) {
|
||||
UserListAction.ClearSearchUsers
|
||||
} else {
|
||||
UserDirectoryAction.FilterKnownUsers(filterValue.toString())
|
||||
UserListAction.SearchUsers(searchValue.toString())
|
||||
}
|
||||
viewModel.handle(action)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
knownUsersFilter.setupAsSearch()
|
||||
knownUsersFilter.requestFocus()
|
||||
userListSearch.setupAsSearch()
|
||||
userListSearch.requestFocus()
|
||||
}
|
||||
|
||||
private fun setupCloseView() {
|
||||
knownUsersClose.debouncedClicks {
|
||||
userListClose.debouncedClicks {
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
knownUsersController.setData(it)
|
||||
userListController.setData(it)
|
||||
}
|
||||
|
||||
private fun renderSelectedUsers(invitees: Set<PendingInvitee>) {
|
||||
|
@ -183,12 +177,35 @@ class KnownUsersFragment @Inject constructor(
|
|||
chip.isCloseIconVisible = true
|
||||
chipGroup.addView(chip)
|
||||
chip.setOnCloseIconClickListener {
|
||||
viewModel.handle(UserDirectoryAction.RemovePendingInvitee(pendingInvitee))
|
||||
viewModel.handle(UserListAction.RemovePendingInvitee(pendingInvitee))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInviteFriendClick() {
|
||||
viewModel.handle(UserListAction.ComputeMatrixToLinkForSharing)
|
||||
}
|
||||
|
||||
override fun onContactBookClick() {
|
||||
sharedActionViewModel.post(UserListSharedAction.OpenPhoneBook)
|
||||
}
|
||||
|
||||
override fun onItemClick(user: User) {
|
||||
view?.hideKeyboard()
|
||||
viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
|
||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
|
||||
}
|
||||
|
||||
override fun onMatrixIdClick(matrixId: String) {
|
||||
view?.hideKeyboard()
|
||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
|
||||
}
|
||||
|
||||
override fun onThreePidClick(threePid: ThreePid) {
|
||||
view?.hideKeyboard()
|
||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
|
||||
}
|
||||
|
||||
override fun onUseQRCode() {
|
||||
view?.hideKeyboard()
|
||||
sharedActionViewModel.post(UserListSharedAction.AddByQrCode)
|
||||
}
|
||||
}
|
|
@ -20,9 +20,9 @@ import android.os.Parcelable
|
|||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class KnownUsersFragmentArgs(
|
||||
data class UserListFragmentArgs(
|
||||
val title: String,
|
||||
val menuResId: Int,
|
||||
val excludedUserIds: Set<String>? = null,
|
||||
val isCreatingRoom: Boolean = false
|
||||
val existingRoomId: String? = null
|
||||
) : Parcelable
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_user_list_header)
|
||||
abstract class UserListHeaderItem : VectorEpoxyModel<UserListHeaderItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute var header: String = ""
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.headerTextView.text = header
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val headerTextView by bind<TextView>(R.id.userListHeaderView)
|
||||
}
|
||||
}
|
|
@ -18,12 +18,10 @@ package im.vector.app.features.userdirectory
|
|||
|
||||
import im.vector.app.core.platform.VectorSharedAction
|
||||
|
||||
sealed class UserDirectorySharedAction : VectorSharedAction {
|
||||
object OpenUsersDirectory : UserDirectorySharedAction()
|
||||
object OpenPhoneBook : UserDirectorySharedAction()
|
||||
object Close : UserDirectorySharedAction()
|
||||
object GoBack : UserDirectorySharedAction()
|
||||
data class OnMenuItemSelected(val itemId: Int,
|
||||
val invitees: Set<PendingInvitee>,
|
||||
val existingDmRoomId: String?) : UserDirectorySharedAction()
|
||||
sealed class UserListSharedAction : VectorSharedAction {
|
||||
object Close : UserListSharedAction()
|
||||
object GoBack : UserListSharedAction()
|
||||
data class OnMenuItemSelected(val itemId: Int, val invitees: Set<PendingInvitee>) : UserListSharedAction()
|
||||
object OpenPhoneBook : UserListSharedAction()
|
||||
object AddByQrCode : UserListSharedAction()
|
||||
}
|
|
@ -19,4 +19,4 @@ package im.vector.app.features.userdirectory
|
|||
import im.vector.app.core.platform.VectorSharedActionViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserDirectorySharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<UserDirectorySharedAction>()
|
||||
class UserListSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<UserListSharedAction>()
|
|
@ -21,4 +21,6 @@ import im.vector.app.core.platform.VectorViewEvents
|
|||
/**
|
||||
* Transient events for invite users to room screen
|
||||
*/
|
||||
sealed class UserDirectoryViewEvents : VectorViewEvents
|
||||
sealed class UserListViewEvents : VectorViewEvents {
|
||||
data class OpenShareMatrixToLing(val link: String) : UserListViewEvents()
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.userdirectory
|
||||
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.toggle
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private typealias KnownUsersSearch = String
|
||||
private typealias DirectoryUsersSearch = String
|
||||
|
||||
class UserListViewModel @AssistedInject constructor(@Assisted initialState: UserListViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<UserListViewState, UserListAction, UserListViewEvents>(initialState) {
|
||||
|
||||
private val knownUsersSearch = BehaviorRelay.create<KnownUsersSearch>()
|
||||
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||
|
||||
private var currentUserSearchDisposable: Disposable? = null
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: UserListViewState): UserListViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<UserListViewModel, UserListViewState> {
|
||||
|
||||
override fun create(viewModelContext: ViewModelContext, state: UserListViewState): UserListViewModel? {
|
||||
val factory = when (viewModelContext) {
|
||||
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||
}
|
||||
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setState {
|
||||
copy(
|
||||
myUserId = session.myUserId,
|
||||
existingRoomId = initialState.existingRoomId
|
||||
)
|
||||
}
|
||||
observeUsers()
|
||||
}
|
||||
|
||||
override fun handle(action: UserListAction) {
|
||||
when (action) {
|
||||
is UserListAction.SearchUsers -> handleSearchUsers(action.value)
|
||||
is UserListAction.ClearSearchUsers -> handleClearSearchUsers()
|
||||
is UserListAction.SelectPendingInvitee -> handleSelectUser(action)
|
||||
is UserListAction.RemovePendingInvitee -> handleRemoveSelectedUser(action)
|
||||
UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleSearchUsers(searchTerm: String) {
|
||||
setState {
|
||||
copy(searchTerm = searchTerm)
|
||||
}
|
||||
knownUsersSearch.accept(searchTerm)
|
||||
directoryUsersSearch.accept(searchTerm)
|
||||
}
|
||||
|
||||
private fun handleShareMyMatrixToLink() {
|
||||
session.permalinkService().createPermalink(session.myUserId)?.let {
|
||||
_viewEvents.post(UserListViewEvents.OpenShareMatrixToLing(it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleClearSearchUsers() {
|
||||
knownUsersSearch.accept("")
|
||||
directoryUsersSearch.accept("")
|
||||
setState {
|
||||
copy(searchTerm = "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeUsers() = withState { state ->
|
||||
knownUsersSearch
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap {
|
||||
session.rx().livePagedUsers(it, state.excludedUserIds)
|
||||
}
|
||||
.execute { async ->
|
||||
copy(knownUsers = async)
|
||||
}
|
||||
|
||||
currentUserSearchDisposable?.dispose()
|
||||
|
||||
directoryUsersSearch
|
||||
.debounce(300, TimeUnit.MILLISECONDS)
|
||||
.switchMapSingle { search ->
|
||||
val stream = if (search.isBlank()) {
|
||||
Single.just(emptyList<User>())
|
||||
} else {
|
||||
val searchObservable = session.rx()
|
||||
.searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet())
|
||||
.map { users ->
|
||||
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
||||
}
|
||||
// If it's a valid user id try to use Profile API
|
||||
// because directory only returns users that are in public rooms or share a room with you, where as
|
||||
// profile will work other federations
|
||||
if (!MatrixPatterns.isUserId(search)) {
|
||||
searchObservable
|
||||
} else {
|
||||
val profileObservable = session.rx().getProfileInfo(search)
|
||||
.map { json ->
|
||||
User(
|
||||
userId = search,
|
||||
displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String,
|
||||
avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String
|
||||
).toOptional()
|
||||
}
|
||||
.onErrorReturn { Optional.empty() }
|
||||
|
||||
Single.zip(searchObservable, profileObservable, { searchResults, optionalProfile ->
|
||||
val profile = optionalProfile.getOrNull() ?: return@zip searchResults
|
||||
val searchContainsProfile = searchResults.indexOfFirst { it.userId == profile.userId } != -1
|
||||
if (searchContainsProfile) {
|
||||
searchResults
|
||||
} else {
|
||||
listOf(profile) + searchResults
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
stream.toAsync {
|
||||
copy(directoryUsers = it)
|
||||
}
|
||||
}
|
||||
.subscribe()
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun handleSelectUser(action: UserListAction.SelectPendingInvitee) = withState { state ->
|
||||
val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee)
|
||||
setState { copy(pendingInvitees = selectedUsers) }
|
||||
}
|
||||
|
||||
private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingInvitee) = withState { state ->
|
||||
val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee)
|
||||
setState { copy(pendingInvitees = selectedUsers) }
|
||||
}
|
||||
}
|
|
@ -17,30 +17,33 @@
|
|||
package im.vector.app.features.userdirectory
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.core.contacts.MappedContact
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
|
||||
data class UserDirectoryViewState(
|
||||
data class UserListViewState(
|
||||
val excludedUserIds: Set<String>? = null,
|
||||
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
||||
val directoryUsers: Async<List<User>> = Uninitialized,
|
||||
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
||||
val pendingInvitees: Set<PendingInvitee> = emptySet(),
|
||||
val createAndInviteState: Async<String> = Uninitialized,
|
||||
val directorySearchTerm: String = "",
|
||||
val filterKnownUsersValue: Option<String> = Option.empty(),
|
||||
val existingDmRoomId: String? = null
|
||||
val searchTerm: String = "",
|
||||
val myUserId: String = "",
|
||||
val existingRoomId: String? = null
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: KnownUsersFragmentArgs) : this(excludedUserIds = args.excludedUserIds)
|
||||
constructor(args: UserListFragmentArgs) : this(
|
||||
existingRoomId = args.existingRoomId
|
||||
)
|
||||
|
||||
fun getSelectedMatrixId(): List<String> {
|
||||
return pendingInvitees
|
||||
.mapNotNull {
|
||||
when (it) {
|
||||
is PendingInvitee.UserPendingInvitee -> it.user.userId
|
||||
is PendingInvitee.UserPendingInvitee -> it.user.userId
|
||||
is PendingInvitee.ThreePidPendingInvitee -> null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,18L6,18v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1z"/>
|
||||
</vector>
|
|
@ -0,0 +1,21 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M4,19.5C4,18.1193 5.1193,17 6.5,17H20"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M6.5,2H20V22H6.5C5.1193,22 4,20.8807 4,19.5V4.5C4,3.1193 5.1193,2 6.5,2Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="37dp"
|
||||
android:height="36dp"
|
||||
android:viewportWidth="37"
|
||||
android:viewportHeight="36">
|
||||
<path
|
||||
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M17.5911,26.2922L16.4715,24.6349L17.5911,26.2922ZM6,26.0007L4.7989,27.5999L4.7989,27.5999L6,26.0007ZM17.5634,23.7488L18.9544,25.1859L19.9234,24.2479L19.4171,22.998L17.5634,23.7488ZM6.4366,23.7488L4.5829,22.998L4.0766,24.2479L5.0456,25.1859L6.4366,23.7488ZM12,30C14.4825,30 16.7945,29.244 18.7107,27.9494L16.4715,24.6349C15.1957,25.4968 13.6596,26 12,26V30ZM4.7989,27.5999C6.8046,29.1065 9.3008,30 12,30V26C10.1967,26 8.538,25.4058 7.2011,24.4016L4.7989,27.5999ZM0,18C0,21.9273 1.8887,25.414 4.7989,27.5999L7.2011,24.4016C5.2535,22.9387 4,20.616 4,18H0ZM12,6C5.3726,6 0,11.3726 0,18H4C4,13.5817 7.5817,10 12,10V6ZM24,18C24,11.3726 18.6274,6 12,6V10C16.4183,10 20,13.5817 20,18H24ZM18.7107,27.9494C21.8977,25.7963 24,22.144 24,18H20C20,20.7596 18.6045,23.1939 16.4715,24.6349L18.7107,27.9494ZM13,15.25C13,16.0941 12.4046,16.5 12,16.5V20.5C14.9091,20.5 17,17.9958 17,15.25H13ZM12,14C12.4046,14 13,14.4059 13,15.25H17C17,12.5042 14.9091,10 12,10V14ZM11,15.25C11,14.4059 11.5954,14 12,14V10C9.0909,10 7,12.5042 7,15.25H11ZM12,16.5C11.5954,16.5 11,16.0941 11,15.25H7C7,17.9958 9.0909,20.5 12,20.5V16.5ZM16.1724,22.3118C15.0906,23.3588 13.6223,24 12,24V28C14.7017,28 17.1567,26.926 18.9544,25.1859L16.1724,22.3118ZM12,22C13.6752,22 15.1146,23.0305 15.7097,24.4996L19.4171,22.998C18.2314,20.0707 15.3599,18 12,18V22ZM8.2903,24.4996C8.8854,23.0305 10.3248,22 12,22V18C8.6401,18 5.7686,20.0707 4.5829,22.998L8.2903,24.4996ZM12,24C10.3777,24 8.9094,23.3588 7.8276,22.3118L5.0456,25.1859C6.8433,26.926 9.2983,28 12,28V24Z"
|
||||
android:fillColor="#000000"/>
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M27,18H35"
|
||||
android:strokeWidth="2.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M31,14L31,22"
|
||||
android:strokeWidth="2.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="37dp"
|
||||
android:height="36dp"
|
||||
android:viewportWidth="37"
|
||||
android:viewportHeight="36">
|
||||
<path
|
||||
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M17.5911,26.2922C15.9951,27.3704 14.0711,28 12,28C9.7488,28 7.6713,27.2561 6,26.0007C3.5711,24.1763 2,21.2716 2,18C2,12.4772 6.4771,8 12,8C17.5228,8 22,12.4772 22,18C22,21.4518 20.2511,24.4951 17.5911,26.2922ZM12,18.5C13.6569,18.5 15,17.0449 15,15.25C15,13.4551 13.6569,12 12,12C10.3431,12 9,13.4551 9,15.25C9,17.0449 10.3431,18.5 12,18.5ZM12,26C14.162,26 16.1236,25.1424 17.5634,23.7488C16.673,21.5506 14.5176,20 12,20C9.4824,20 7.327,21.5506 6.4366,23.7488C7.8763,25.1424 9.838,26 12,26Z"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M17.5911,26.2922L16.4715,24.6349L17.5911,26.2922ZM6,26.0007L4.7989,27.5999L4.7989,27.5999L6,26.0007ZM17.5634,23.7488L18.9544,25.1859L19.9234,24.2479L19.4171,22.998L17.5634,23.7488ZM6.4366,23.7488L4.5829,22.998L4.0766,24.2479L5.0456,25.1859L6.4366,23.7488ZM12,30C14.4825,30 16.7945,29.244 18.7107,27.9494L16.4715,24.6349C15.1957,25.4968 13.6596,26 12,26V30ZM4.7989,27.5999C6.8046,29.1065 9.3008,30 12,30V26C10.1967,26 8.538,25.4058 7.2011,24.4016L4.7989,27.5999ZM0,18C0,21.9273 1.8887,25.414 4.7989,27.5999L7.2011,24.4016C5.2535,22.9387 4,20.616 4,18H0ZM12,6C5.3726,6 0,11.3726 0,18H4C4,13.5817 7.5817,10 12,10V6ZM24,18C24,11.3726 18.6274,6 12,6V10C16.4183,10 20,13.5817 20,18H24ZM18.7107,27.9494C21.8977,25.7963 24,22.144 24,18H20C20,20.7596 18.6045,23.1939 16.4715,24.6349L18.7107,27.9494ZM13,15.25C13,16.0941 12.4046,16.5 12,16.5V20.5C14.9091,20.5 17,17.9958 17,15.25H13ZM12,14C12.4046,14 13,14.4059 13,15.25H17C17,12.5042 14.9091,10 12,10V14ZM11,15.25C11,14.4059 11.5954,14 12,14V10C9.0909,10 7,12.5042 7,15.25H11ZM12,16.5C11.5954,16.5 11,16.0941 11,15.25H7C7,17.9958 9.0909,20.5 12,20.5V16.5ZM16.1724,22.3118C15.0906,23.3588 13.6223,24 12,24V28C14.7017,28 17.1567,26.926 18.9544,25.1859L16.1724,22.3118ZM12,22C13.6752,22 15.1146,23.0305 15.7097,24.4996L19.4171,22.998C18.2314,20.0707 15.3599,18 12,18V22ZM8.2903,24.4996C8.8854,23.0305 10.3248,22 12,22V18C8.6401,18 5.7686,20.0707 4.5829,22.998L8.2903,24.4996ZM12,24C10.3777,24 8.9094,23.3588 7.8276,22.3118L5.0456,25.1859C6.8433,26.926 9.2983,28 12,28V24Z"
|
||||
android:fillColor="#000000"/>
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M27,18H35"
|
||||
android:strokeWidth="2.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M31,14L31,22"
|
||||
android:strokeWidth="2.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19.1001,9C18.7779,9 18.5168,8.7388 18.5168,8.4167V6.0833H16.1834C15.8613,6.0833 15.6001,5.8222 15.6001,5.5C15.6001,5.1778 15.8613,4.9167 16.1834,4.9167H18.5168V2.5833C18.5168,2.2612 18.7779,2 19.1001,2C19.4223,2 19.6834,2.2612 19.6834,2.5833V4.9167H22.0168C22.3389,4.9167 22.6001,5.1778 22.6001,5.5C22.6001,5.8222 22.3389,6.0833 22.0168,6.0833H19.6834V8.4167C19.6834,8.7388 19.4223,9 19.1001,9ZM19.6001,11C20.0669,11 20.5212,10.9467 20.9574,10.8458C21.1161,11.5383 21.2,12.2594 21.2,13C21.2,16.1409 19.6917,18.9294 17.3598,20.6808V20.6807C16.0014,21.7011 14.3635,22.3695 12.5815,22.5505C12.2588,22.5832 11.9314,22.6 11.6,22.6C6.2981,22.6 2,18.302 2,13C2,7.6981 6.2981,3.4 11.6,3.4C12.3407,3.4 13.0618,3.4839 13.7543,3.6427C13.6534,4.0788 13.6001,4.5332 13.6001,5C13.6001,8.3137 16.2864,11 19.6001,11ZM11.5999,20.68C13.6754,20.68 15.5585,19.8567 16.9407,18.5189C16.0859,16.4086 14.0167,14.92 11.5998,14.92C9.183,14.92 7.1138,16.4086 6.259,18.5189C7.6411,19.8567 9.5244,20.68 11.5999,20.68ZM11.7426,7.4117C10.3168,7.5417 9.2,8.7404 9.2,10.2C9.2,11.7464 10.4536,13 12,13C13.0308,13 13.9315,12.443 14.4176,11.6135C13.0673,10.6058 12.0929,9.1225 11.7426,7.4117Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -0,0 +1,29 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M3,5C3,3.8954 3.8954,3 5,3H19C20.1046,3 21,3.8954 21,5V19C21,20.1046 20.1046,21 19,21H5C3.8954,21 3,20.1046 3,19V5Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M8.5,10C9.3284,10 10,9.3284 10,8.5C10,7.6716 9.3284,7 8.5,7C7.6716,7 7,7.6716 7,8.5C7,9.3284 7.6716,10 8.5,10Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M21,15L16,10L5,21"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,36 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M15,3L20,3A1,1 0,0 1,21 4L21,9A1,1 0,0 1,20 10L15,10A1,1 0,0 1,14 9L14,4A1,1 0,0 1,15 3z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M14.25,16.5L20.75,16.5A0.25,0.25 0,0 1,21 16.75L21,18.25A0.25,0.25 0,0 1,20.75 18.5L14.25,18.5A0.25,0.25 0,0 1,14 18.25L14,16.75A0.25,0.25 0,0 1,14.25 16.5z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M18.5,14.25L18.5,20.75A0.25,0.25 0,0 1,18.25 21L16.75,21A0.25,0.25 0,0 1,16.5 20.75L16.5,14.25A0.25,0.25 0,0 1,16.75 14L18.25,14A0.25,0.25 0,0 1,18.5 14.25z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M4,14L9,14A1,1 0,0 1,10 15L10,20A1,1 0,0 1,9 21L4,21A1,1 0,0 1,3 20L3,15A1,1 0,0 1,4 14z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M4,3L9,3A1,1 0,0 1,10 4L10,9A1,1 0,0 1,9 10L4,10A1,1 0,0 1,3 9L3,4A1,1 0,0 1,4 3z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M5.75,5.5L7.25,5.5A0.25,0.25 0,0 1,7.5 5.75L7.5,7.25A0.25,0.25 0,0 1,7.25 7.5L5.75,7.5A0.25,0.25 0,0 1,5.5 7.25L5.5,5.75A0.25,0.25 0,0 1,5.75 5.5z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M5.75,16.5L7.25,16.5A0.25,0.25 0,0 1,7.5 16.75L7.5,18.25A0.25,0.25 0,0 1,7.25 18.5L5.75,18.5A0.25,0.25 0,0 1,5.5 18.25L5.5,16.75A0.25,0.25 0,0 1,5.75 16.5z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M16.75,5.5L18.25,5.5A0.25,0.25 0,0 1,18.5 5.75L18.5,7.25A0.25,0.25 0,0 1,18.25 7.5L16.75,7.5A0.25,0.25 0,0 1,16.5 7.25L16.5,5.75A0.25,0.25 0,0 1,16.75 5.5z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
|
@ -1,26 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/vector_coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<include layout="@layout/merge_overlay_waiting_view"/>
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<include layout="@layout/merge_overlay_waiting_view" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/vector_coordinator_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
@ -10,4 +10,21 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/simpleActivityWaitingView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/colorBackgroundFloating"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/matrixToCardAvatar"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:elevation="4dp"
|
||||
android:transitionName="profile"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/matrixToCardNameText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/matrixToCardAvatar"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/matrixToCardUserIdText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/matrixToCardNameText"
|
||||
tools:text="@sample/matrix.json/data/mxid" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/matrixToCardSendMessageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
|
||||
android:minWidth="130dp"
|
||||
android:text="@string/start_chatting"
|
||||
app:icon="@drawable/ic_fab_add_chat"
|
||||
app:iconTint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/matrixToCardUserIdText" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<im.vector.app.core.ui.views.QrCodeImageView
|
||||
android:id="@+id/itemShareQrCodeImage"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:contentDescription="@string/a11y_qr_code_for_verification"
|
||||
tools:src="@drawable/ic_qr_code_add" />
|
||||
|
||||
</FrameLayout>
|
|
@ -31,10 +31,11 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/homeDrawerHeaderAvatarView"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="24dp"
|
||||
android:transitionName="profile"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
@ -43,13 +44,13 @@
|
|||
android:id="@+id/homeDrawerUsernameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/homeDrawerQRCodeButton"
|
||||
app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeaderAvatarView"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
@ -58,18 +59,67 @@
|
|||
android:id="@+id/homeDrawerUserIdView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="17dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/homeDrawerInviteFriendButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/homeDrawerQRCodeButton"
|
||||
app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView"
|
||||
tools:text="@sample/matrix.json/data/mxid" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/homeDrawerQRCodeButton"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:backgroundTint="?riotx_bottom_nav_background_color"
|
||||
android:elevation="0dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
app:cornerRadius="17dp"
|
||||
app:icon="@drawable/ic_qr_code_add"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp"
|
||||
app:iconSize="20dp"
|
||||
app:iconTint="@color/riotx_accent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/homeDrawerUsernameView" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/homeDrawerInviteFriendButton"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
android:text="@string/invite_friends"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="13sp"
|
||||
app:icon="@drawable/ic_share"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="20dp"
|
||||
app:iconTint="?colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/homeDrawerUserIdView" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
|
|
|
@ -15,4 +15,28 @@
|
|||
|
||||
<!-- TODO In the future we could add a toggle to switch the flash, and other possible settings -->
|
||||
|
||||
<!-- TODO add take from album option.. -->
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/openAlbumButton"-->
|
||||
<!-- style="@style/Widget.MaterialComponents.Button.Icon"-->
|
||||
<!-- android:layout_width="34dp"-->
|
||||
<!-- android:layout_height="34dp"-->
|
||||
<!-- android:layout_marginEnd="@dimen/layout_horizontal_margin"-->
|
||||
<!-- android:layout_marginBottom="@dimen/layout_vertical_margin_big"-->
|
||||
<!-- android:backgroundTint="?riotx_bottom_nav_background_color"-->
|
||||
<!-- android:elevation="0dp"-->
|
||||
<!-- android:insetLeft="0dp"-->
|
||||
<!-- android:insetTop="0dp"-->
|
||||
<!-- android:insetRight="0dp"-->
|
||||
<!-- android:insetBottom="0dp"-->
|
||||
<!-- android:padding="0dp"-->
|
||||
<!-- app:cornerRadius="17dp"-->
|
||||
<!-- app:icon="@drawable/ic_picture_icon"-->
|
||||
<!-- app:iconGravity="textStart"-->
|
||||
<!-- app:iconPadding="0dp"-->
|
||||
<!-- app:iconSize="20dp"-->
|
||||
<!-- app:iconTint="@color/riotx_accent"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"/>-->
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,52 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
android:id="@+id/userCodeScannerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/userCodeMyCodeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
|
||||
android:maxWidth="160dp"
|
||||
android:text="@string/user_code_my_code"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/userCodeOpenGalleryButton"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:backgroundTint="?riotx_bottom_nav_background_color"
|
||||
android:elevation="0dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
app:cornerRadius="17dp"
|
||||
app:icon="@drawable/ic_picture_icon"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp"
|
||||
app:iconSize="20dp"
|
||||
app:iconTint="@color/riotx_accent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/userCodeMyCodeButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/userCodeMyCodeButton"
|
||||
app:layout_constraintTop_toTopOf="@id/userCodeMyCodeButton" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -14,12 +14,12 @@
|
|||
android:overScrollMode="always"
|
||||
tools:listitem="@layout/item_room" />
|
||||
|
||||
<im.vector.app.features.home.room.list.widget.FabMenuView
|
||||
<im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||
android:id="@+id/createChatFabMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
app:layoutDescription="@xml/motion_scene_fab_menu"
|
||||
app:layoutDescription="@xml/motion_scene_notifs_fab_menu"
|
||||
tools:showPaths="true"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/showUserCodeToolBar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/showUserCodeClose"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_x_18dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/showUserCodeTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/add_by_qr_code"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/showUserCodeClose"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/showUserCodeAvatar"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:elevation="4dp"
|
||||
android:transitionName="profile"
|
||||
app:layout_constraintBottom_toBottomOf="@id/showUserCodeCardTopBarrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/showUserCodeCardTopBarrier"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/showUserCodeCardTopBarrier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="showUserCodeCard" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/showUserCodeCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="50dp"
|
||||
android:padding="16dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/showUserCodeToolBar"
|
||||
app:layout_constraintWidth_percent="0.8">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="300dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/showUserCodeCardNameText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/showUserCodeCardUserIdText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/showUserCodeCardNameText"
|
||||
tools:text="@sample/matrix.json/data/mxid" />
|
||||
|
||||
|
||||
<!-- android:id="@+id/itemShareQrCodeImage"-->
|
||||
<!-- android:layout_width="300dp"-->
|
||||
<!-- android:layout_height="300dp"-->
|
||||
<!-- android:layout_gravity="center_horizontal"-->
|
||||
<!-- android:contentDescription="@string/a11y_qr_code_for_verification"-->
|
||||
<!-- tools:src="@color/riotx_header_panel_background_black" />-->
|
||||
|
||||
<im.vector.app.core.ui.views.QrCodeImageView
|
||||
android:id="@+id/showUserCodeQRImage"
|
||||
android:layout_width="260dp"
|
||||
android:layout_height="260dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/showUserCodeCardUserIdText"
|
||||
tools:src="@drawable/ic_qr_code_add" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/showUserCodeInfoText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/user_code_info_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="@id/showUserCodeCard"
|
||||
app:layout_constraintStart_toStartOf="@id/showUserCodeCard"
|
||||
app:layout_constraintTop_toBottomOf="@id/showUserCodeCard" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/showUserCodeShareButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:minWidth="130dp"
|
||||
android:text="@string/user_code_share"
|
||||
app:icon="@drawable/ic_share"
|
||||
app:iconTint="@color/white"
|
||||
app:layout_constraintBottom_toTopOf="@id/showUserCodeScanButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/showUserCodeInfoText"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/showUserCodeScanButton"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||
android:minWidth="130dp"
|
||||
android:text="@string/user_code_scan"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/showUserCodeShareButton"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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"
|
||||
|
@ -10,7 +10,7 @@
|
|||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/knownUsersToolbar"
|
||||
android:id="@+id/userListToolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="?actionBarSize"
|
||||
|
@ -24,7 +24,7 @@
|
|||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/knownUsersClose"
|
||||
android:id="@+id/userListClose"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:clickable="true"
|
||||
|
@ -37,7 +37,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/knownUsersTitle"
|
||||
android:id="@+id/userListTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
|
@ -51,7 +51,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/knownUsersClose"
|
||||
app:layout_constraintStart_toEndOf="@+id/userListClose"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -67,7 +67,7 @@
|
|||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/knownUsersToolbar"
|
||||
app:layout_constraintTop_toBottomOf="@+id/userListToolbar"
|
||||
app:maxHeight="64dp">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
|
@ -79,7 +79,7 @@
|
|||
</im.vector.app.core.platform.MaxHeightScrollView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/knownUsersFilter"
|
||||
android:id="@+id/userListSearch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
|
@ -87,28 +87,29 @@
|
|||
android:background="@null"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:hint="@string/direct_room_filter_hint"
|
||||
android:hint="@string/user_directory_search_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:maxHeight="80dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollView" />
|
||||
|
||||
<View
|
||||
android:id="@+id/knownUsersFilterDivider"
|
||||
android:id="@+id/userListFilterDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/vctr_list_divider_color"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/knownUsersFilter" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/userListSearch" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/knownUsersE2EbyDefaultDisabled"
|
||||
android:id="@+id/userListE2EbyDefaultDisabled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
|
@ -117,46 +118,11 @@
|
|||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider"
|
||||
app:layout_constraintTop_toBottomOf="@id/userListFilterDivider"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/addByMatrixId"
|
||||
style="@style/VectorButtonStyleText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:minHeight="@dimen/layout_touch_size"
|
||||
android:text="@string/add_by_matrix_id"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_plus_circle"
|
||||
app:iconPadding="13dp"
|
||||
app:iconTint="@color/riotx_accent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/knownUsersE2EbyDefaultDisabled" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/addFromPhoneBook"
|
||||
style="@style/VectorButtonStyleText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:minHeight="@dimen/layout_touch_size"
|
||||
android:text="@string/search_in_my_contacts"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_plus_circle"
|
||||
app:iconPadding="13dp"
|
||||
app:iconTint="@color/riotx_accent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/addByMatrixId" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/knownUsersRecyclerView"
|
||||
android:id="@+id/userListRecyclerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:fastScrollEnabled="true"
|
||||
|
@ -166,10 +132,8 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/addFromPhoneBook"
|
||||
app:layout_constraintTop_toBottomOf="@+id/userListE2EbyDefaultDisabled"
|
||||
tools:listitem="@layout/item_known_user" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.checkbox.MaterialCheckBox xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
|
||||
tools:text="@string/matrix_only_filter" />
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionIconImageView"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:padding="10dp"
|
||||
app:tint="?riotx_text_secondary"
|
||||
tools:src="@drawable/ic_invite_people" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionTitleText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/invite_friends" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/userListHeaderView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:padding="8dp"
|
||||
android:textColor="?attr/riotx_text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="normal"
|
||||
tools:text="Recents | Contacts" />
|
|
@ -11,6 +11,6 @@
|
|||
android:layout_height="200dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:contentDescription="@string/a11y_qr_code_for_verification"
|
||||
tools:src="@color/riotx_header_panel_background_black" />
|
||||
tools:src="@drawable/ic_qr_code_add" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutDescription="@xml/motion_scene_fab_menu"
|
||||
app:layoutDescription="@xml/motion_scene_notifs_fab_menu"
|
||||
tools:motionProgress="0.65"
|
||||
tools:parentTag="androidx.constraintlayout.motion.widget.MotionLayout"
|
||||
tools:showPaths="true">
|
|
@ -79,6 +79,7 @@
|
|||
<string name="pause_video">Pause</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="reset">Reset</string>
|
||||
<string name="start_chatting">Start Chatting</string>
|
||||
|
||||
|
||||
<!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video -->
|
||||
|
@ -424,6 +425,8 @@
|
|||
<string name="permissions_msg_contacts_warning_other_androids">Element can check your address book to find other Matrix users based on their email and phone numbers.\n\nDo you agree to share your address book for this purpose?</string>
|
||||
|
||||
<string name="permissions_action_not_performed_missing_permissions">Sorry. Action not performed, due to missing permissions</string>
|
||||
<string name="permissions_denied_qr_code">To scan a QR code, you need to allow camera access.</string>
|
||||
<string name="permissions_denied_add_contact">Allow permission to access your contacts.</string>
|
||||
|
||||
<!-- medias slider string -->
|
||||
<string name="media_slider_saved">Saved</string>
|
||||
|
@ -1754,6 +1757,7 @@
|
|||
<string name="room_filtering_footer_open_room_directory">View the room directory</string>
|
||||
|
||||
<string name="room_directory_search_hint">Name or ID (#example:matrix.org)</string>
|
||||
<string name="user_directory_search_hint">Search by name or ID</string>
|
||||
|
||||
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
|
||||
<string name="labs_show_unread_notifications_as_tab">Add a dedicated tab for unread notifications on main screen.</string>
|
||||
|
@ -1761,10 +1765,16 @@
|
|||
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
|
||||
|
||||
<string name="add_by_matrix_id">Add by matrix ID</string>
|
||||
<string name="add_by_qr_code">Add by QR code</string>
|
||||
<string name="qr_code">QR code</string>
|
||||
<string name="creating_direct_room">"Creating room…"</string>
|
||||
<string name="direct_room_no_known_users">"No result found, use Add by matrix ID to search on server."</string>
|
||||
<string name="direct_room_start_search">"Start typing to get results"</string>
|
||||
<string name="direct_room_filter_hint">"Filter by username or ID…"</string>
|
||||
<string name="direct_room_user_list_recent_title">Recent</string>
|
||||
<string name="direct_room_user_list_known_title">Known Users</string>
|
||||
<string name="direct_room_user_list_contacts_title">Contacts</string>
|
||||
<string name="direct_room_user_list_suggestions_title">Suggestions</string>
|
||||
|
||||
<string name="joining_room">"Joining room…"</string>
|
||||
|
||||
|
@ -1828,6 +1838,8 @@
|
|||
<string name="a11y_create_menu_open">Open the create room menu</string>
|
||||
<string name="a11y_create_menu_close">Close the create room menu…</string>
|
||||
<string name="a11y_create_direct_message">Create a new direct conversation</string>
|
||||
<string name="a11y_create_direct_message_by_mxid">Create a new direct conversation by Matrix ID</string>
|
||||
<string name="a11y_create_direct_message_by_qr_code">Create a new direct conversation by scanning a QR code</string>
|
||||
<string name="a11y_create_room">Create a new room</string>
|
||||
<string name="a11y_close_keys_backup_banner">Close keys backup banner</string>
|
||||
<string name="a11y_show_password">Show password</string>
|
||||
|
@ -2537,14 +2549,23 @@
|
|||
<string name="invite_users_to_room_action_invite">INVITE</string>
|
||||
<string name="inviting_users_to_room">Inviting users…</string>
|
||||
<string name="invite_users_to_room_title">Invite Users</string>
|
||||
<string name="invite_friends">Invite friends</string>
|
||||
<string name="invite_friends_text">Hey, talk to me on Element: %s</string>
|
||||
<string name="invite_friends_rich_title">🔐️ Join me on element</string>
|
||||
<string name="invitation_sent_to_one_user">Invitation sent to %1$s</string>
|
||||
<string name="invitations_sent_to_two_users">Invitations sent to %1$s and %2$s</string>
|
||||
<string name="not_a_valid_qr_code">"It's not a valid matrix QR code"</string>
|
||||
<plurals name="invitations_sent_to_one_and_more_users">
|
||||
<item quantity="one">Invitations sent to %1$s and one more</item>
|
||||
<item quantity="other">Invitations sent to %1$s and %2$d more</item>
|
||||
</plurals>
|
||||
<string name="invite_users_to_room_failure">We could not invite users. Please check the users you want to invite and try again.</string>
|
||||
|
||||
<string name="user_code_scan">Scan a QR code</string>
|
||||
<string name="user_code_share">Share my code</string>
|
||||
<string name="user_code_my_code">My code</string>
|
||||
<string name="user_code_info_text">Share this code with people so they can scan it to add you and start chatting.</string>
|
||||
|
||||
<string name="choose_locale_current_locale_title">Current language</string>
|
||||
<string name="choose_locale_other_locales_title">Other available languages</string>
|
||||
<string name="choose_locale_loading_locales">Loading available languages…</string>
|
||||
|
@ -2669,9 +2690,17 @@
|
|||
<string name="error_opening_banned_room">Can\'t open a room where you are banned from.</string>
|
||||
<string name="room_error_not_found">Can\'t find this room. Make sure it exists.</string>
|
||||
|
||||
<!-- Add by QR code -->
|
||||
<string name="share_by_text">Share by text</string>
|
||||
<string name="cannot_dm_self">Cannot DM yourself!</string>
|
||||
<string name="invalid_qr_code_uri">Invalid QR code (Invalid URI)!</string>
|
||||
<string name="qr_code_not_scanned">QR code not scanned!</string>
|
||||
|
||||
<!-- Universal link -->
|
||||
<string name="universal_link_malformed">The link was malformed</string>
|
||||
<string name="warning_room_not_created_yet">The room is not yet created. Cancel the room creation?</string>
|
||||
<string name="warning_unsaved_change">There are unsaved changes. Discard the changes?</string>
|
||||
<string name="warning_unsaved_change_discard">Discard changes</string>
|
||||
|
||||
<string name="matrix_to_card_title">Matrix Link</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
<resources>
|
||||
|
||||
<style name="VectorSnackBarStyle" parent="@style/Widget.MaterialComponents.Snackbar">
|
||||
<item name="android:background">@color/notification_accent_color</item>
|
||||
<!-- <item name="android:background">@color/notification_accent_color</item>-->
|
||||
</style>
|
||||
|
||||
<style name="VectorSnackBarButton" parent="@style/Widget.MaterialComponents.Button" />
|
||||
<style name="VectorSnackBarButton" parent="@style/Widget.MaterialComponents.Button.TextButton">
|
||||
<!-- <item name="android:textColor">@color/white</item>-->
|
||||
</style>
|
||||
|
||||
<style name="VectorSnackBarText" parent="@style/Widget.MaterialComponents.Snackbar.TextView">
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<!-- <item name="android:textColor">@color/white</item>-->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue