Home: start reworking UX [WIP]

This commit is contained in:
ganfra 2019-05-15 19:44:06 +02:00 committed by Benoit Marty
parent 0feb10315b
commit 268730e71b
23 changed files with 498 additions and 201 deletions

View file

@ -44,6 +44,7 @@
android:label="@string/title_activity_emoji_reaction_picker" />
<activity android:name=".features.roomdirectory.RoomDirectoryActivity" />
<activity android:name=".features.home.room.detail.RoomDetailActivity" />
<service
android:name=".core.services.CallService"

View file

@ -21,7 +21,6 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat
@ -32,12 +31,10 @@ import com.airbnb.mvrx.viewModel
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.hideKeyboard
import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.core.platform.OnBackPressed
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragment
import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity
@ -71,13 +68,11 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
drawerLayout.addDrawerListener(drawerListener)
if (savedInstanceState == null) {
val homeDrawerFragment = HomeDrawerFragment.newInstance()
val loadingDetail = LoadingRoomDetailFragment.newInstance()
val loadingDetail = LoadingFragment.newInstance()
replaceFragment(loadingDetail, R.id.homeDetailFragmentContainer)
replaceFragment(homeDrawerFragment, R.id.homeDrawerFragmentContainer)
}
homeActivityViewModel.openRoomLiveData.observeEvent(this) {
homeNavigator.openRoomDetail(it, null)
}
homeActivityViewModel.isLoading.observe(this, Observer<Boolean> {
// TODO better UI
if (it) {
@ -115,30 +110,27 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun configure(toolbar: Toolbar) {
setSupportActionBar(toolbar)
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val drawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, 0, 0)
drawerLayout.addDrawerListener(drawerToggle)
drawerToggle.syncState()
supportActionBar?.setDisplayUseLogoEnabled(true)
}
override fun getMenuRes() = R.menu.home
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
android.R.id.home -> {
drawerLayout.openDrawer(GravityCompat.START)
return true
}
R.id.sliding_menu_settings -> {
R.id.sliding_menu_settings -> {
startActivity(VectorSettingsActivity.getIntent(this, "TODO"))
return true
}
R.id.sliding_menu_sign_out -> {
R.id.sliding_menu_sign_out -> {
SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!)
return true
}
// TODO Temporary code here to create a room
R.id.tmp_menu_create_room -> {
R.id.tmp_menu_create_room -> {
// Start Activity for now
startActivity(Intent(this, RoomDirectoryActivity::class.java))
return true

View file

@ -17,11 +17,12 @@
package im.vector.riotredesign.features.home
import android.os.Bundle
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.replaceChildFragment
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.group.GroupListFragment
import im.vector.riotredesign.features.home.room.list.RoomListFragment
import kotlinx.android.synthetic.main.fragment_home_drawer.*
class HomeDrawerFragment : VectorBaseFragment() {
@ -38,9 +39,14 @@ class HomeDrawerFragment : VectorBaseFragment() {
super.onActivityCreated(savedInstanceState)
if (savedInstanceState == null) {
val groupListFragment = GroupListFragment.newInstance()
replaceChildFragment(groupListFragment, R.id.groupListFragmentContainer)
val roomListFragment = RoomListFragment.newInstance()
replaceChildFragment(roomListFragment, R.id.roomListFragmentContainer)
replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer)
}
val session = Matrix.getInstance().currentSession ?: return
val user = session.getUser(session.sessionParams.credentials.userId)
if (user != null) {
AvatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
homeDrawerUsernameView.text = user.displayName
homeDrawerUserIdView.text = user.userId
}
}

View file

@ -18,11 +18,13 @@ package im.vector.riotredesign.features.home
import androidx.core.view.GravityCompat
import androidx.fragment.app.FragmentManager
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.addFragmentToBackstack
import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.features.home.group.SelectedGroupFragment
import im.vector.riotredesign.features.home.group.SelectedGroupParams
import im.vector.riotredesign.features.home.room.detail.RoomDetailActivity
import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs
import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment
import kotlinx.android.synthetic.main.activity_home.*
import timber.log.Timber
@ -32,22 +34,25 @@ class HomeNavigator {
private var rootRoomId: String? = null
fun openSelectedGroup(groupSummary: GroupSummary) {
Timber.v("Open selected group ${groupSummary.groupId}")
activity?.let {
val args = SelectedGroupParams(groupSummary.groupId, groupSummary.displayName, groupSummary.avatarUrl)
val selectedGroupFragment = SelectedGroupFragment.newInstance(args)
it.drawerLayout?.closeDrawer(GravityCompat.START)
it.replaceFragment(selectedGroupFragment, R.id.homeDetailFragmentContainer)
}
}
fun openRoomDetail(roomId: String,
eventId: String?,
addToBackstack: Boolean = false) {
Timber.v("Open room detail $roomId - $eventId - $addToBackstack")
eventId: String?) {
Timber.v("Open room detail $roomId - $eventId")
activity?.let {
//TODO enable eventId permalink. It doesn't work enough at the moment.
val args = RoomDetailArgs(roomId)
val roomDetailFragment = RoomDetailFragment.newInstance(args)
it.drawerLayout?.closeDrawer(GravityCompat.START)
if (addToBackstack) {
it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId)
} else {
rootRoomId = roomId
clearBackStack(it.supportFragmentManager)
it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
}
val args = RoomDetailArgs(roomId)
val roomDetailIntent = RoomDetailActivity.newIntent(it, args)
it.startActivity(roomDetailIntent)
}
}

View file

@ -34,10 +34,10 @@ class HomePermalinkHandler(private val navigator: HomeNavigator) {
val permalinkData = PermalinkParser.parse(deepLink)
when (permalinkData) {
is PermalinkData.EventLink -> {
navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId, true)
navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId)
}
is PermalinkData.RoomLink -> {
navigator.openRoomDetail(permalinkData.roomIdOrAlias, null, true)
navigator.openRoomDetail(permalinkData.roomIdOrAlias, null)
}
is PermalinkData.GroupLink -> {
navigator.openGroupDetail(permalinkData.groupId)

View file

@ -0,0 +1,49 @@
/*
*
* * 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.riotredesign.features.home
import android.graphics.drawable.AnimationDrawable
import android.os.Bundle
import android.view.View
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_loading.*
class LoadingFragment : VectorBaseFragment() {
companion object {
fun newInstance(): LoadingFragment {
return LoadingFragment()
}
}
override fun getLayoutResId() = R.layout.fragment_loading
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val background = animatedLogoImageView.background
if (background is AnimationDrawable) {
background.start()
}
}
}

View file

@ -22,9 +22,11 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.platform.StateView
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.HomeNavigator
import kotlinx.android.synthetic.main.fragment_group_list.*
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
@ -39,6 +41,7 @@ class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback
}
private val viewModel: GroupListViewModel by fragmentViewModel()
private val homeNavigator by inject<HomeNavigator>()
private val groupController by inject<GroupSummaryController>()
override fun getLayoutResId() = R.layout.fragment_group_list
@ -50,6 +53,9 @@ class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback
stateView.contentView = epoxyRecyclerView
epoxyRecyclerView.setController(groupController)
viewModel.subscribe { renderState(it) }
viewModel.openGroupLiveData.observeEvent(this) {
homeNavigator.openSelectedGroup(it)
}
}
private fun renderState(state: GroupListViewState) {

View file

@ -16,17 +16,25 @@
package im.vector.riotredesign.features.home.group
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.utils.LiveEvent
import org.koin.android.ext.android.get
const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID"
class GroupListViewModel(initialState: GroupListViewState,
private val selectedGroupHolder: SelectedGroupStore,
private val session: Session
private val session: Session,
private val stringProvider: StringProvider
) : VectorViewModel<GroupListViewState>(initialState) {
companion object : MvRxViewModelFactory<GroupListViewModel, GroupListViewState> {
@ -35,10 +43,15 @@ class GroupListViewModel(initialState: GroupListViewState,
override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? {
val currentSession = viewModelContext.activity.get<Session>()
val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupStore>()
return GroupListViewModel(state, selectedGroupHolder, currentSession)
val stringProvider = viewModelContext.activity.get<StringProvider>()
return GroupListViewModel(state, selectedGroupHolder, currentSession, stringProvider)
}
}
private val _openGroupLiveData = MutableLiveData<LiveEvent<GroupSummary>>()
val openGroupLiveData: LiveData<LiveEvent<GroupSummary>>
get() = _openGroupLiveData
init {
observeGroupSummaries()
observeState()
@ -46,8 +59,8 @@ class GroupListViewModel(initialState: GroupListViewState,
private fun observeState() {
subscribe {
val selectedGroup = Option.fromNullable(it.selectedGroup)
selectedGroupHolder.post(selectedGroup)
val optionGroup = Option.fromNullable(it.selectedGroup)
selectedGroupHolder.post(optionGroup)
}
}
@ -62,15 +75,21 @@ class GroupListViewModel(initialState: GroupListViewState,
private fun handleSelectGroup(action: GroupListActions.SelectGroup) = withState { state ->
if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
setState { copy(selectedGroup = action.groupSummary) }
} else {
setState { copy(selectedGroup = null) }
_openGroupLiveData.postValue(LiveEvent(action.groupSummary))
}
}
private fun observeGroupSummaries() {
session
.rx().liveGroupSummaries()
.map {
val myUser = session.getUser(session.sessionParams.credentials.userId)
val allCommunityGroup = GroupSummary(
groupId = ALL_COMMUNITIES_GROUP_ID,
displayName = "All Communities",
avatarUrl = myUser?.avatarUrl ?: "")
listOf(allCommunityGroup) + it
}
.execute { async ->
copy(asyncGroups = async)
}

View file

@ -16,13 +16,14 @@
package im.vector.riotredesign.features.home.group
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.platform.CheckableFrameLayout
import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_group)
@ -36,14 +37,15 @@ abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.isSelected = selected
holder.rootView.setOnClickListener { listener?.invoke() }
holder.groupNameView.text = groupName
AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val rootView by bind<CheckableFrameLayout>(R.id.itemGroupLayout)
val groupNameView by bind<TextView>(R.id.groupNameView)
val rootView by bind<ViewGroup>(R.id.itemGroupLayout)
}
}

View file

@ -0,0 +1,111 @@
/*
*
* * 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.riotredesign.features.home.group
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Parcelable
import com.airbnb.mvrx.args
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.replaceChildFragment
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.list.RoomListFragment
import im.vector.riotredesign.features.home.room.list.RoomListParams
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_selected_group.*
@Parcelize
data class SelectedGroupParams(
val groupId: String,
val groupName: String,
val groupAvatar: String
) : Parcelable
class SelectedGroupFragment : VectorBaseFragment() {
private val selectedGroupParams: SelectedGroupParams by args()
override fun getLayoutResId(): Int {
return R.layout.fragment_selected_group
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (savedInstanceState == null) {
updateSelectedFragment(RoomListFragment.DisplayMode.HOME)
toolbar.setTitle(RoomListFragment.DisplayMode.HOME.titleRes)
}
setupBottomNavigationView()
setupToolbar()
}
private fun setupToolbar() {
val parentActivity = vectorBaseActivity
if (parentActivity is ToolbarConfigurable) {
parentActivity.configure(toolbar)
}
val toolbarLogoTarget = object : SimpleTarget<Drawable>() {
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
toolbar.logo = resource
}
}
AvatarRenderer.render(
requireContext(),
GlideApp.with(this),
selectedGroupParams.groupAvatar,
selectedGroupParams.groupId,
selectedGroupParams.groupName,
toolbarLogoTarget
)
}
private fun setupBottomNavigationView() {
bottomNavigationView.setOnNavigationItemSelectedListener {
val displayMode = when {
it.itemId == R.id.bottom_action_people -> RoomListFragment.DisplayMode.PEOPLE
it.itemId == R.id.bottom_action_rooms -> RoomListFragment.DisplayMode.ROOMS
else -> RoomListFragment.DisplayMode.HOME
}
updateSelectedFragment(displayMode)
toolbar.setTitle(displayMode.titleRes)
true
}
}
private fun updateSelectedFragment(displayMode: RoomListFragment.DisplayMode) {
val roomListParams = RoomListParams(displayMode)
val roomListFragment = RoomListFragment.newInstance(roomListParams)
replaceChildFragment(roomListFragment, R.id.roomListContainer)
}
companion object {
fun newInstance(args: SelectedGroupParams): SelectedGroupFragment {
return SelectedGroupFragment().apply {
setArguments(args)
}
}
}
}

View file

@ -1,47 +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.riotredesign.features.home.room.detail
import android.graphics.drawable.AnimationDrawable
import android.os.Bundle
import android.view.View
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_loading_room_detail.*
class LoadingRoomDetailFragment : VectorBaseFragment() {
companion object {
fun newInstance(): LoadingRoomDetailFragment {
return LoadingRoomDetailFragment()
}
}
override fun getLayoutResId() = R.layout.fragment_loading_room_detail
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val background = animatedLogoImageView.background
if (background is AnimationDrawable) {
background.start()
}
}
}

View file

@ -0,0 +1,57 @@
/*
*
* * 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.riotredesign.features.home.room.detail
import android.content.Context
import android.content.Intent
import android.os.Bundle
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.core.platform.VectorBaseActivity
class RoomDetailActivity : VectorBaseActivity() {
override fun getLayoutRes(): Int {
return R.layout.activity_room_detail
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
?: return
val roomDetailFragment = RoomDetailFragment.newInstance(roomDetailArgs)
replaceFragment(roomDetailFragment, R.id.roomDetailContainer)
}
}
companion object {
private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent {
return Intent(context, RoomDetailActivity::class.java).apply {
putExtra(EXTRA_ROOM_DETAIL_ARGS, roomDetailArgs)
}
}
}
}

View file

@ -64,7 +64,6 @@ import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotredesign.core.extensions.hideKeyboard
import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.*
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter
@ -170,7 +169,6 @@ class RoomDetailFragment :
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE))
setupRecyclerView()
setupToolbar()
setupComposer()
setupAttachmentButton()
setupInviteView()
@ -194,7 +192,7 @@ class RoomDetailFragment :
if (resultCode == RESULT_OK && data != null) {
when (requestCode) {
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
REACTION_SELECT_REQUEST_CODE -> {
REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
@ -213,13 +211,6 @@ class RoomDetailFragment :
// PRIVATE METHODS *****************************************************************************
private fun setupToolbar() {
val parentActivity = vectorBaseActivity
if (parentActivity is ToolbarConfigurable) {
parentActivity.configure(toolbar)
}
}
private fun setupRecyclerView() {
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(recyclerView)
@ -362,24 +353,24 @@ class RoomDetailFragment :
private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
Timber.v("On send choice clicked: $dialogListItem")
when (dialogListItem) {
is DialogListItem.SendFile -> {
is DialogListItem.SendFile -> {
// launchFileIntent
}
is DialogListItem.SendVoice -> {
is DialogListItem.SendVoice -> {
//launchAudioRecorderIntent()
}
is DialogListItem.SendSticker -> {
is DialogListItem.SendSticker -> {
//startStickerPickerActivity()
}
is DialogListItem.TakePhotoVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
// launchCamera()
}
is DialogListItem.TakePhoto ->
is DialogListItem.TakePhoto ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) {
openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE)
}
is DialogListItem.TakeVideo ->
is DialogListItem.TakeVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) {
// launchNativeVideoRecorder()
}
@ -426,20 +417,20 @@ class RoomDetailFragment :
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
when (sendMessageResult) {
is SendMessageResult.MessageSent,
is SendMessageResult.SlashCommandHandled -> {
is SendMessageResult.SlashCommandHandled -> {
// Clear composer
composerEditText.text = null
}
is SendMessageResult.SlashCommandError -> {
is SendMessageResult.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
}
is SendMessageResult.SlashCommandUnknown -> {
is SendMessageResult.SlashCommandUnknown -> {
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
}
is SendMessageResult.SlashCommandResultOk -> {
is SendMessageResult.SlashCommandResultOk -> {
// Ignore
}
is SendMessageResult.SlashCommandResultError -> {
is SendMessageResult.SlashCommandResultError -> {
displayCommandError(sendMessageResult.throwable.localizedMessage)
}
is SendMessageResult.SlashCommandNotImplemented -> {
@ -537,22 +528,22 @@ class RoomDetailFragment :
it?.getContentIfNotHandled()?.let { actionData ->
when (actionData.actionId) {
MessageMenuViewModel.ACTION_ADD_REACTION -> {
MessageMenuViewModel.ACTION_ADD_REACTION -> {
val eventId = actionData.data?.toString() ?: return
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
}
MessageMenuViewModel.ACTION_COPY -> {
MessageMenuViewModel.ACTION_COPY -> {
//I need info about the current selected message :/
copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false)
val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color))
snack.show()
}
MessageMenuViewModel.ACTION_DELETE -> {
MessageMenuViewModel.ACTION_DELETE -> {
val eventId = actionData.data?.toString() ?: return
roomDetailViewModel.process(RoomDetailActions.RedactAction(eventId, context?.getString(R.string.event_redacted_by_user_reason)))
}
MessageMenuViewModel.ACTION_SHARE -> {
MessageMenuViewModel.ACTION_SHARE -> {
//TODO current data communication is too limited
//Need to now the media type
actionData.data?.toString()?.let {
@ -595,13 +586,13 @@ class RoomDetailFragment :
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
.show()
}
MessageMenuViewModel.ACTION_QUICK_REACT -> {
MessageMenuViewModel.ACTION_QUICK_REACT -> {
//eventId,ClickedOn,Opposite
(actionData.data as? Triple<String, String, String>)?.let { (eventId, clickedOn, opposite) ->
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, opposite))
}
}
else -> {
else -> {
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
}
}

View file

@ -17,8 +17,8 @@
package im.vector.riotredesign.features.home.room.list
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.os.Parcelable
import androidx.annotation.StringRes
import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
@ -29,21 +29,35 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.extensions.setupAsSearch
import im.vector.riotredesign.core.platform.StateView
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.HomeNavigator
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.*
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
@Parcelize
data class RoomListParams(
val displayMode: RoomListFragment.DisplayMode
) : Parcelable
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
enum class DisplayMode(@StringRes val titleRes: Int) {
HOME(R.string.bottom_action_home),
PEOPLE(R.string.bottom_action_people),
ROOMS(R.string.bottom_action_rooms)
}
companion object {
fun newInstance(): RoomListFragment {
return RoomListFragment()
fun newInstance(roomListParams: RoomListParams): RoomListFragment {
return RoomListFragment().apply {
setArguments(roomListParams)
}
}
}
@ -57,7 +71,6 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
super.onActivityCreated(savedInstanceState)
bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE))
setupRecyclerView()
setupFilterView()
roomListViewModel.subscribe { renderState(it) }
roomListViewModel.openRoomLiveData.observeEvent(this) {
homeNavigator.openRoomDetail(it, null)
@ -74,19 +87,6 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
epoxyRecyclerView.setController(roomController)
}
private fun setupFilterView() {
filterRoomView.setupAsSearch()
filterRoomView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) = Unit
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
roomListViewModel.accept(RoomListActions.FilterRooms(s))
}
})
}
private fun renderState(state: RoomListViewState) {
when (state.asyncRooms) {
is Incomplete -> renderLoading()

View file

@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.home.group.ALL_COMMUNITIES_GROUP_ID
import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.VisibleRoomStore
import io.reactivex.Observable
@ -118,7 +119,7 @@ class RoomListViewModel(initialState: RoomListViewState,
val filteredDirectRooms = filteredRooms
.filter { it.isDirect }
.filter {
if (selectedGroup == null) {
if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) {
true
} else {
it.otherMemberIds
@ -130,7 +131,8 @@ class RoomListViewModel(initialState: RoomListViewState,
val filteredGroupRooms = filteredRooms
.filter { !it.isDirect }
.filter {
selectedGroup?.roomIds?.contains(it.roomId) ?: true
selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID
|| selectedGroup?.roomIds?.contains(it.roomId) ?: true
}
buildRoomSummaries(filteredDirectRooms + filteredGroupRooms)
}

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/roomDetailContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>

View file

@ -4,8 +4,7 @@
<im.vector.riotredesign.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dark">
android:layout_height="match_parent">
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/epoxyRecyclerView"

View file

@ -1,23 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/groupListFragmentContainer"
android:layout_width="64dp"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/homeDrawerHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:padding="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/homeDrawerHeaderAvatarView"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/homeDrawerUsernameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@android:color/white"
app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView"
app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeaderAvatarView"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/homeDrawerUserIdView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/homeDrawerUsernameView"
app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView"
tools:text="@tools:sample/full_names" />
<ImageButton
android:id="@+id/homeDrawerHeaderSettingsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/homeDrawerUserIdView" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/roomListFragmentContainer"
android:id="@+id/homeDrawerGroupListContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/groupListFragmentContainer"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeader" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,53 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<im.vector.riotredesign.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pale_grey">
<EditText
android:id="@+id/filterRoomView"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_search_edit_text"
android:drawableStart="@drawable/ic_search_white"
android:drawableLeft="@drawable/ic_search_white"
android:drawablePadding="8dp"
android:drawableTint="#9fa9ba"
android:hint="@string/home_filter_placeholder_rooms"
android:lines="1"
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:layout_constraintBottom_toTopOf="@+id/stateView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/epoxyRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<im.vector.riotredesign.core.platform.StateView
android:id="@+id/stateView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="0dp"
android:layout_marginRight="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/filterRoomView">
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/epoxyRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</im.vector.riotredesign.core.platform.StateView>
</androidx.constraintlayout.widget.ConstraintLayout>
</im.vector.riotredesign.core.platform.StateView>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:contentInsetStartWithNavigation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
<FrameLayout
android:id="@+id/roomListContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:itemIconTint="@android:color/white"
app:itemTextColor="@color/home_bottom_nav_view_tint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu='@menu/selected_group_navigation' />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,22 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.riotredesign.core.platform.CheckableFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemGroupLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="56dp"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="true"
android:padding="8dp">
android:padding="16dp">
<ImageView
android:id="@+id/groupAvatarImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:duplicateParentState="true"
android:foreground="@drawable/fg_group_item"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
</im.vector.riotredesign.core.platform.CheckableFrameLayout>
<TextView
android:id="@+id/groupNameView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="32dp"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="@+id/groupAvatarImageView"
app:layout_constraintEnd_toStartOf="@+id/groupAvatarChevron"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="@+id/groupAvatarImageView"
tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/groupAvatarChevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_material_chevron_right_black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/bottom_action_home"
android:contentDescription="@string/bottom_action_home"
android:enabled="true"
android:icon="@drawable/ic_home_black_24dp"
android:title="" />
<item
android:id="@+id/bottom_action_people"
android:contentDescription="@string/bottom_action_people"
android:enabled="true"
android:icon="@drawable/ic_person_black_24dp"
android:title="" />
<item
android:id="@+id/bottom_action_rooms"
android:contentDescription="@string/bottom_action_rooms"
android:enabled="true"
android:icon="@drawable/riot_tab_rooms"
android:title="" />
</menu>