diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt new file mode 100644 index 0000000000..5750fd93b4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.core.ui.list + +import android.content.res.ColorStateList +import android.view.Gravity +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.core.widget.ImageViewCompat +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.features.themes.ThemeUtils + +/** + * A generic list item. + * Displays an item with a title, and optional description. + * Can display an accessory on the right, that can be an image or an indeterminate progress. + * If provided with an action, will display a button at the bottom of the list item. + */ +@EpoxyModelClass(layout = R.layout.item_generic_pill_footer) +abstract class GenericPillItem : VectorEpoxyModel() { + + @EpoxyAttribute + var text: CharSequence? = null + + @EpoxyAttribute + var style: ItemStyle = ItemStyle.NORMAL_TEXT + + @EpoxyAttribute + var itemClickAction: GenericItem.Action? = null + + @EpoxyAttribute + var centered: Boolean = false + + @EpoxyAttribute + @DrawableRes + var imageRes: Int? = null + + @EpoxyAttribute + var tintIcon: Boolean = true + + override fun bind(holder: Holder) { + super.bind(holder) + + holder.textView.setTextOrHide(text) + holder.textView.typeface = style.toTypeFace() + holder.textView.textSize = style.toTextSize() + holder.textView.gravity = if (centered) Gravity.CENTER_HORIZONTAL else Gravity.START + + imageRes?.let { holder.imageView.setImageResource(it) } + if (tintIcon) { + val iconTintColor = ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary) + ImageViewCompat.setImageTintList(holder.imageView, ColorStateList.valueOf(iconTintColor)) + } else { + ImageViewCompat.setImageTintList(holder.imageView, null) + } + + holder.view.setOnClickListener { + itemClickAction?.perform?.run() + } + } + + class Holder : VectorEpoxyHolder() { + val imageView by bind(R.id.itemGenericPillImage) + val textView by bind(R.id.itemGenericPillText) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index f8e45dcd5e..28d410529e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -18,13 +18,21 @@ package im.vector.app.features.spaces.explore import android.view.View import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import im.vector.app.R +import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem +import im.vector.app.core.ui.list.genericPillItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.spaceChildInfoItem +import me.gujun.android.span.span +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixError.Companion.M_UNRECOGNIZED import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.util.MatrixItem @@ -32,13 +40,16 @@ import javax.inject.Inject class SpaceDirectoryController @Inject constructor( private val avatarRenderer: AvatarRenderer, - private val stringProvider: StringProvider + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, + private val errorFormatter: ErrorFormatter ) : TypedEpoxyController() { interface InteractionListener { fun onButtonClick(spaceChildInfo: SpaceChildInfo) fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo) fun onRoomClick(spaceChildInfo: SpaceChildInfo) + fun retry() } var listener: InteractionListener? = null @@ -50,6 +61,33 @@ class SpaceDirectoryController @Inject constructor( loadingItem { id("loading") } + } else if (results is Fail) { + val failure = results.error + if (failure is Failure.ServerError && failure.error.code == M_UNRECOGNIZED) { + genericPillItem { + id("HS no Support") + imageRes(R.drawable.error) + tintIcon(false) + text( + span { + span(stringProvider.getString(R.string.spaces_no_server_support_title)) { + textStyle = "bold" + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_primary) + } + +"\n\n" + span(stringProvider.getString(R.string.spaces_no_server_support_description)) { + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + } + } + ) + } + } else { + errorWithRetryItem { + id("api_err") + text(errorFormatter.toHumanReadable(failure)) + listener { listener?.retry() } + } + } } else { val flattenChildInfo = results?.invoke() ?.filter { diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 41ab8d9006..fa44f4595e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -94,6 +94,9 @@ class SpaceDirectoryFragment @Inject constructor( return true } + override fun retry() { + viewModel.handle(SpaceDirectoryViewAction.Retry) + } // override fun navigateToRoom(roomId: String) { // viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId)) // } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt index a7e1482e24..83cdf2916d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt @@ -25,4 +25,5 @@ sealed class SpaceDirectoryViewAction : VectorViewModelAction { data class ShowDetails(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction() object HandleBack : SpaceDirectoryViewAction() + object Retry : SpaceDirectoryViewAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 9d74849386..0c23752936 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -39,7 +39,7 @@ import org.matrix.android.sdk.rx.rx import timber.log.Timber class SpaceDirectoryViewModel @AssistedInject constructor( - @Assisted initialState: SpaceDirectoryState, + @Assisted val initialState: SpaceDirectoryState, private val session: Session ) : VectorViewModel(initialState) { @@ -63,11 +63,21 @@ class SpaceDirectoryViewModel @AssistedInject constructor( val spaceSum = session.getRoomSummary(initialState.spaceId) setState { copy( - childList = spaceSum?.spaceChildren ?: emptyList(), - spaceSummaryApiResult = Loading() + childList = spaceSum?.spaceChildren ?: emptyList() ) } + refreshFromApi() + observeJoinedRooms() + observeMembershipChanges() + } + + private fun refreshFromApi() { + setState { + copy( + spaceSummaryApiResult = Loading() + ) + } viewModelScope.launch(Dispatchers.IO) { try { val query = session.spaceService().querySpaceChildren(initialState.spaceId) @@ -84,8 +94,6 @@ class SpaceDirectoryViewModel @AssistedInject constructor( } } } - observeJoinedRooms() - observeMembershipChanges() } private fun observeJoinedRooms() { @@ -139,13 +147,16 @@ class SpaceDirectoryViewModel @AssistedInject constructor( is SpaceDirectoryViewAction.NavigateToRoom -> { _viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(action.roomId)) } - is SpaceDirectoryViewAction.ShowDetails -> { + is SpaceDirectoryViewAction.ShowDetails -> { // This is temporary for now to at least display something for the space beta // It's not ideal as it's doing some peeking that is not needed. session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let { - _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) + _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) } } + SpaceDirectoryViewAction.Retry -> { + refreshFromApi() + } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt index 46ca2a6606..dffb09529b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt @@ -19,6 +19,8 @@ package im.vector.app.features.spaces.manage import androidx.recyclerview.widget.DiffUtil import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.R +import im.vector.app.core.ui.list.GenericPillItem_ import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.createUIHandler import im.vector.app.features.home.AvatarRenderer @@ -56,6 +58,8 @@ class AddRoomListController @Inject constructor( var listener: Listener? = null var ignoreRooms: List? = null + var subHeaderText: CharSequence? = null + var initialLoadOccurred = false fun boundaryChange(boundary: ResultBoundaries) { @@ -100,6 +104,15 @@ class AddRoomListController @Inject constructor( expanded(true) } ) + if (subHeaderText != null) { + add( + GenericPillItem_().apply { + id("sub_header") + text(subHeaderText) + imageRes(R.drawable.ic_info) + } + ) + } } super.addModels(filteredModel) if (!initialLoadOccurred) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index 9a25e79f85..203098d32b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -16,9 +16,6 @@ package im.vector.app.features.spaces.manage -import android.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.Drawable import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -26,12 +23,8 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.ConcatAdapter -import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.airbnb.epoxy.EpoxyViewHolder import com.airbnb.mvrx.Loading import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel @@ -41,7 +34,6 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceAddRoomsBinding -import im.vector.app.features.home.room.list.RoomCategoryItem_ import io.reactivex.rxkotlin.subscribeBy import org.matrix.android.sdk.api.session.room.model.RoomSummary import java.util.concurrent.TimeUnit @@ -88,6 +80,7 @@ class SpaceAddRoomFragment @Inject constructor( } .disposeOnDestroyView() + spaceEpoxyController.subHeaderText = getString(R.string.spaces_feeling_experimental_subspace) viewModel.selectionListLiveData.observe(viewLifecycleOwner) { spaceEpoxyController.selectedItems = it roomEpoxyController.selectedItems = it @@ -183,36 +176,6 @@ class SpaceAddRoomFragment @Inject constructor( } views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) - views.roomList.addItemDecoration( - - object : DividerItemDecoration(context, VERTICAL) { - val decorationDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.divider_horizontal) - - override fun getDrawable(): Drawable? { - return decorationDrawable - } - - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - val position = parent.getChildAdapterPosition(view) - val vh = parent.findViewHolderForAdapterPosition(position) - val nextIsSectionOrFinal = parent.findViewHolderForAdapterPosition(position + 1)?.let { - (it as? EpoxyViewHolder)?.model is RoomCategoryItem_ - } ?: true - if (vh == null - || (vh as? EpoxyViewHolder)?.model is RoomCategoryItem_ - || nextIsSectionOrFinal - ) { - outRect.setEmpty() - } else { - super.getItemOffsets(outRect, view, parent, state) - } - } - - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) - } - } - ) views.roomList.setHasFixedSize(true) concatAdapter.addAdapter(roomEpoxyController.adapter) diff --git a/vector/src/main/res/drawable/ic_info.xml b/vector/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000000..afca98641a --- /dev/null +++ b/vector/src/main/res/drawable/ic_info.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/fragment_space_add_rooms.xml b/vector/src/main/res/layout/fragment_space_add_rooms.xml index 6619d06cca..84774d9cf9 100644 --- a/vector/src/main/res/layout/fragment_space_add_rooms.xml +++ b/vector/src/main/res/layout/fragment_space_add_rooms.xml @@ -10,7 +10,7 @@ android:id="@+id/roomList" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?riotx_header_panel_background" + android:background="?riotx_background" android:overScrollMode="always" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:listitem="@layout/item_room_to_add_in_space" /> diff --git a/vector/src/main/res/layout/item_generic_pill_footer.xml b/vector/src/main/res/layout/item_generic_pill_footer.xml new file mode 100644 index 0000000000..160fbc5210 --- /dev/null +++ b/vector/src/main/res/layout/item_generic_pill_footer.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 9d2763e243..cdd33b9a92 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3365,4 +3365,7 @@ Experimental Space - Only show orphans in Home + Feeling experimental?\nYou can add existing spaces to a space. + It looks like your homeserver does not support Spaces yet + Please contact your homserver admin for further information