mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Sharing: start extracting from RoomList as it's getting messy
This commit is contained in:
parent
b7a7aa2f15
commit
eccc52fe13
20 changed files with 531 additions and 349 deletions
|
@ -75,6 +75,7 @@ import im.vector.riotx.features.settings.crosssigning.CrossSigningSettingsFragme
|
|||
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
||||
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||
import im.vector.riotx.features.share.IncomingShareFragment
|
||||
import im.vector.riotx.features.signout.soft.SoftLogoutFragment
|
||||
|
||||
@Module
|
||||
|
@ -355,4 +356,9 @@ interface FragmentModule {
|
|||
@FragmentKey(AttachmentsPreviewFragment::class)
|
||||
fun bindAttachmentsPreviewFragment(fragment: AttachmentsPreviewFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(IncomingShareFragment::class)
|
||||
fun bindIncomingShareFragment(fragment: IncomingShareFragment): Fragment
|
||||
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
|||
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
||||
import im.vector.riotx.features.session.SessionListener
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import im.vector.riotx.features.share.ShareRoomListDataSource
|
||||
import im.vector.riotx.features.ui.UiStateRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -97,8 +96,6 @@ interface VectorComponent {
|
|||
|
||||
fun homeRoomListObservableStore(): HomeRoomListDataSource
|
||||
|
||||
fun shareRoomListObservableStore(): ShareRoomListDataSource
|
||||
|
||||
fun selectedGroupStore(): SelectedGroupDataSource
|
||||
|
||||
fun activeSessionObservableStore(): ActiveSessionDataSource
|
||||
|
|
|
@ -23,6 +23,5 @@ enum class RoomListDisplayMode(@StringRes val titleRes: Int) {
|
|||
HOME(R.string.bottom_action_home),
|
||||
PEOPLE(R.string.bottom_action_people_x),
|
||||
ROOMS(R.string.bottom_action_rooms),
|
||||
FILTERED(/* Not used */ 0),
|
||||
SHARE(/* Not used */ 0)
|
||||
FILTERED(/* Not used */ 0)
|
||||
}
|
||||
|
|
|
@ -16,20 +16,18 @@
|
|||
|
||||
package im.vector.riotx.features.home.room.list
|
||||
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
|
||||
sealed class RoomListAction : VectorViewModelAction {
|
||||
data class SelectRoom(val roomSummary: RoomSummary, val enableMultiSelect: Boolean) : RoomListAction()
|
||||
data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class ToggleCategory(val category: RoomCategory) : RoomListAction()
|
||||
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class FilterWith(val filter: String) : RoomListAction()
|
||||
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction()
|
||||
data class LeaveRoom(val roomId: String) : RoomListAction()
|
||||
data class ShareToSelectedRooms(val sharedData: SharedData, val optionalMessage: String? = null): RoomListAction()
|
||||
object MarkAllRoomsRead : RoomListAction()
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) :
|
|||
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
|
||||
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
|
||||
RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
|
||||
RoomListDisplayMode.SHARE -> roomSummary.membership == Membership.JOIN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,15 +50,13 @@ import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsShare
|
|||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.list.widget.FabMenuView
|
||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
data class RoomListParams(
|
||||
val displayMode: RoomListDisplayMode,
|
||||
val sharedData: SharedData? = null
|
||||
val displayMode: RoomListDisplayMode
|
||||
) : Parcelable
|
||||
|
||||
class RoomListFragment @Inject constructor(
|
||||
|
@ -110,11 +108,6 @@ class RoomListFragment @Inject constructor(
|
|||
}.exhaustive
|
||||
}
|
||||
|
||||
sendShareButton.setOnClickListener { _ ->
|
||||
roomListViewModel.handle(RoomListAction.ShareToSelectedRooms(roomListParams.sharedData!!))
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
createChatFabMenu.listener = this
|
||||
|
||||
sharedActionViewModel
|
||||
|
@ -137,19 +130,7 @@ class RoomListFragment @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleSelectRoom(event: RoomListViewEvents.SelectRoom) {
|
||||
if (roomListParams.displayMode == RoomListDisplayMode.SHARE) {
|
||||
val sharedData = roomListParams.sharedData ?: return
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.send_attachment)
|
||||
.setMessage(getString(R.string.share_confirm_room, event.roomSummary.displayName))
|
||||
.setPositiveButton(R.string.send) { _, _ ->
|
||||
navigator.openRoomForSharing(requireActivity(), event.roomSummary.roomId, sharedData)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
} else {
|
||||
navigator.openRoom(requireActivity(), event.roomSummary.roomId)
|
||||
}
|
||||
navigator.openRoom(requireActivity(), event.roomSummary.roomId)
|
||||
}
|
||||
|
||||
private fun setupCreateRoomButton() {
|
||||
|
@ -268,7 +249,6 @@ class RoomListFragment @Inject constructor(
|
|||
is Fail -> renderFailure(state.asyncFilteredRooms.error)
|
||||
}
|
||||
roomController.update(state)
|
||||
sendShareButton.isVisible = state.multiSelectionEnabled
|
||||
// Mark all as read menu
|
||||
when (roomListParams.displayMode) {
|
||||
RoomListDisplayMode.HOME,
|
||||
|
@ -356,18 +336,14 @@ class RoomListFragment @Inject constructor(
|
|||
// RoomSummaryController.Callback **************************************************************
|
||||
|
||||
override fun onRoomClicked(room: RoomSummary) {
|
||||
roomListViewModel.handle(RoomListAction.SelectRoom(room, enableMultiSelect = false))
|
||||
roomListViewModel.handle(RoomListAction.SelectRoom(room))
|
||||
}
|
||||
|
||||
override fun onRoomLongClicked(room: RoomSummary): Boolean {
|
||||
if (roomListParams.displayMode == RoomListDisplayMode.SHARE) {
|
||||
roomListViewModel.handle(RoomListAction.SelectRoom(room, enableMultiSelect = true))
|
||||
} else {
|
||||
roomController.onRoomLongClicked()
|
||||
RoomListQuickActionsBottomSheet
|
||||
.newInstance(room.roomId, RoomListActionsArgs.Mode.FULL)
|
||||
.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
|
||||
}
|
||||
roomController.onRoomLongClicked()
|
||||
RoomListQuickActionsBottomSheet
|
||||
.newInstance(room.roomId, RoomListActionsArgs.Mode.FULL)
|
||||
.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
|||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.DataSource
|
||||
import im.vector.riotx.features.home.RoomListDisplayMode
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
|
@ -69,38 +68,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
is RoomListAction.MarkAllRoomsRead -> handleMarkAllRoomsRead()
|
||||
is RoomListAction.LeaveRoom -> handleLeaveRoom(action)
|
||||
is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||
is RoomListAction.ShareToSelectedRooms -> handleShareToSelectedRooms(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleShareToSelectedRooms(action: RoomListAction.ShareToSelectedRooms) = withState {
|
||||
val sharedData = action.sharedData
|
||||
it.selectedRoomIds.forEach { roomId ->
|
||||
val room = session.getRoom(roomId)
|
||||
if (sharedData is SharedData.Text) {
|
||||
room?.sendTextMessage(sharedData.text)
|
||||
} else if (sharedData is SharedData.Attachments) {
|
||||
room?.sendMedias(sharedData.attachmentData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState {
|
||||
if (it.multiSelectionEnabled) {
|
||||
val selectedRooms = it.selectedRoomIds
|
||||
val newSelectedRooms = if (selectedRooms.contains(action.roomSummary.roomId)) {
|
||||
selectedRooms.minus(action.roomSummary.roomId)
|
||||
} else {
|
||||
selectedRooms.plus(action.roomSummary.roomId)
|
||||
}
|
||||
setState { copy(multiSelectionEnabled = newSelectedRooms.isNotEmpty(), selectedRoomIds = newSelectedRooms) }
|
||||
} else if (action.enableMultiSelect) {
|
||||
setState { copy(multiSelectionEnabled = true, selectedRoomIds = setOf(action.roomSummary.roomId)) }
|
||||
} else {
|
||||
_viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary))
|
||||
}
|
||||
_viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary))
|
||||
}
|
||||
|
||||
private fun handleToggleCategory(action: RoomListAction.ToggleCategory) = setState {
|
||||
|
@ -231,54 +205,35 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
}
|
||||
|
||||
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
||||
if (displayMode == RoomListDisplayMode.SHARE) {
|
||||
val recentRooms = ArrayList<RoomSummary>(20)
|
||||
val otherRooms = ArrayList<RoomSummary>(rooms.size)
|
||||
// Set up init size on directChats and groupRooms as they are the biggest ones
|
||||
val invites = ArrayList<RoomSummary>()
|
||||
val favourites = ArrayList<RoomSummary>()
|
||||
val directChats = ArrayList<RoomSummary>(rooms.size)
|
||||
val groupRooms = ArrayList<RoomSummary>(rooms.size)
|
||||
val lowPriorities = ArrayList<RoomSummary>()
|
||||
val serverNotices = ArrayList<RoomSummary>()
|
||||
|
||||
rooms
|
||||
.filter { roomListDisplayModeFilter.test(it) }
|
||||
.forEach { room ->
|
||||
when (room.breadcrumbsIndex) {
|
||||
RoomSummary.NOT_IN_BREADCRUMBS -> otherRooms.add(room)
|
||||
else -> recentRooms.add(room)
|
||||
}
|
||||
rooms
|
||||
.filter { roomListDisplayModeFilter.test(it) }
|
||||
.forEach { room ->
|
||||
val tags = room.tags.map { it.name }
|
||||
when {
|
||||
room.membership == Membership.INVITE -> invites.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
|
||||
room.isDirect -> directChats.add(room)
|
||||
else -> groupRooms.add(room)
|
||||
}
|
||||
}
|
||||
|
||||
return RoomSummaries().apply {
|
||||
put(RoomCategory.RECENT_ROOMS, recentRooms)
|
||||
put(RoomCategory.OTHER_ROOMS, otherRooms)
|
||||
}
|
||||
} else {
|
||||
// Set up init size on directChats and groupRooms as they are the biggest ones
|
||||
val invites = ArrayList<RoomSummary>()
|
||||
val favourites = ArrayList<RoomSummary>()
|
||||
val directChats = ArrayList<RoomSummary>(rooms.size)
|
||||
val groupRooms = ArrayList<RoomSummary>(rooms.size)
|
||||
val lowPriorities = ArrayList<RoomSummary>()
|
||||
val serverNotices = ArrayList<RoomSummary>()
|
||||
|
||||
rooms
|
||||
.filter { roomListDisplayModeFilter.test(it) }
|
||||
.forEach { room ->
|
||||
val tags = room.tags.map { it.name }
|
||||
when {
|
||||
room.membership == Membership.INVITE -> invites.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
|
||||
room.isDirect -> directChats.add(room)
|
||||
else -> groupRooms.add(room)
|
||||
}
|
||||
}
|
||||
|
||||
return RoomSummaries().apply {
|
||||
put(RoomCategory.INVITE, invites)
|
||||
put(RoomCategory.FAVOURITE, favourites)
|
||||
put(RoomCategory.DIRECT, directChats)
|
||||
put(RoomCategory.GROUP, groupRooms)
|
||||
put(RoomCategory.LOW_PRIORITY, lowPriorities)
|
||||
put(RoomCategory.SERVER_NOTICE, serverNotices)
|
||||
}
|
||||
return RoomSummaries().apply {
|
||||
put(RoomCategory.INVITE, invites)
|
||||
put(RoomCategory.FAVOURITE, favourites)
|
||||
put(RoomCategory.DIRECT, directChats)
|
||||
put(RoomCategory.GROUP, groupRooms)
|
||||
put(RoomCategory.LOW_PRIORITY, lowPriorities)
|
||||
put(RoomCategory.SERVER_NOTICE, serverNotices)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,21 +18,18 @@ package im.vector.riotx.features.home.room.list
|
|||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.riotx.features.home.HomeRoomListDataSource
|
||||
import im.vector.riotx.features.home.RoomListDisplayMode
|
||||
import im.vector.riotx.features.share.ShareRoomListDataSource
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
|
||||
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>,
|
||||
private val shareRoomListDataSource: Provider<ShareRoomListDataSource>)
|
||||
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>)
|
||||
: RoomListViewModel.Factory {
|
||||
|
||||
override fun create(initialState: RoomListViewState): RoomListViewModel {
|
||||
return RoomListViewModel(
|
||||
initialState,
|
||||
session.get(),
|
||||
if (initialState.displayMode == RoomListDisplayMode.SHARE) shareRoomListDataSource.get() else homeRoomListDataSource.get()
|
||||
homeRoomListDataSource.get()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,12 +43,7 @@ data class RoomListViewState(
|
|||
val isDirectRoomsExpanded: Boolean = true,
|
||||
val isGroupRoomsExpanded: Boolean = true,
|
||||
val isLowPriorityRoomsExpanded: Boolean = true,
|
||||
val isServerNoticeRoomsExpanded: Boolean = true,
|
||||
// For sharing
|
||||
val isRecentExpanded: Boolean = true,
|
||||
val isOtherExpanded: Boolean = true,
|
||||
val selectedRoomIds: Set<String> = emptySet(),
|
||||
val multiSelectionEnabled: Boolean = false
|
||||
val isServerNoticeRoomsExpanded: Boolean = true
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
|
||||
|
@ -61,8 +56,6 @@ data class RoomListViewState(
|
|||
RoomCategory.GROUP -> isGroupRoomsExpanded
|
||||
RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded
|
||||
RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded
|
||||
RoomCategory.RECENT_ROOMS -> isRecentExpanded
|
||||
RoomCategory.OTHER_ROOMS -> isOtherExpanded
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,8 +67,6 @@ data class RoomListViewState(
|
|||
RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded)
|
||||
RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded)
|
||||
RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded)
|
||||
RoomCategory.RECENT_ROOMS -> copy(isRecentExpanded = !isRecentExpanded)
|
||||
RoomCategory.OTHER_ROOMS -> copy(isOtherExpanded = !isOtherExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,11 +86,7 @@ enum class RoomCategory(@StringRes val titleRes: Int) {
|
|||
DIRECT(R.string.bottom_action_people_x),
|
||||
GROUP(R.string.bottom_action_rooms),
|
||||
LOW_PRIORITY(R.string.low_priority_header),
|
||||
SERVER_NOTICE(R.string.system_alerts_header),
|
||||
|
||||
// For Sharing
|
||||
RECENT_ROOMS(R.string.room_list_sharing_header_recent_rooms),
|
||||
OTHER_ROOMS(R.string.room_list_sharing_header_other_rooms)
|
||||
SERVER_NOTICE(R.string.system_alerts_header)
|
||||
}
|
||||
|
||||
fun RoomSummaries?.isNullOrEmpty(): Boolean {
|
||||
|
|
|
@ -60,7 +60,6 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
val nonNullViewState = viewState ?: return
|
||||
when (nonNullViewState.displayMode) {
|
||||
RoomListDisplayMode.FILTERED -> buildFilteredRooms(nonNullViewState)
|
||||
RoomListDisplayMode.SHARE -> buildShareRooms(nonNullViewState)
|
||||
else -> buildRooms(nonNullViewState)
|
||||
}
|
||||
}
|
||||
|
@ -78,44 +77,11 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
viewState.joiningErrorRoomsIds,
|
||||
viewState.rejectingRoomsIds,
|
||||
viewState.rejectingErrorRoomsIds,
|
||||
viewState.selectedRoomIds)
|
||||
emptySet())
|
||||
|
||||
addFilterFooter(viewState)
|
||||
}
|
||||
|
||||
private fun buildShareRooms(viewState: RoomListViewState) {
|
||||
var hasResult = false
|
||||
val roomSummaries = viewState.asyncFilteredRooms()
|
||||
roomListNameFilter.filter = viewState.roomFilter
|
||||
|
||||
roomSummaries?.forEach { (category, summaries) ->
|
||||
val filteredSummaries = summaries
|
||||
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
|
||||
|
||||
if (filteredSummaries.isEmpty()) {
|
||||
return@forEach
|
||||
} else {
|
||||
hasResult = true
|
||||
val isExpanded = viewState.isCategoryExpanded(category)
|
||||
buildRoomCategory(viewState, emptyList(), category.titleRes, viewState.isCategoryExpanded(category)) {
|
||||
listener?.onToggleRoomCategory(category)
|
||||
}
|
||||
if (isExpanded) {
|
||||
buildRoomModels(filteredSummaries,
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
viewState.selectedRoomIds
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasResult) {
|
||||
addNoResultItem()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRooms(viewState: RoomListViewState) {
|
||||
var showHelp = false
|
||||
val roomSummaries = viewState.asyncFilteredRooms()
|
||||
|
@ -133,7 +99,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
viewState.joiningErrorRoomsIds,
|
||||
viewState.rejectingRoomsIds,
|
||||
viewState.rejectingErrorRoomsIds,
|
||||
viewState.selectedRoomIds)
|
||||
emptySet())
|
||||
// Never set showHelp to true for invitation
|
||||
if (category != RoomCategory.INVITE) {
|
||||
showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp()
|
||||
|
@ -162,13 +128,6 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
}
|
||||
}
|
||||
|
||||
private fun addNoResultItem() {
|
||||
noResultItem {
|
||||
id("no_result")
|
||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRoomCategory(viewState: RoomListViewState,
|
||||
summaries: List<RoomSummary>,
|
||||
@StringRes titleRes: Int,
|
||||
|
|
|
@ -25,7 +25,6 @@ import im.vector.riotx.R
|
|||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.extensions.localDateTime
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.resources.DateProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||
|
@ -50,16 +49,16 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
return when (roomSummary.membership) {
|
||||
Membership.INVITE -> createInvitationItem(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener)
|
||||
else -> createRoomItem(roomSummary, selectedRoomIds, listener)
|
||||
else -> createRoomItem(roomSummary, selectedRoomIds, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked })
|
||||
}
|
||||
}
|
||||
|
||||
private fun createInvitationItem(roomSummary: RoomSummary,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
fun createInvitationItem(roomSummary: RoomSummary,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
val secondLine = if (roomSummary.isDirect) {
|
||||
roomSummary.latestPreviewableEvent?.root?.senderId
|
||||
} else {
|
||||
|
@ -82,7 +81,12 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||
.listener { listener?.onRoomClicked(roomSummary) }
|
||||
}
|
||||
|
||||
private fun createRoomItem(roomSummary: RoomSummary, selectedRoomIds: Set<String>, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
fun createRoomItem(
|
||||
roomSummary: RoomSummary,
|
||||
selectedRoomIds: Set<String>,
|
||||
onClick: ((RoomSummary) -> Unit)?,
|
||||
onLongClick: ((RoomSummary) -> Boolean)?
|
||||
): VectorEpoxyModel<*> {
|
||||
val unreadCount = roomSummary.notificationCount
|
||||
val showHighlighted = roomSummary.highlightCount > 0
|
||||
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
||||
|
@ -124,11 +128,11 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
||||
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
||||
.itemLongClickListener { _ ->
|
||||
listener?.onRoomLongClicked(roomSummary) ?: false
|
||||
onLongClick?.invoke(roomSummary) ?: false
|
||||
}
|
||||
.itemClickListener(
|
||||
DebouncedClickListener(View.OnClickListener { _ ->
|
||||
listener?.onRoomClicked(roomSummary)
|
||||
onClick?.invoke(roomSummary)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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.riotx.features.share
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class IncomingShareAction: VectorViewModelAction {
|
||||
data class SelectRoom(val roomSummary: RoomSummary, val enableMultiSelect: Boolean) : IncomingShareAction()
|
||||
object ShareToSelectedRooms: IncomingShareAction()
|
||||
data class FilterWith(val filter: String) : IncomingShareAction()
|
||||
data class UpdateSharedData(val sharedData: SharedData): IncomingShareAction()
|
||||
}
|
|
@ -9,127 +9,31 @@
|
|||
*
|
||||
* 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.
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.V
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.share
|
||||
|
||||
import android.content.ClipDescription
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Window
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.kbeanie.multipicker.utils.IntentUtils
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.extensions.replaceFragment
|
||||
import im.vector.riotx.core.extensions.addFragment
|
||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.features.attachments.AttachmentsHelper
|
||||
import im.vector.riotx.features.home.LoadingFragment
|
||||
import im.vector.riotx.features.home.RoomListDisplayMode
|
||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListParams
|
||||
import im.vector.riotx.features.login.LoginActivity
|
||||
import kotlinx.android.synthetic.main.activity_incoming_share.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class IncomingShareActivity :
|
||||
VectorBaseActivity(), AttachmentsHelper.Callback {
|
||||
class IncomingShareActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
|
||||
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
||||
@Inject lateinit var incomingShareViewModelFactory: IncomingShareViewModel.Factory
|
||||
private lateinit var attachmentsHelper: AttachmentsHelper
|
||||
// Do not remove, even if not used, it instantiates the view model
|
||||
@Suppress("unused")
|
||||
private val viewModel: IncomingShareViewModel by viewModel()
|
||||
private val roomListFragment: RoomListFragment?
|
||||
get() {
|
||||
return supportFragmentManager.findFragmentById(R.id.shareRoomListFragmentContainer) as? RoomListFragment
|
||||
}
|
||||
override fun getLayoutRes() = R.layout.activity_simple
|
||||
|
||||
override fun getLayoutRes() = R.layout.activity_incoming_share
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// If we are not logged in, stop the sharing process and open login screen.
|
||||
// In the future, we might want to relaunch the sharing process after login.
|
||||
if (!sessionHolder.hasActiveSession()) {
|
||||
startLoginActivity()
|
||||
return
|
||||
}
|
||||
configureToolbar(incomingShareToolbar)
|
||||
override fun initUiAndData() {
|
||||
if (isFirstCreation()) {
|
||||
replaceFragment(R.id.shareRoomListFragmentContainer, LoadingFragment::class.java)
|
||||
addFragment(R.id.simpleFragmentContainer, IncomingShareFragment::class.java)
|
||||
}
|
||||
attachmentsHelper = AttachmentsHelper.create(this, this).register()
|
||||
if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) {
|
||||
var isShareManaged = attachmentsHelper.handleShareIntent(
|
||||
IntentUtils.getPickerIntentForSharing(intent)
|
||||
)
|
||||
if (!isShareManaged) {
|
||||
isShareManaged = handleTextShare(intent)
|
||||
}
|
||||
if (!isShareManaged) {
|
||||
cannotManageShare()
|
||||
}
|
||||
} else {
|
||||
cannotManageShare()
|
||||
}
|
||||
|
||||
incomingShareSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
roomListFragment?.filterRoomsWith(newText)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
val roomListParams = RoomListParams(RoomListDisplayMode.SHARE, sharedData = SharedData.Attachments(attachments))
|
||||
replaceFragment(R.id.shareRoomListFragmentContainer, RoomListFragment::class.java, roomListParams)
|
||||
}
|
||||
|
||||
override fun onAttachmentsProcessFailed() {
|
||||
cannotManageShare()
|
||||
}
|
||||
|
||||
private fun cannotManageShare() {
|
||||
Toast.makeText(this, R.string.error_handling_incoming_share, Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun handleTextShare(intent: Intent): Boolean {
|
||||
if (intent.type == ClipDescription.MIMETYPE_TEXT_PLAIN) {
|
||||
val sharedText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString()
|
||||
return if (sharedText.isNullOrEmpty()) {
|
||||
false
|
||||
} else {
|
||||
val roomListParams = RoomListParams(RoomListDisplayMode.SHARE, sharedData = SharedData.Text(sharedText))
|
||||
replaceFragment(R.id.shareRoomListFragmentContainer, RoomListFragment::class.java, roomListParams)
|
||||
true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun startLoginActivity() {
|
||||
val intent = LoginActivity.newIntent(this, null)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
override fun configure(toolbar: Toolbar) {
|
||||
configureToolbar(toolbar, displayBack = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.riotx.features.share
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.loadingItem
|
||||
import im.vector.riotx.core.epoxy.noResultItem
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.home.room.list.RoomSummaryItemFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
class IncomingShareController @Inject constructor(private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||
private val stringProvider: StringProvider) : TypedEpoxyController<IncomingShareViewState>() {
|
||||
|
||||
interface Callback {
|
||||
fun onRoomClicked(roomSummary: RoomSummary)
|
||||
fun onRoomLongClicked(roomSummary: RoomSummary): Boolean
|
||||
}
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
override fun buildModels(data: IncomingShareViewState) {
|
||||
if (data.sharedData == null || data.filteredRoomSummaries is Incomplete) {
|
||||
loadingItem {
|
||||
id("loading")
|
||||
}
|
||||
return
|
||||
}
|
||||
val roomSummaries = data.filteredRoomSummaries()
|
||||
if (roomSummaries.isNullOrEmpty()) {
|
||||
noResultItem {
|
||||
id("no_result")
|
||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
||||
}
|
||||
} else {
|
||||
roomSummaries.forEach { roomSummary ->
|
||||
roomSummaryItemFactory
|
||||
.createRoomItem(roomSummary, data.selectedRoomIds, callback?.let { it::onRoomClicked }, callback?.let { it::onRoomLongClicked })
|
||||
.addTo(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* 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.riotx.features.share
|
||||
|
||||
import android.content.ClipDescription
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.kbeanie.multipicker.utils.IntentUtils
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.attachments.AttachmentsHelper
|
||||
import im.vector.riotx.features.login.LoginActivity
|
||||
import kotlinx.android.synthetic.main.fragment_incoming_share.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class IncomingShareFragment @Inject constructor(
|
||||
val incomingShareViewModelFactory: IncomingShareViewModel.Factory,
|
||||
private val incomingShareController: IncomingShareController,
|
||||
private val sessionHolder: ActiveSessionHolder
|
||||
) : VectorBaseFragment(), AttachmentsHelper.Callback, IncomingShareController.Callback {
|
||||
|
||||
private lateinit var attachmentsHelper: AttachmentsHelper
|
||||
private val incomingShareViewModel: IncomingShareViewModel by fragmentViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_incoming_share
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// If we are not logged in, stop the sharing process and open login screen.
|
||||
// In the future, we might want to relaunch the sharing process after login.
|
||||
if (!sessionHolder.hasActiveSession()) {
|
||||
startLoginActivity()
|
||||
return
|
||||
}
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupRecyclerView()
|
||||
setupToolbar(incomingShareToolbar)
|
||||
attachmentsHelper = AttachmentsHelper.create(this, this).register()
|
||||
|
||||
val intent = vectorBaseActivity.intent
|
||||
if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) {
|
||||
var isShareManaged = attachmentsHelper.handleShareIntent(
|
||||
IntentUtils.getPickerIntentForSharing(intent)
|
||||
)
|
||||
if (!isShareManaged) {
|
||||
isShareManaged = handleTextShare(intent)
|
||||
}
|
||||
if (!isShareManaged) {
|
||||
cannotManageShare()
|
||||
}
|
||||
} else {
|
||||
cannotManageShare()
|
||||
}
|
||||
|
||||
incomingShareSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
incomingShareViewModel.handle(IncomingShareAction.FilterWith(newText))
|
||||
return true
|
||||
}
|
||||
})
|
||||
sendShareButton.setOnClickListener { _ ->
|
||||
handleSendShare()
|
||||
}
|
||||
incomingShareViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is IncomingShareViewEvents.ShareToRoom -> handleShareToRoom(it)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShareToRoom(event: IncomingShareViewEvents.ShareToRoom) {
|
||||
if (event.showAlert) {
|
||||
showConfirmationDialog(event.roomSummary, event.sharedData)
|
||||
} else {
|
||||
navigator.openRoomForSharing(requireActivity(), event.roomSummary.roomId, event.sharedData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendShare() {
|
||||
incomingShareViewModel.handle(IncomingShareAction.ShareToSelectedRooms)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
incomingShareController.callback = null
|
||||
incomingShareRoomList.cleanup()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
incomingShareRoomList.configureWith(incomingShareController, hasFixedSize = true)
|
||||
incomingShareController.callback = this
|
||||
}
|
||||
|
||||
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
val sharedData = SharedData.Attachments(attachments)
|
||||
incomingShareViewModel.handle(IncomingShareAction.UpdateSharedData(sharedData))
|
||||
}
|
||||
|
||||
override fun onAttachmentsProcessFailed() {
|
||||
cannotManageShare()
|
||||
}
|
||||
|
||||
private fun cannotManageShare() {
|
||||
Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_LONG).show()
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
private fun handleTextShare(intent: Intent): Boolean {
|
||||
if (intent.type == ClipDescription.MIMETYPE_TEXT_PLAIN) {
|
||||
val sharedText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString()
|
||||
return if (sharedText.isNullOrEmpty()) {
|
||||
false
|
||||
} else {
|
||||
val sharedData = SharedData.Text(sharedText)
|
||||
incomingShareViewModel.handle(IncomingShareAction.UpdateSharedData(sharedData))
|
||||
true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun showConfirmationDialog(roomSummary: RoomSummary, sharedData: SharedData) {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.send_attachment)
|
||||
.setMessage(getString(R.string.share_confirm_room, roomSummary.displayName))
|
||||
.setPositiveButton(R.string.send) { _, _ ->
|
||||
navigator.openRoomForSharing(requireActivity(), roomSummary.roomId, sharedData)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun startLoginActivity() {
|
||||
val intent = LoginActivity.newIntent(requireActivity(), null)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(incomingShareViewModel) {
|
||||
sendShareButton.isVisible = it.multiSelectionEnabled
|
||||
incomingShareController.setData(it)
|
||||
}
|
||||
|
||||
override fun onRoomClicked(roomSummary: RoomSummary) {
|
||||
incomingShareViewModel.handle(IncomingShareAction.SelectRoom(roomSummary, false))
|
||||
}
|
||||
|
||||
override fun onRoomLongClicked(roomSummary: RoomSummary): Boolean {
|
||||
incomingShareViewModel.handle(IncomingShareAction.SelectRoom(roomSummary, true))
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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
|
||||
* 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,
|
||||
|
@ -17,9 +17,8 @@
|
|||
package im.vector.riotx.features.share
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.core.utils.BehaviorDataSource
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
@Singleton
|
||||
class ShareRoomListDataSource @Inject constructor() : BehaviorDataSource<List<RoomSummary>>()
|
||||
sealed class IncomingShareViewEvents : VectorViewEvents {
|
||||
data class ShareToRoom(val roomSummary: RoomSummary, val sharedData: SharedData, val showAlert: Boolean) : IncomingShareViewEvents()
|
||||
}
|
|
@ -16,71 +16,127 @@
|
|||
|
||||
package im.vector.riotx.features.share
|
||||
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
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.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.ActiveSessionDataSource
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.home.room.list.BreadcrumbsRoomComparator
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import im.vector.riotx.features.home.room.list.ChronologicalRoomComparator
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
data class IncomingShareState(private val dummy: Boolean = false) : MvRxState
|
||||
|
||||
/**
|
||||
* View model used to observe the room list and post update to the ShareRoomListObservableStore
|
||||
*/
|
||||
class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState,
|
||||
private val sessionObservableStore: ActiveSessionDataSource,
|
||||
private val shareRoomListObservableStore: ShareRoomListDataSource,
|
||||
private val breadcrumbsRoomComparator: BreadcrumbsRoomComparator)
|
||||
: VectorViewModel<IncomingShareState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareViewState,
|
||||
private val session: Session,
|
||||
private val chronologicalRoomComparator: ChronologicalRoomComparator)
|
||||
: VectorViewModel<IncomingShareViewState, IncomingShareAction, IncomingShareViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: IncomingShareState): IncomingShareViewModel
|
||||
fun create(initialState: IncomingShareViewState): IncomingShareViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<IncomingShareViewModel, IncomingShareState> {
|
||||
companion object : MvRxViewModelFactory<IncomingShareViewModel, IncomingShareViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: IncomingShareState): IncomingShareViewModel? {
|
||||
val activity: IncomingShareActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||
return activity.incomingShareViewModelFactory.create(state)
|
||||
override fun create(viewModelContext: ViewModelContext, state: IncomingShareViewState): IncomingShareViewModel? {
|
||||
val fragment: IncomingShareFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.incomingShareViewModelFactory.create(state)
|
||||
}
|
||||
}
|
||||
|
||||
private val filterStream: BehaviorRelay<String> = BehaviorRelay.createDefault("")
|
||||
|
||||
init {
|
||||
observeRoomSummaries()
|
||||
}
|
||||
|
||||
private fun observeRoomSummaries() {
|
||||
val queryParams = roomSummaryQueryParams()
|
||||
sessionObservableStore.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap {
|
||||
it.orNull()?.rx()?.liveRoomSummaries(queryParams)
|
||||
?: Observable.just(emptyList())
|
||||
val queryParams = roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
}
|
||||
session
|
||||
.rx().liveRoomSummaries(queryParams)
|
||||
.execute {
|
||||
copy(roomSummaries = it)
|
||||
}
|
||||
|
||||
filterStream
|
||||
.switchMap { filter ->
|
||||
val displayNameQuery = if (filter.isEmpty()) {
|
||||
QueryStringValue.NoCondition
|
||||
} else {
|
||||
QueryStringValue.Contains(filter, QueryStringValue.Case.INSENSITIVE)
|
||||
}
|
||||
val filterQueryParams = roomSummaryQueryParams {
|
||||
displayName = displayNameQuery
|
||||
memberships = listOf(Membership.JOIN)
|
||||
}
|
||||
session.rx().liveRoomSummaries(filterQueryParams)
|
||||
}
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||
.map {
|
||||
it.sortedWith(breadcrumbsRoomComparator)
|
||||
.map { it.sortedWith(chronologicalRoomComparator) }
|
||||
.execute {
|
||||
copy(filteredRoomSummaries = it)
|
||||
}
|
||||
.subscribe {
|
||||
shareRoomListObservableStore.post(it)
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {
|
||||
// No op
|
||||
override fun handle(action: IncomingShareAction) {
|
||||
when (action) {
|
||||
is IncomingShareAction.SelectRoom -> handleSelectRoom(action)
|
||||
is IncomingShareAction.ShareToSelectedRooms -> handleShareToSelectedRooms()
|
||||
is IncomingShareAction.FilterWith -> handleFilter(action)
|
||||
is IncomingShareAction.UpdateSharedData -> handleUpdateSharedData(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleUpdateSharedData(action: IncomingShareAction.UpdateSharedData) {
|
||||
setState { copy(sharedData = action.sharedData) }
|
||||
}
|
||||
|
||||
private fun handleFilter(action: IncomingShareAction.FilterWith) {
|
||||
filterStream.accept(action.filter)
|
||||
}
|
||||
|
||||
private fun handleShareToSelectedRooms() = withState { state ->
|
||||
val sharedData = state.sharedData ?: return@withState
|
||||
if (state.selectedRoomIds.size == 1) {
|
||||
val selectedRoomId = state.selectedRoomIds.first()
|
||||
val selectedRoom = state.roomSummaries()?.find { it.roomId == selectedRoomId } ?: return@withState
|
||||
_viewEvents.post(IncomingShareViewEvents.ShareToRoom(selectedRoom, sharedData, showAlert = false))
|
||||
} else {
|
||||
state.selectedRoomIds.forEach { roomId ->
|
||||
val room = session.getRoom(roomId)
|
||||
if (sharedData is SharedData.Text) {
|
||||
room?.sendTextMessage(sharedData.text)
|
||||
} else if (sharedData is SharedData.Attachments) {
|
||||
room?.sendMedias(sharedData.attachmentData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSelectRoom(action: IncomingShareAction.SelectRoom) = withState {
|
||||
if (it.multiSelectionEnabled) {
|
||||
val selectedRooms = it.selectedRoomIds
|
||||
val newSelectedRooms = if (selectedRooms.contains(action.roomSummary.roomId)) {
|
||||
selectedRooms.minus(action.roomSummary.roomId)
|
||||
} else {
|
||||
selectedRooms.plus(action.roomSummary.roomId)
|
||||
}
|
||||
setState { copy(multiSelectionEnabled = newSelectedRooms.isNotEmpty(), selectedRoomIds = newSelectedRooms) }
|
||||
} else if (action.enableMultiSelect) {
|
||||
setState { copy(multiSelectionEnabled = true, selectedRoomIds = setOf(action.roomSummary.roomId)) }
|
||||
} else {
|
||||
val sharedData = it.sharedData ?: return@withState
|
||||
_viewEvents.post(IncomingShareViewEvents.ShareToRoom(action.roomSummary, sharedData, showAlert = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.riotx.features.share
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
|
||||
data class IncomingShareViewState(
|
||||
val sharedData: SharedData? = null,
|
||||
val roomSummaries: Async<List<RoomSummary>> = Uninitialized,
|
||||
val filteredRoomSummaries: Async<List<RoomSummary>> = Uninitialized,
|
||||
val selectedRoomIds: Set<String> = emptySet(),
|
||||
val multiSelectionEnabled: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
|
57
vector/src/main/res/layout/fragment_incoming_share.xml
Normal file
57
vector/src/main/res/layout/fragment_incoming_share.xml
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/incomingShareToolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:contentInsetStart="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/incomingShareSearchView"
|
||||
style="@style/VectorSearchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:queryHint="@string/room_filtering_filter_hint"
|
||||
app:searchIcon="@drawable/ic_filter" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/incomingShareRoomList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/incomingShareToolbar" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/sendShareButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:accessibilityTraversalBefore="@id/incomingShareRoomList"
|
||||
android:contentDescription="@string/a11y_create_room"
|
||||
android:src="@drawable/ic_send"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -55,17 +55,4 @@
|
|||
tools:layout_marginEnd="144dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/sendShareButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:accessibilityTraversalBefore="@+id/roomListView"
|
||||
android:contentDescription="@string/a11y_create_room"
|
||||
android:src="@drawable/ic_send"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</im.vector.riotx.core.platform.StateView>
|
||||
|
|
Loading…
Reference in a new issue