From 689b8e93af42c6068de1070c901785258b508c67 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Mon, 6 Jan 2020 21:09:44 +0100 Subject: [PATCH] Improvements & new search Signed-off-by: Mario Danic --- .../talk/adapters/items/ConversationItem.kt | 249 ------------------ .../talk/controllers/base/BaseController.kt | 57 +++- .../models/json/conversations/Conversation.kt | 45 ++++ .../ConversationsListView.kt | 77 +++--- .../res/drawable/ic_baseline_clear_24.xml | 28 ++ app/src/main/res/layout/activity_main.xml | 23 +- .../layout/controller_conversations_rv.xml | 3 +- app/src/main/res/layout/search_layout.xml | 122 +++++++++ app/src/main/res/values/strings.xml | 1 + 9 files changed, 297 insertions(+), 308 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt create mode 100644 app/src/main/res/drawable/ic_baseline_clear_24.xml create mode 100644 app/src/main/res/layout/search_layout.xml diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt deleted file mode 100644 index 17ee5da1e..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.adapters.items - -import android.content.Context -import android.graphics.drawable.Drawable -import android.text.TextUtils -import android.text.format.DateUtils -import android.util.Log -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import coil.api.load -import coil.transform.CircleCropTransformation -import com.nextcloud.talk.R -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.conversations.Conversation -import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.ONE_TO_ONE_CONVERSATION -import com.nextcloud.talk.newarch.local.models.UserNgEntity -import com.nextcloud.talk.newarch.local.models.getCredentials -import com.nextcloud.talk.newarch.utils.Images -import com.nextcloud.talk.utils.ApiUtils -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFilterable -import eu.davidea.flexibleadapter.items.IFlexible -import eu.davidea.flexibleadapter.utils.FlexibleUtils -import eu.davidea.viewholders.FlexibleViewHolder -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.rv_item_conversation_with_last_message.* -import kotlinx.android.synthetic.main.rv_item_conversation_with_last_message.view.* -import java.util.* -import java.util.regex.Pattern - -class ConversationItem( - val model: Conversation, - val user: UserNgEntity, - private val context: Context -) : AbstractFlexibleItem(), IFilterable { - - override fun equals(other: Any?): Boolean { - if (other is ConversationItem) { - val inItem = other as ConversationItem? - val comparedConversation = inItem!!.model - - return (model.conversationId == comparedConversation.conversationId - && model.token == comparedConversation.token - && model.name == comparedConversation.name - && model.displayName == comparedConversation.displayName - && model.type == comparedConversation.type - && model.lastMessage == comparedConversation.lastMessage - && model.favorite == comparedConversation.favorite - && model.hasPassword == comparedConversation.hasPassword - && model.unreadMessages == comparedConversation.unreadMessages - && model.unreadMention == comparedConversation.unreadMention - && model.objectType == comparedConversation.objectType - && model.changing == comparedConversation.changing - && inItem.user.id == user.id) - } - return false - } - - override fun hashCode(): Int { - return Objects.hash( - model.token, - user.id - ) - } - - override fun getLayoutRes(): Int { - return R.layout.rv_item_conversation_with_last_message - } - - override fun createViewHolder( - view: View, - adapter: FlexibleAdapter> - ): ConversationItemViewHolder { - return ConversationItemViewHolder(view, adapter) - } - - override fun bindViewHolder( - adapter: FlexibleAdapter>, - holder: ConversationItemViewHolder, - position: Int, - payloads: List - ) { - val appContext = NextcloudTalkApplication.sharedApplication!!.applicationContext - - if (model.changing) { - holder.actionProgressBar!!.visibility = View.VISIBLE - } else { - holder.actionProgressBar!!.visibility = View.GONE - } - - if (adapter.hasFilter()) { - FlexibleUtils.highlightText( - holder.dialogName!!, model.displayName, - adapter.getFilter(String::class.java).toString(), - NextcloudTalkApplication.sharedApplication!! - .resources.getColor(R.color.colorPrimary) - ) - } else { - holder.dialogName!!.text = model.displayName - } - - if (model.unreadMessages > 0) { - holder.dialogUnreadBubble!!.visibility = View.VISIBLE - if (model.unreadMessages < 100) { - holder.dialogUnreadBubble!!.text = model.unreadMessages.toLong() - .toString() - } else { - holder.dialogUnreadBubble!!.text = context.getString(R.string.nc_99_plus) - } - - if (model.unreadMention) { - holder.dialogUnreadBubble!!.background = - context.getDrawable(R.drawable.bubble_circle_unread_mention) - } else { - holder.dialogUnreadBubble!!.background = - context.getDrawable(R.drawable.bubble_circle_unread) - } - } else { - holder.dialogUnreadBubble!!.visibility = View.GONE - } - - if (model.hasPassword) { - holder.passwordProtectedRoomImageView!!.visibility = View.VISIBLE - } else { - holder.passwordProtectedRoomImageView!!.visibility = View.GONE - } - - if (model.favorite) { - holder.favoriteConversationImageView!!.visibility = View.VISIBLE - } else { - holder.favoriteConversationImageView!!.visibility = View.GONE - } - - if (model.lastMessage != null) { - holder.dialogDate!!.visibility = View.VISIBLE - holder.dialogDate!!.text = DateUtils.getRelativeTimeSpanString( - model.lastActivity * 1000L, - System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE - ) - - if (!TextUtils.isEmpty( - model.lastMessage!!.systemMessage - ) || Conversation.ConversationType.SYSTEM_CONVERSATION == model.type - ) { - holder.dialogLastMessage!!.text = model.lastMessage!!.text - } else { - var authorDisplayName = "" - model.lastMessage!!.activeUser = user - val text: String - if (model.lastMessage!! - .messageType == ChatMessage.MessageType.REGULAR_TEXT_MESSAGE && (!(ONE_TO_ONE_CONVERSATION).equals( - model.type) || model.lastMessage!!.actorId == user.userId) - ) { - if (model.lastMessage!!.actorId == user.userId) { - text = String.format( - appContext.getString(R.string.nc_formatted_message_you), - model.lastMessage!!.lastMessageDisplayText - ) - } else { - authorDisplayName = if (!TextUtils.isEmpty(model.lastMessage!!.actorDisplayName)) - model.lastMessage!!.actorDisplayName - else if ("guests" == model.lastMessage!!.actorType) - appContext.getString(R.string.nc_guest) - else - "" - text = String.format( - appContext.getString(R.string.nc_formatted_message), - authorDisplayName, - model.lastMessage!!.lastMessageDisplayText - ) - } - } else { - text = model.lastMessage!!.lastMessageDisplayText - } - - holder.dialogLastMessage.text = text - } - } else { - holder.dialogDate.visibility = View.GONE - holder.dialogLastMessage.setText(R.string.nc_no_messages_yet) - } - - val conversationDrawable: Drawable? = Images().getImageForConversation(context, model) - - conversationDrawable?.let { - holder.itemView.dialogAvatar.load(conversationDrawable) - }?: run { - holder.itemView.dialogAvatar.load(ApiUtils.getUrlForAvatarWithName( - user.baseUrl, - model.name, R.dimen.avatar_size)) - { - addHeader("Authorization", user.getCredentials()) - transformations(CircleCropTransformation()) - } - } - - } - - override fun onViewAttached(adapter: FlexibleAdapter>?, holder: ConversationItemViewHolder?, position: Int) { - super.onViewAttached(adapter, holder, position) - Log.d("MAriO", model.displayName!!) - } - - override fun onViewDetached(adapter: FlexibleAdapter>?, holder: ConversationItemViewHolder?, position: Int) { - super.onViewDetached(adapter, holder, position) - Log.d("MAriO DETACH", model.displayName!!) - } - - override fun filter(constraint: String): Boolean { - return model.displayName != null && Pattern.compile( - constraint, Pattern.CASE_INSENSITIVE or Pattern.LITERAL - ) - .matcher(model.displayName!!.trim { it <= ' ' }) - .find() - } - - class ConversationItemViewHolder( - view: View, - adapter: FlexibleAdapter<*> - ) : FlexibleViewHolder(view, adapter), LayoutContainer { - - override val containerView: View? - get() = itemView - - - } -} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt b/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt index 66db49505..5d17c274a 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt @@ -20,11 +20,14 @@ package com.nextcloud.talk.controllers.base +import android.app.Activity import android.content.ComponentCallbacks import android.content.Context import android.content.res.Configuration import android.os.Build import android.util.Log +import android.util.TypedValue +import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup @@ -33,7 +36,14 @@ import android.view.inputmethod.InputMethodManager import android.widget.EditText import androidx.annotation.RequiresApi import androidx.appcompat.app.ActionBar +import androidx.core.view.isVisible +import androidx.core.view.marginBottom +import androidx.core.view.updatePadding import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider +import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.appbar.MaterialToolbar +import com.nextcloud.talk.R +import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.controllers.AccountVerificationController import com.nextcloud.talk.controllers.ServerSelectionController import com.nextcloud.talk.controllers.SwitchAccountController @@ -41,6 +51,8 @@ import com.nextcloud.talk.controllers.WebViewLoginController import com.nextcloud.talk.controllers.base.providers.ActionBarProvider import com.nextcloud.talk.utils.preferences.AppPreferences import com.uber.autodispose.lifecycle.LifecycleScopeProvider +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.search_layout.* import org.greenrobot.eventbus.EventBus import org.koin.android.ext.android.inject import java.util.* @@ -68,15 +80,39 @@ abstract class BaseController : ButterKnifeController(), ComponentCallbacks { } override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { + return when (item.itemId) { android.R.id.home -> { router.popCurrentController() - return true + true } - else -> return super.onOptionsItemSelected(item) + else -> super.onOptionsItemSelected(item) } } + private fun showSearchOrToolbar() { + val value = getIsUsingSearchLayout() + activity?.let { + if (it is MainActivity) { + it.searchCardView.isVisible = value + it.inputEditText.hint = getSearchHint() + + val layoutParams = it.toolbar.layoutParams as AppBarLayout.LayoutParams + + if (value) { + it.appBar.setBackgroundResource(R.color.transparent) + //it.toolbar.setContentInsetsAbsolute(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources?.displayMetrics).toInt(), 0) + //layoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP + it.toolbar.layoutParams = layoutParams + } else { + it.appBar.setBackgroundResource(R.color.colorPrimary) + //it.toolbar.setContentInsetsAbsolute(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24f, resources?.displayMetrics).toInt(), 0) + layoutParams.scrollFlags = 0 + it.toolbar.layoutParams = layoutParams + } + } + } + + } private fun cleanTempCertPreference() { val temporaryClassNames = ArrayList() temporaryClassNames.add(ServerSelectionController::class.java.name) @@ -95,16 +131,22 @@ abstract class BaseController : ButterKnifeController(), ComponentCallbacks { cleanTempCertPreference() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) { disableKeyboardPersonalisedLearning(view as ViewGroup) + + activity?.let { + if (it is MainActivity && getIsUsingSearchLayout()) { + disableKeyboardPersonalisedLearning(it.appBar) + } + } } + } override fun onAttach(view: View) { super.onAttach(view) setTitle() - if (actionBar != null) { - actionBar!!.setDisplayHomeAsUpEnabled(parentController != null || router.backstackSize > 1) - } + actionBar?.setDisplayHomeAsUpEnabled(parentController != null || router.backstackSize > 1) + showSearchOrToolbar() } override fun onDetach(view: View) { @@ -159,4 +201,7 @@ abstract class BaseController : ButterKnifeController(), ComponentCallbacks { open fun getTitle(): String? { return null } + + open fun getIsUsingSearchLayout(): Boolean = false + open fun getSearchHint(): String? = null } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt index c160fb3fd..52934a71b 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt @@ -100,7 +100,10 @@ class Conversation { @JsonIgnore var changing: Boolean = false + + @JsonIgnore val isPublic: Boolean = ConversationType.PUBLIC_CONVERSATION == type + @JsonIgnore val isGuest: Boolean = Participant.ParticipantType.GUEST == participantType || Participant.ParticipantType.USER_FOLLOWING_LINK == participantType @@ -155,6 +158,48 @@ class Conversation { ) || type != ConversationType.ONE_TO_ONE_CONVERSATION && participants!!.size > 1 } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Conversation + + if (databaseId != other.databaseId) return false + if (databaseUserId != other.databaseUserId) return false + if (conversationId != other.conversationId) return false + if (token != other.token) return false + if (name != other.name) return false + if (displayName != other.displayName) return false + if (type != other.type) return false + if (count != other.count) return false + if (numberOfGuests != other.numberOfGuests) return false + if (participants != other.participants) return false + if (participantType != other.participantType) return false + if (hasPassword != other.hasPassword) return false + if (password != other.password) return false + if (favorite != other.favorite) return false + if (lastActivity != other.lastActivity) return false + if (unreadMessages != other.unreadMessages) return false + if (unreadMention != other.unreadMention) return false + if (lastMessage != other.lastMessage) return false + if (objectType != other.objectType) return false + if (notificationLevel != other.notificationLevel) return false + if (conversationReadOnlyState != other.conversationReadOnlyState) return false + if (lobbyState != other.lobbyState) return false + if (lobbyTimer != other.lobbyTimer) return false + if (lastReadMessageId != other.lastReadMessageId) return false + if (canStartCall != other.canStartCall) return false + if (changing != other.changing) return false + + return true + } + + override fun hashCode(): Int { + var result = databaseUserId?.hashCode() ?: 0 + result = 31 * result + (token?.hashCode() ?: 0) + return result + } + enum class NotificationLevel { DEFAULT, ALWAYS, diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt index e441829c7..7d82e66c9 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt @@ -55,8 +55,12 @@ import com.nextcloud.talk.utils.bundle.BundleKeys import com.otaliastudios.elements.* import com.uber.autodispose.lifecycle.LifecycleScopeProvider import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.activity_main.view.* import kotlinx.android.synthetic.main.controller_conversations_rv.view.* import kotlinx.android.synthetic.main.message_state.view.* +import kotlinx.android.synthetic.main.search_layout.* +import kotlinx.android.synthetic.main.search_layout.view.* import org.koin.android.ext.android.inject import org.parceler.Parcels import java.util.* @@ -68,50 +72,6 @@ class ConversationsListView : BaseView() { private lateinit var viewModel: ConversationsListViewModel val factory: ConversationListViewModelFactory by inject() - private var searchItem: MenuItem? = null - private var settingsItem: MenuItem? = null - private var searchView: SearchView? = null - - override fun onCreateOptionsMenu( - menu: Menu, - inflater: MenuInflater - ) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.menu_conversation_plus_filter, menu) - searchItem = menu.findItem(R.id.action_search) - } - - override fun onPrepareOptionsMenu(menu: Menu) { - super.onPrepareOptionsMenu(menu) - settingsItem = menu.findItem(R.id.action_settings) - settingsItem?.actionView?.transitionName = "userAvatar.transitionTag" - viewModel.loadAvatar() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_settings -> { - val names = ArrayList() - names.add("userAvatar.transitionTag") - router.pushController( - RouterTransaction.with(SettingsController()) - .pushChangeHandler( - TransitionChangeHandlerCompat( - SharedElementTransition(names), VerticalChangeHandler() - ) - ) - .popChangeHandler( - TransitionChangeHandlerCompat( - SharedElementTransition(names), VerticalChangeHandler() - ) - ) - ) - return true - } - else -> return super.onOptionsItemSelected(item) - } - } - /*private fun initSearchView() { val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager searchView = MenuItemCompat.getActionView(searchItem) as SearchView @@ -144,7 +104,6 @@ class ConversationsListView : BaseView() { inflater: LayoutInflater, container: ViewGroup ): View { - setHasOptionsMenu(true) actionBar?.show() viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java) @@ -172,8 +131,26 @@ class ConversationsListView : BaseView() { swipeRefreshLayoutView.setColorSchemeResources(R.color.colorPrimary) } + activity?.rightButton?.setOnClickListener { + val settingsTransitionName = "userAvatar.transitionTag" + router.pushController( + RouterTransaction.with(SettingsController()) + .pushChangeHandler( + TransitionChangeHandlerCompat( + SharedElementTransition(arrayListOf(settingsTransitionName)), VerticalChangeHandler() + ) + ) + .popChangeHandler( + TransitionChangeHandlerCompat( + SharedElementTransition(arrayListOf(settingsTransitionName)), VerticalChangeHandler() + ) + ) + ) + + } + viewModel.avatar.observe(this@ConversationsListView) { avatar -> - settingsItem?.icon = avatar + activity?.rightButton?.setImageDrawable(avatar) } return view @@ -321,6 +298,14 @@ class ConversationsListView : BaseView() { return items } + override fun getIsUsingSearchLayout(): Boolean { + return true + } + + override fun getSearchHint(): String? { + return resources?.getString(R.string.nc_search_conversations) + } + override fun getTitle(): String? { return resources?.getString(R.string.nc_app_name) } diff --git a/app/src/main/res/drawable/ic_baseline_clear_24.xml b/app/src/main/res/drawable/ic_baseline_clear_24.xml new file mode 100644 index 000000000..f911ed626 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_clear_24.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 93822f260..aadb3ff55 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -33,16 +33,27 @@ - + android:layout_height="wrap_content" + android:layout_margin="0dp" + android:id="@+id/appBar" + app:elevation="0dp" + > + android:layout_height="?android:attr/actionBarSize" + android:background="@color/transparent" + app:elevation="0dp" + app:contentInsetEnd="12dp" + app:contentInsetStart="12dp" + app:popupTheme="@style/appActionBarPopupMenu"> + + + + > + + diff --git a/app/src/main/res/layout/controller_conversations_rv.xml b/app/src/main/res/layout/controller_conversations_rv.xml index 24d3206c6..5b51d8cac 100644 --- a/app/src/main/res/layout/controller_conversations_rv.xml +++ b/app/src/main/res/layout/controller_conversations_rv.xml @@ -38,7 +38,8 @@ android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" - tools:listitem="@layout/rv_item_conversation_with_last_message" /> + tools:listitem="@layout/rv_item_conversation_with_last_message" + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> diff --git a/app/src/main/res/layout/search_layout.xml b/app/src/main/res/layout/search_layout.xml new file mode 100644 index 000000000..a28bde9aa --- /dev/null +++ b/app/src/main/res/layout/search_layout.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8077eec0e..0f59d7621 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,6 +46,7 @@ Never joined Search + Search conversations Check out the certificate Do you trust the until now unknown SSL certificate, issued by %1$s for %2$s, valid from %3$s to %4$s?