diff --git a/CHANGES.md b/CHANGES.md
index 4873a999b6..e1731433c3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,8 @@ 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)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
index 645fb55bb9..48705ee7b7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
@@ -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,
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index fb4764b3be..7d2ca11813 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -229,6 +229,7 @@
+
diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
index acdad5407c..32c98922fb 100644
--- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
@@ -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
}
diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
index fde40f9195..818a32fca3 100644
--- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
@@ -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
diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt
index 836dab00c5..7ae8bc9c2e 100644
--- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt
@@ -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
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt
new file mode 100644
index 0000000000..4e53b293d3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt
@@ -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.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
+import kotlinx.android.synthetic.main.vector_preference_push_rule.view.*
+
+@EpoxyModelClass(layout = R.layout.item_checkbox)
+abstract class CheckBoxItem : VectorEpoxyModel() {
+
+ @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(R.id.checkbox)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt
index 355dd8442f..05b70def3d 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt
@@ -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) {
diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
index d228adab12..2348b07c7b 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
@@ -136,13 +136,19 @@ fun startSharePlainTextIntent(fragment: Fragment,
activityResultLauncher: ActivityResultLauncher?,
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) {
diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
index 23d21f5240..6c3ec06f75 100644
--- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
@@ -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)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 2035ee50f6..2e21d04d06 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -45,23 +45,23 @@ import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
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
@@ -71,37 +71,36 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
injector.inject(this)
}
+ override fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel {
+ return userListViewModelFactory.create(initialState, args)
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toolbar.visibility = View.GONE
- sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
- if (intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
- if (isFirstCreation()) { openAddByQrCode() }
- } else {
- 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
- }
- .disposeOnDestroy()
- if (isFirstCreation()) {
- addFragment(
- R.id.container,
- KnownUsersFragment::class.java,
- KnownUsersFragmentArgs(
- title = getString(R.string.fab_menu_create_chat),
- menuResId = R.menu.vector_create_direct_room,
- isCreatingRoom = true
- )
- )
- }
+
+ sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
+ sharedActionViewModel
+ .observe()
+ .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,
+ UserListFragment::class.java,
+ UserListFragmentArgs(
+ title = getString(R.string.fab_menu_create_chat),
+ menuResId = R.menu.vector_create_direct_room
+ )
+ )
}
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
renderCreateAndInviteState(it)
@@ -129,22 +128,22 @@ 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 && intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
+ } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
}
} else {
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
- if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
+ if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
finish()
}
}
}
- 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
))
}
}
@@ -198,12 +197,9 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
}
companion object {
- private const val BY_QR_CODE = "BY_QR_CODE"
- fun getIntent(context: Context, byQrCode: Boolean = false): Intent {
- return Intent(context, CreateDirectRoomActivity::class.java).apply {
- putExtra(BY_QR_CODE, byQrCode)
- }
+ fun getIntent(context: Context): Intent {
+ return Intent(context, CreateDirectRoomActivity::class.java)
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
index f03368fdd5..3fee3a3285 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
@@ -21,7 +21,11 @@ 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
@@ -36,16 +40,32 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
override fun getLayoutResId() = R.layout.fragment_qr_code_scanner
- override fun onResume() {
- super.onResume()
- // Register ourselves as a handler for scan results.
- scannerView.setResultHandler(null)
+ 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()
}
@@ -73,23 +93,17 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
requireActivity().finish()
} else {
val existingDm = viewModel.session.getExistingDirectRoomWithUser(mxid)
-
- if (existingDm === null) {
- // 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)))
- )
- }
- } else {
- navigator.openRoom(requireContext(), existingDm, null, false)
+ // 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)
+ )
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
index e267248fc3..1a60d8e219 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
@@ -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 {
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt
index 58747a4c18..b695f48ee5 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt
@@ -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()
+class HomeSharedActionViewModel @Inject constructor(val session: Session) : VectorSharedActionViewModel()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
index e47072d0b0..60c2745b44 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
@@ -45,7 +45,6 @@ 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.DmsFabMenuView
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
import im.vector.app.features.notifications.NotificationDrawerManager
import kotlinx.android.parcel.Parcelize
@@ -67,7 +66,7 @@ class RoomListFragment @Inject constructor(
val roomListViewModelFactory: RoomListViewModel.Factory,
private val notificationDrawerManager: NotificationDrawerManager,
private val sharedViewPool: RecyclerView.RecycledViewPool
-) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, DmsFabMenuView.Listener, NotifsFabMenuView.Listener {
+) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, NotifsFabMenuView.Listener {
private var modelBuildListener: OnModelBuildFinishedListener? = null
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
@@ -111,7 +110,6 @@ class RoomListFragment @Inject constructor(
}.exhaustive
}
- createDmFabMenu.listener = this
createChatFabMenu.listener = this
sharedActionViewModel
@@ -130,7 +128,6 @@ class RoomListFragment @Inject constructor(
roomListView.cleanup()
roomController.listener = null
stateRestorer.clear()
- createDmFabMenu.listener = null
createChatFabMenu.listener = null
super.onDestroyView()
}
@@ -142,32 +139,33 @@ class RoomListFragment @Inject constructor(
private fun setupCreateRoomButton() {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true
- RoomListDisplayMode.PEOPLE -> createDmFabMenu.isVisible = true
+ RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode
}
+ createChatRoomButton.debouncedClicks {
+ createDirectChat()
+ }
createGroupRoomButton.debouncedClicks {
openRoomDirectory()
}
- // Hide FABs when list is scrolling
+ // Hide FAB when list is scrolling
roomListView.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
- createDmFabMenu.removeCallbacks(showFabRunnable)
createChatFabMenu.removeCallbacks(showFabRunnable)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
- createDmFabMenu.postDelayed(showFabRunnable, 250)
createChatFabMenu.postDelayed(showFabRunnable, 250)
}
RecyclerView.SCROLL_STATE_DRAGGING,
RecyclerView.SCROLL_STATE_SETTLING -> {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide()
- RoomListDisplayMode.PEOPLE -> createDmFabMenu.hide()
+ RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide()
else -> Unit
}
@@ -192,10 +190,6 @@ class RoomListFragment @Inject constructor(
navigator.openCreateDirectRoom(requireActivity())
}
- override fun createDirectChatByQrCode() {
- navigator.openCreateDirectRoom(requireContext(), true)
- }
-
private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context)
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
@@ -214,7 +208,7 @@ class RoomListFragment @Inject constructor(
if (isAdded) {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show()
- RoomListDisplayMode.PEOPLE -> createDmFabMenu.show()
+ RoomListDisplayMode.PEOPLE -> createChatRoomButton.show()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.show()
else -> Unit
}
@@ -343,9 +337,6 @@ class RoomListFragment @Inject constructor(
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
- if (createDmFabMenu.onBackPressed()) {
- return true
- }
if (createChatFabMenu.onBackPressed()) {
return true
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt b/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt
deleted file mode 100644
index 9659b7b12b..0000000000
--- a/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.home.room.list.widget
-
-import android.content.Context
-import android.util.AttributeSet
-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_dms_fab_menu_merge.view.*
-
-class DmsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
- defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) {
-
- var listener: Listener? = null
-
- init {
- inflate(context, R.layout.motion_dms_fab_menu_merge, this)
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
-
- listOf(createDmByMxid, createDmByMxidLabel)
- .forEach {
- it.setOnClickListener {
- closeFabMenu()
- listener?.createDirectChat()
- }
- }
- listOf(createDmByQrCode, createDmByQrCodeLabel)
- .forEach {
- it.setOnClickListener {
- closeFabMenu()
- listener?.createDirectChatByQrCode()
- }
- }
-
- dmsCreateRoomTouchGuard.setOnClickListener {
- closeFabMenu()
- }
- }
-
- override fun transitionToEnd() {
- super.transitionToEnd()
-
- dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_close)
- }
-
- override fun transitionToStart() {
- super.transitionToStart()
-
- dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_open)
- }
-
- fun show() {
- isVisible = true
- dmsCreateRoomButton.show()
- }
-
- fun hide() {
- dmsCreateRoomButton.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
- override fun onHidden(fab: FloatingActionButton?) {
- super.onHidden(fab)
- isVisible = false
- }
- })
- }
-
- private fun closeFabMenu() {
- transitionToStart()
- }
-
- fun onBackPressed(): Boolean {
- if (currentState == R.id.constraint_set_fab_menu_open) {
- closeFabMenu()
- return true
- }
-
- return false
- }
-
- interface Listener {
- fun createDirectChat()
- fun createDirectChatByQrCode()
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
index f6c60f6a6d..fe7a8006e0 100644
--- a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
@@ -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 {
@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)
}
diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
index 087b7c2f55..513fbb5d83 100644
--- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
@@ -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, args: UserListFragmentArgs): UserListViewModel {
+ return userListViewModelFactory.create(initialState, args)
+ }
+
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()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt
new file mode 100644
index 0000000000..3f3706699f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.fragment_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.fragment_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 {
+ const val ARGS = "MatrixToFragment.Args"
+
+ fun create(matrixItem: MatrixItem, listener: InteractionListener?): MatrixToBottomSheet {
+ return MatrixToBottomSheet(matrixItem).apply {
+ interactionListener = listener
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 9ff103113f..2d0ca86d52 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -203,8 +203,8 @@ class DefaultNavigator @Inject constructor(
context.startActivity(intent)
}
- override fun openCreateDirectRoom(context: Context, byQrCode: Boolean) {
- val intent = CreateDirectRoomActivity.getIntent(context, byQrCode)
+ override fun openCreateDirectRoom(context: Context) {
+ val intent = CreateDirectRoomActivity.getIntent(context)
context.startActivity(intent)
}
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index 23d24b709c..504fccb63a 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -56,7 +56,7 @@ interface Navigator {
fun openCreateRoom(context: Context, initialName: String = "")
- fun openCreateDirectRoom(context: Context, byQrCode: Boolean = false)
+ fun openCreateDirectRoom(context: Context)
fun openInviteUsersToRoom(context: Context, roomId: String)
diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt
index 2e91091443..e29c197ab8 100644
--- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt
+++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt
@@ -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) {
diff --git a/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt b/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt
new file mode 100644
index 0000000000..178a283d2c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt
@@ -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()
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt
new file mode 100644
index 0000000000..8b4820b06d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.app.Activity
+import android.os.Bundle
+import android.view.View
+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.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()
+ }
+ }
+
+ 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 onResume() {
+ super.onResume()
+ // Register ourselves as a handler for scan results.
+ userCodeScannerView.setResultHandler(this)
+ // Start camera on resume
+ if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
+ startCamera()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ // 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) {
+ 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
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt
new file mode 100644
index 0000000000..ab88f79bef
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.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()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ showUserCodeClose.debouncedClicks {
+ sharedViewModel.handle(UserCodeActions.DismissAction)
+ }
+ showUserCodeScanButton.debouncedClicks {
+ doOpenQRCodeScanner()
+ }
+ }
+
+ 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)
+ Unit
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt
new file mode 100644
index 0000000000..0611e0f8c3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt
@@ -0,0 +1,27 @@
+/*
+ * 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()
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
new file mode 100644
index 0000000000..388dc220a8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.exhaustive
+import im.vector.app.core.platform.VectorBaseActivity
+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) {
+ is UserCodeShareViewEvents.InviteFriend -> TODO()
+ 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)
+ }.exhaustive
+ }
+ }
+
+ private fun showFragment(fragmentClass: KClass, bundle: Bundle) {
+ if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
+ supportFragmentManager.beginTransaction().let {
+ it.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+ it.replace(R.id.simpleFragmentContainer,
+ fragmentClass.java,
+ bundle,
+ fragmentClass.simpleName
+ )
+ it.commit()
+ }
+ }
+ }
+
+ 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))
+ }
+ }
+
+ override fun create(initialState: UserCodeState, args: Args) =
+ viewModelFactory.create(initialState, args)
+
+ companion object {
+ fun newIntent(context: Context, userId: String): Intent {
+ return Intent(context, UserCodeActivity::class.java).apply {
+ putExtra(MvRx.KEY_ARG, Args(userId))
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt
new file mode 100644
index 0000000000..26fcffadd2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt
@@ -0,0 +1,28 @@
+/*
+ * 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 {
+ data class InviteFriend(val permalink: String) : UserCodeShareViewEvents()
+ object Dismiss : UserCodeShareViewEvents()
+ object ShowWaitingScreen : UserCodeShareViewEvents()
+ object HideWaitingScreen : UserCodeShareViewEvents()
+ data class ToastMessage(val message: String) : UserCodeShareViewEvents()
+ data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
new file mode 100644
index 0000000000..17dd97cffa
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
@@ -0,0 +1,161 @@
+/*
+ * 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,
+ @Assisted val args: UserCodeActivity.Args,
+ private val session: Session,
+ private val stringProvider: StringProvider,
+ private val rawService: RawService) : VectorViewModel(initialState) {
+
+ companion object : MvRxViewModelFactory {
+ override fun create(viewModelContext: ViewModelContext, state: UserCodeState): UserCodeSharedViewModel? {
+ val args = viewModelContext.args()
+ val factory = when (viewModelContext) {
+ is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+ is ActivityViewModelContext -> viewModelContext.activity as? Factory
+ }
+ return factory?.create(state, args) ?: error("You should let your activity/fragment implements Factory interface")
+ }
+
+ override fun initialState(viewModelContext: ViewModelContext): UserCodeState? {
+ return UserCodeState(viewModelContext.args().userId)
+ }
+ }
+
+ init {
+ val user = session.getUser(args.userId)
+ setState {
+ copy(
+ matrixItem = user?.toMatrixItem(),
+ shareLink = session.permalinkService().createPermalink(args.userId)
+ )
+ }
+ }
+
+ private fun handleInviteFriend() {
+ session.permalinkService().createPermalink(initialState.userId)?.let { permalink ->
+ _viewEvents.post(UserCodeShareViewEvents.InviteFriend(permalink))
+ }
+ }
+
+ @AssistedInject.Factory
+ interface Factory {
+ fun create(initialState: UserCodeState, args: UserCodeActivity.Args): 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)
+ }
+ }
+
+ 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 { 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 -> {
+ var user = session.getUser(linkedId.userId) ?: awaitCallback> {
+ 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)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt
new file mode 100644
index 0000000000..3be882af3d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt
@@ -0,0 +1,33 @@
+/*
+ * 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()
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt
new file mode 100644
index 0000000000..2307640634
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+@EpoxyModelClass(layout = R.layout.item_contact_action)
+abstract class ActionItem : VectorEpoxyModel() {
+
+ @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)
+ // 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(R.id.actionTitleText)
+ val actionTitleImageView by bind(R.id.actionIconImageView)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt
new file mode 100644
index 0000000000..ee96c34f45
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt
@@ -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() {
+
+ @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(R.id.contactDetailName)
+ val matrixIdView by bind(R.id.contactDetailMatrixId)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt
new file mode 100644
index 0000000000..d9f424d961
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt
@@ -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() {
+
+ @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(R.id.contactDisplayName)
+ val avatarImageView by bind(R.id.contactAvatar)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt
deleted file mode 100644
index e68d9855dd..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt
+++ /dev/null
@@ -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, searchTerms: String): List {
- 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,
- selectedUsers: List,
- hasSearch: Boolean) {
- if (users.isEmpty()) {
- renderEmptyState(hasSearch)
- } else {
- renderUsers(users, selectedUsers)
- }
- }
-
- private fun renderUsers(users: List, selectedUsers: List) {
- 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()
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt
deleted file mode 100644
index 4fbb9bbb41..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt
+++ /dev/null
@@ -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(
- modelBuildingHandler = createUIHandler()
-) {
-
- private var selectedUsers: List = emptyList()
- private var users: Async> = 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>) {
- 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)
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt
deleted file mode 100644
index 70ea9141e7..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt
+++ /dev/null
@@ -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))
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt
deleted file mode 100644
index 0a24b85ce2..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt
+++ /dev/null
@@ -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(initialState) {
-
- @AssistedInject.Factory
- interface Factory {
- fun create(initialState: UserDirectoryViewState): UserDirectoryViewModel
- }
-
- private val knownUsersFilter = BehaviorRelay.createDefault