mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-19 10:45:13 +01:00
Lots of progress on reworked conversations
This commit is contained in:
parent
af04c95bab
commit
9d6feca3f9
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
|
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/talk-android.iml" filepath="$PROJECT_DIR$/talk-android.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'findbugs'
|
||||
//apply plugin: 'findbugs'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
@ -82,9 +82,26 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
/*buildFeatures {
|
||||
// Determines whether to enable support for Jetpack Compose.
|
||||
compose = false
|
||||
// Determines whether to generate a BuildConfig class.
|
||||
buildConfig = true
|
||||
// Determines whether to support View Binding.
|
||||
// Note that the viewBinding.enabled property is now deprecated.
|
||||
viewBinding = true
|
||||
// Determines whether to support Data Binding.
|
||||
// Note that the dataBinding.enabled property is now deprecated.
|
||||
dataBinding = true
|
||||
// Determines whether to generate binder classes for your AIDL files.
|
||||
aidl = true
|
||||
// Determines whether to support RenderScript.
|
||||
renderScript = true
|
||||
// Determines whether to support injecting custom variables into the module’s R class.
|
||||
resValues = true
|
||||
// Determines whether to support shader AOT compilation.
|
||||
shaders = true
|
||||
}*/
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
@ -134,27 +151,6 @@ android {
|
||||
htmlOutput file("$project.buildDir/reports/lint/lint.html")
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
task findbugs(type: FindBugs) {
|
||||
ignoreFailures = false
|
||||
effort = "max"
|
||||
reportLevel = "medium"
|
||||
classes = fileTree("$project.buildDir/intermediates/javac/gplayDebug/classes/com/nextcloud")
|
||||
excludeFilter = file("${project.rootDir}/findbugs-filter.xml")
|
||||
source = fileTree('src/main/java')
|
||||
pluginClasspath = project.configurations.findbugsPlugins
|
||||
classpath = files()
|
||||
include '**/*.java'
|
||||
exclude '**/gen/**'
|
||||
|
||||
reports {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
html {
|
||||
destination = file("$project.buildDir/reports/findbugs/findbugs.html")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
@ -242,7 +238,7 @@ dependencies {
|
||||
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||
|
||||
implementation 'androidx.biometric:biometric:1.0.0'
|
||||
implementation 'androidx.biometric:biometric:1.0.1'
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
|
||||
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
@ -278,7 +274,7 @@ dependencies {
|
||||
kapt 'io.requery:requery-processor:1.5.1'
|
||||
implementation 'net.orange-box.storebox:storebox-lib:1.4.0'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.10'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.10'
|
||||
annotationProcessor "org.projectlombok:lombok:1.18.10"
|
||||
kapt "org.projectlombok:lombok:1.18.10"
|
||||
|
||||
implementation 'com.jakewharton:butterknife:10.2.0'
|
||||
@ -287,6 +283,7 @@ dependencies {
|
||||
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
||||
implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
|
||||
implementation 'eu.davidea:flexible-adapter-livedata:1.0.0-b3'
|
||||
implementation 'com.otaliastudios:elements:0.3.7'
|
||||
implementation 'org.webrtc:google-webrtc:1.0.23295'
|
||||
implementation 'com.yarolegovich:lovely-dialog:1.1.0'
|
||||
implementation 'com.yarolegovich:lovelyinput:1.0.9'
|
||||
@ -327,9 +324,6 @@ dependencies {
|
||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.3.0-alpha02', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
findbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.0'
|
||||
findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
|
||||
implementation 'com.github.Kennyc1012:BottomSheet:2.4.1'
|
||||
implementation 'com.google.firebase:firebase-messaging:20.1.0'
|
||||
}
|
||||
|
@ -19,6 +19,6 @@
|
||||
*/
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.work:work-gcm:2.3.0-beta01"
|
||||
implementation "androidx.work:work-gcm:2.3.0-beta02"
|
||||
implementation "com.google.firebase:firebase-messaging:20.1.0"
|
||||
}
|
||||
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
*
|
||||
* * Nextcloud Talk application
|
||||
* *
|
||||
* * @author Mario Danic
|
||||
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||
* *
|
||||
* * 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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.newarch.local.models.getCredentials
|
||||
import com.nextcloud.talk.newarch.services.GlobalService
|
||||
import com.nextcloud.talk.newarch.utils.Images
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.otaliastudios.elements.Element
|
||||
import com.otaliastudios.elements.Page
|
||||
import com.otaliastudios.elements.Presenter
|
||||
import kotlinx.android.synthetic.main.rv_item_conversation_with_last_message.view.*
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
|
||||
open class ConversationsPresenter(context: Context, onElementClick: ((Page, Holder, Element<Conversation>) -> Unit)?) : Presenter<Conversation>(context, onElementClick), KoinComponent {
|
||||
private val globalService: GlobalService by inject()
|
||||
|
||||
override val elementTypes: Collection<Int>
|
||||
get() = listOf(0)
|
||||
|
||||
override fun onCreate(parent: ViewGroup, elementType: Int): Holder {
|
||||
return Holder(getLayoutInflater().inflate(R.layout.rv_item_conversation_with_last_message, parent, false))
|
||||
}
|
||||
|
||||
override fun onBind(page: Page, holder: Holder, element: Element<Conversation>, payloads: List<Any>) {
|
||||
super.onBind(page, holder, element, payloads)
|
||||
val conversation = element.data
|
||||
val user = globalService.currentUserLiveData.value
|
||||
|
||||
user?.let { user ->
|
||||
conversation?.let { conversation ->
|
||||
val appContext = NextcloudTalkApplication.sharedApplication!!.applicationContext
|
||||
|
||||
if (conversation.changing) {
|
||||
holder.itemView.actionProgressBar!!.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.itemView.actionProgressBar!!.visibility = View.GONE
|
||||
}
|
||||
|
||||
holder.itemView.dialogName!!.text = conversation.displayName
|
||||
|
||||
if (conversation.unreadMessages > 0) {
|
||||
holder.itemView.dialogUnreadBubble!!.visibility = View.VISIBLE
|
||||
if (conversation.unreadMessages < 100) {
|
||||
holder.itemView.dialogUnreadBubble!!.text = conversation.unreadMessages.toLong()
|
||||
.toString()
|
||||
} else {
|
||||
holder.itemView.dialogUnreadBubble!!.text = context.getString(R.string.nc_99_plus)
|
||||
}
|
||||
|
||||
if (conversation.unreadMention) {
|
||||
holder.itemView.dialogUnreadBubble!!.background =
|
||||
context.getDrawable(R.drawable.bubble_circle_unread_mention)
|
||||
} else {
|
||||
holder.itemView.dialogUnreadBubble!!.background =
|
||||
context.getDrawable(R.drawable.bubble_circle_unread)
|
||||
}
|
||||
} else {
|
||||
holder.itemView.dialogUnreadBubble!!.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (conversation.hasPassword) {
|
||||
holder.itemView.passwordProtectedRoomImageView!!.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.itemView.passwordProtectedRoomImageView!!.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (conversation.favorite) {
|
||||
holder.itemView.favoriteConversationImageView!!.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.itemView.favoriteConversationImageView!!.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (conversation.lastMessage != null) {
|
||||
holder.itemView.dialogDate!!.visibility = View.VISIBLE
|
||||
holder.itemView.dialogDate!!.text = DateUtils.getRelativeTimeSpanString(
|
||||
conversation.lastActivity * 1000L,
|
||||
System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
|
||||
if (!TextUtils.isEmpty(
|
||||
conversation.lastMessage!!.systemMessage
|
||||
) || Conversation.ConversationType.SYSTEM_CONVERSATION == conversation.type
|
||||
) {
|
||||
holder.itemView.dialogLastMessage!!.text = conversation.lastMessage!!.text
|
||||
} else {
|
||||
var authorDisplayName = ""
|
||||
conversation.lastMessage!!.activeUser = user
|
||||
val text: String
|
||||
if (conversation.lastMessage!!
|
||||
.messageType == ChatMessage.MessageType.REGULAR_TEXT_MESSAGE && (!(Conversation.ConversationType.ONE_TO_ONE_CONVERSATION).equals(
|
||||
conversation.type) || conversation.lastMessage!!.actorId == user.userId)
|
||||
) {
|
||||
if (conversation.lastMessage!!.actorId == user.userId) {
|
||||
text = String.format(
|
||||
appContext.getString(R.string.nc_formatted_message_you),
|
||||
conversation.lastMessage!!.lastMessageDisplayText
|
||||
)
|
||||
} else {
|
||||
authorDisplayName = if (!TextUtils.isEmpty(conversation.lastMessage!!.actorDisplayName))
|
||||
conversation.lastMessage!!.actorDisplayName
|
||||
else if ("guests" == conversation.lastMessage!!.actorType)
|
||||
appContext.getString(R.string.nc_guest)
|
||||
else
|
||||
""
|
||||
text = String.format(
|
||||
appContext.getString(R.string.nc_formatted_message),
|
||||
authorDisplayName,
|
||||
conversation.lastMessage!!.lastMessageDisplayText
|
||||
)
|
||||
}
|
||||
} else {
|
||||
text = conversation.lastMessage!!.lastMessageDisplayText
|
||||
}
|
||||
|
||||
holder.itemView.dialogLastMessage.text = text
|
||||
}
|
||||
} else {
|
||||
holder.itemView.dialogDate.visibility = View.GONE
|
||||
holder.itemView.dialogLastMessage.setText(R.string.nc_no_messages_yet)
|
||||
}
|
||||
|
||||
val conversationDrawable: Drawable? = Images().getImageForConversation(context, conversation)
|
||||
|
||||
conversationDrawable?.let {
|
||||
holder.itemView.dialogAvatar.load(conversationDrawable)
|
||||
}?: run {
|
||||
holder.itemView.dialogAvatar.load(ApiUtils.getUrlForAvatarWithName(
|
||||
user.baseUrl,
|
||||
conversation.name, R.dimen.avatar_size))
|
||||
{
|
||||
addHeader("Authorization", user.getCredentials())
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
*
|
||||
* * Nextcloud Talk application
|
||||
* *
|
||||
* * @author Mario Danic
|
||||
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||
* *
|
||||
* * 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.otaliastudios.elements.extensions.LiveDataSource
|
||||
|
||||
class ConversationsSource(data: LiveData<List<Conversation>>, elementType: Int) : LiveDataSource<Conversation>(data, elementType) {
|
||||
}
|
@ -24,7 +24,9 @@ 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
|
||||
@ -212,9 +214,18 @@ class ConversationItem(
|
||||
addHeader("Authorization", user.getCredentials())
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onViewAttached(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>?, holder: ConversationItemViewHolder?, position: Int) {
|
||||
super.onViewAttached(adapter, holder, position)
|
||||
Log.d("MAriO", model.displayName!!)
|
||||
}
|
||||
|
||||
override fun onViewDetached(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>?, holder: ConversationItemViewHolder?, position: Int) {
|
||||
super.onViewDetached(adapter, holder, position)
|
||||
Log.d("MAriO DETACH", model.displayName!!)
|
||||
}
|
||||
|
||||
override fun filter(constraint: String): Boolean {
|
||||
|
@ -54,7 +54,7 @@ public class MentionAutocompleteCallback implements AutocompleteCallback<Mention
|
||||
if (range == null) return false;
|
||||
int start = range[0];
|
||||
int end = range[1];
|
||||
String replacement = item.getLabel();
|
||||
String replacement = item.label;
|
||||
|
||||
StringBuilder replacementStringBuilder = new StringBuilder(item.getLabel());
|
||||
for (EmojiRange emojiRange : EmojiUtils.emojis(replacement)) {
|
||||
|
@ -47,7 +47,7 @@ import java.util.*
|
||||
|
||||
abstract class BaseController : ButterKnifeController(), ComponentCallbacks {
|
||||
|
||||
val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
||||
open val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
||||
|
||||
val appPreferences: AppPreferences by inject()
|
||||
val context: Context by inject()
|
||||
|
@ -20,63 +20,44 @@
|
||||
|
||||
package com.nextcloud.talk.newarch.features.conversationsList
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.SearchView.OnQueryTextListener
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.observe
|
||||
import butterknife.OnClick
|
||||
import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat
|
||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.R.drawable
|
||||
import com.nextcloud.talk.adapters.items.ConversationItem
|
||||
import com.nextcloud.talk.adapters.ConversationsPresenter
|
||||
import com.nextcloud.talk.controllers.ContactsController
|
||||
import com.nextcloud.talk.controllers.SettingsController
|
||||
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
|
||||
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
|
||||
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewNetworkState.*
|
||||
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
||||
import com.nextcloud.talk.utils.ConductorRemapping
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.ShareUtils
|
||||
import com.nextcloud.talk.utils.animations.SharedElementTransition
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener
|
||||
import com.otaliastudios.elements.*
|
||||
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.*
|
||||
import kotlinx.android.synthetic.main.fast_scroller.view.*
|
||||
import kotlinx.android.synthetic.main.view_states.view.*
|
||||
import kotlinx.android.synthetic.main.message_state.view.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.parceler.Parcels
|
||||
import java.util.*
|
||||
|
||||
class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
OnItemClickListener, OnItemLongClickListener {
|
||||
class ConversationsListView : BaseView() {
|
||||
|
||||
override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
||||
|
||||
private lateinit var viewModel: ConversationsListViewModel
|
||||
val factory: ConversationListViewModelFactory by inject()
|
||||
|
||||
private val recyclerViewAdapter = FlexibleAdapter(mutableListOf(), this, true)
|
||||
|
||||
private var searchItem: MenuItem? = null
|
||||
private var settingsItem: MenuItem? = null
|
||||
private var searchView: SearchView? = null
|
||||
@ -88,19 +69,11 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.menu_conversation_plus_filter, menu)
|
||||
searchItem = menu.findItem(R.id.action_search)
|
||||
initSearchView()
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
if (recyclerViewAdapter.hasFilter()) {
|
||||
searchItem?.expandActionView()
|
||||
searchView?.setQuery(viewModel.searchQuery.value, false)
|
||||
recyclerViewAdapter.filterItems()
|
||||
}
|
||||
|
||||
settingsItem = menu.findItem(R.id.action_settings)
|
||||
searchItem?.isVisible = searchItem?.isVisible == false && !recyclerViewAdapter.isEmpty
|
||||
viewModel.loadAvatar()
|
||||
}
|
||||
|
||||
@ -128,7 +101,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
/*private fun initSearchView() {
|
||||
val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
searchView = MenuItemCompat.getActionView(searchItem) as SearchView
|
||||
searchView!!.maxWidth = Integer.MAX_VALUE
|
||||
@ -154,7 +127,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
return onQueryTextSubmit(newText)
|
||||
}
|
||||
}*/
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@ -164,114 +137,58 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
actionBar?.show()
|
||||
|
||||
viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java)
|
||||
|
||||
val view = super.onCreateView(inflater, container)
|
||||
|
||||
val adapter = Adapter.builder(this)
|
||||
.addSource(Source.fromLiveData(viewModel.conversationsLiveData))
|
||||
.addPresenter(ConversationsPresenter(context, ::onElementClick))
|
||||
.addPresenter(Presenter.forLoadingIndicator(context, R.layout.loading_state))
|
||||
.addPresenter(Presenter.forEmptyIndicator(context, R.layout.message_state))
|
||||
.addPresenter(Presenter.forErrorIndicator(context, R.layout.message_state) { view, throwable ->
|
||||
view.messageStateTextView.setText(R.string.nc_oops)
|
||||
view.messageStateImageView.setImageDrawable(context.getDrawable(drawable.ic_announcement_white_24dp))
|
||||
})
|
||||
.into(view.recyclerView)
|
||||
view.recyclerView.initRecyclerView(SmoothScrollLinearLayoutManager(activity), adapter, false)
|
||||
|
||||
view.apply {
|
||||
recyclerView.initRecyclerView(
|
||||
SmoothScrollLinearLayoutManager(activity), recyclerViewAdapter, false)
|
||||
recyclerViewAdapter.fastScroller = fast_scroller
|
||||
recyclerView.initRecyclerView(SmoothScrollLinearLayoutManager(activity), adapter, false)
|
||||
|
||||
swipeRefreshLayoutView.setOnRefreshListener {
|
||||
view.swipeRefreshLayoutView.isRefreshing = false
|
||||
viewModel.loadConversations()
|
||||
}
|
||||
|
||||
swipeRefreshLayoutView.setColorSchemeResources(R.color.colorPrimary)
|
||||
fast_scroller.setBubbleTextCreator { position ->
|
||||
var displayName =
|
||||
(recyclerViewAdapter.getItem(position) as ConversationItem).model.displayName
|
||||
|
||||
if (displayName!!.length > 8) {
|
||||
displayName = displayName.substring(0, 4) + "..."
|
||||
}
|
||||
|
||||
displayName
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.apply {
|
||||
currentUserAvatar.observe(this@ConversationsListView, Observer { value ->
|
||||
settingsItem?.icon = value
|
||||
})
|
||||
|
||||
conversationsLiveData.observe(this@ConversationsListView, Observer {
|
||||
val isListEmpty = it.isNullOrEmpty()
|
||||
|
||||
if (isListEmpty) {
|
||||
view.stateWithMessageView?.errorStateTextView?.text =
|
||||
resources?.getText(R.string.nc_conversations_empty)
|
||||
view.stateWithMessageView?.errorStateImageView?.setImageResource(drawable.ic_logo)
|
||||
}
|
||||
|
||||
view.stateWithMessageView?.visibility = if (isListEmpty && networkStateLiveData.value != LOADING) View.VISIBLE else View.GONE
|
||||
|
||||
if (view.floatingActionButton?.isShown == false) {
|
||||
view.floatingActionButton?.show()
|
||||
}
|
||||
|
||||
searchItem?.isVisible = searchItem?.isVisible == false && !isListEmpty
|
||||
|
||||
val newConversations = mutableListOf<ConversationItem>()
|
||||
for (conversation in it) {
|
||||
newConversations.add(
|
||||
ConversationItem(
|
||||
conversation, globalService.currentUserLiveData.value!!,
|
||||
activity!!
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
recyclerViewAdapter.updateDataSet(
|
||||
newConversations as List<IFlexible<ConversationItem.ConversationItemViewHolder>>, false
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
networkStateLiveData.observe(this@ConversationsListView, Observer { value ->
|
||||
when (value) {
|
||||
LOADING -> {
|
||||
view.post {
|
||||
view.loadingStateView?.visibility = View.VISIBLE
|
||||
view.recyclerView?.visibility = View.GONE
|
||||
view.stateWithMessageView?.visibility = View.GONE
|
||||
view.floatingActionButton?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
LOADED -> {
|
||||
// awesome, but we delegate the magic stuff to the data handler
|
||||
view.post {
|
||||
view.loadingStateView?.visibility = View.GONE
|
||||
view.recyclerView?.visibility = View.VISIBLE
|
||||
view.stateWithMessageView?.visibility = if (recyclerViewAdapter.isEmpty) View.VISIBLE else View.GONE
|
||||
view.floatingActionButton?.visibility = View.VISIBLE
|
||||
if (view.floatingActionButton?.isShown == false) {
|
||||
view.floatingActionButton?.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
FAILED -> {
|
||||
// probably offline, so what? :)
|
||||
view.post {
|
||||
view.loadingStateView?.visibility = View.GONE
|
||||
view.recyclerView?.visibility = View.VISIBLE
|
||||
view.floatingActionButton?.visibility = View.GONE
|
||||
view.stateWithMessageView?.visibility = if (recyclerViewAdapter.isEmpty) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// We should not be here
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
searchQuery.observe(this@ConversationsListView, Observer {
|
||||
recyclerViewAdapter.setFilter(it)
|
||||
recyclerViewAdapter.filterItems(500)
|
||||
})
|
||||
viewModel.avatar.observe(this@ConversationsListView) { avatar ->
|
||||
settingsItem?.icon = avatar
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun onElementClick(page: Page, holder: Presenter.Holder, element: Element<Conversation>) {
|
||||
val conversation = element.data
|
||||
val user = viewModel.globalService.currentUserLiveData.value
|
||||
|
||||
user?.let { user ->
|
||||
conversation?.let { conversation ->
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, user)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.token)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId)
|
||||
bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
|
||||
ConductorRemapping.remapChatController(
|
||||
router, user.id!!, conversation.token!!,
|
||||
bundle, false
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLayoutId(): Int {
|
||||
return R.layout.controller_conversations_rv
|
||||
}
|
||||
@ -281,13 +198,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
openNewConversationScreen()
|
||||
}
|
||||
|
||||
@OnClick(R.id.stateWithMessageView)
|
||||
fun onStateWithMessageViewClick() {
|
||||
if (view?.floatingActionButton?.isVisible == true) {
|
||||
openNewConversationScreen()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openNewConversationScreen() {
|
||||
val bundle = Bundle()
|
||||
bundle.putBoolean(BundleKeys.KEY_NEW_CONVERSATION, true)
|
||||
@ -298,31 +208,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getShareIntentForConversation(conversation: Conversation): Intent {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(
|
||||
Intent.EXTRA_SUBJECT,
|
||||
String.format(
|
||||
context.getString(R.string.nc_share_subject),
|
||||
context.getString(R.string.nc_app_name)
|
||||
)
|
||||
)
|
||||
|
||||
// TODO, make sure we ask for password if needed
|
||||
putExtra(
|
||||
Intent.EXTRA_TEXT, ShareUtils.getStringForIntent(
|
||||
context, null, conversation
|
||||
)
|
||||
)
|
||||
|
||||
type = "text/plain"
|
||||
}
|
||||
|
||||
// TODO filter our own app once we're there
|
||||
return Intent.createChooser(sendIntent, context.getString(R.string.nc_share_link))
|
||||
}
|
||||
|
||||
private fun getConversationMenuItemsForConversation(conversation: Conversation): MutableList<BasicListItemWithImage> {
|
||||
val items = mutableListOf<BasicListItemWithImage>()
|
||||
|
||||
@ -381,88 +266,4 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
super.onRestoreViewState(view, savedViewState)
|
||||
viewModel.loadConversations()
|
||||
}
|
||||
|
||||
override fun onItemLongClick(position: Int) {
|
||||
val clickedItem = recyclerViewAdapter.getItem(position)
|
||||
clickedItem?.let {
|
||||
val conversation = (it as ConversationItem).model
|
||||
|
||||
activity?.let { activity ->
|
||||
MaterialDialog(activity, BottomSheet(WRAP_CONTENT)).show {
|
||||
cornerRadius(res = R.dimen.corner_radius)
|
||||
title(text = conversation.displayName)
|
||||
listItemsWithImage(getConversationMenuItemsForConversation(conversation)
|
||||
) { dialog,
|
||||
index, item ->
|
||||
|
||||
when (item.iconRes) {
|
||||
drawable.ic_star_border_black_24dp -> {
|
||||
viewModel.changeFavoriteValueForConversation(conversation, false)
|
||||
|
||||
}
|
||||
drawable.ic_star_black_24dp -> {
|
||||
viewModel.changeFavoriteValueForConversation(conversation, true)
|
||||
}
|
||||
drawable.ic_share_black_24dp -> {
|
||||
startActivity(getShareIntentForConversation(conversation))
|
||||
}
|
||||
drawable.ic_exit_to_app_black_24dp -> {
|
||||
MaterialDialog(activity).show {
|
||||
title(R.string.nc_leave)
|
||||
message(R.string.nc_leave_message)
|
||||
positiveButton(R.string.nc_simple_leave) { dialog ->
|
||||
viewModel.leaveConversation(conversation)
|
||||
}
|
||||
negativeButton(R.string.nc_cancel)
|
||||
icon(drawable.ic_exit_to_app_black_24dp)
|
||||
}
|
||||
}
|
||||
drawable.ic_delete_grey600_24dp -> {
|
||||
MaterialDialog(activity).show {
|
||||
title(R.string.nc_delete)
|
||||
message(text = conversation.deleteWarningMessage)
|
||||
positiveButton(R.string.nc_delete_call) { dialog ->
|
||||
viewModel.deleteConversation(conversation)
|
||||
}
|
||||
negativeButton(R.string.nc_cancel)
|
||||
icon(
|
||||
drawable = DisplayUtils.getTintedDrawable(
|
||||
resources!!, drawable
|
||||
.ic_delete_grey600_24dp, R.color.nc_darkRed
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(
|
||||
view: View?,
|
||||
position: Int
|
||||
): Boolean {
|
||||
val clickedItem = recyclerViewAdapter.getItem(position)
|
||||
if (clickedItem != null) {
|
||||
val conversationItem = clickedItem as ConversationItem
|
||||
val conversation = conversationItem.model
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationItem.user)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.token)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId)
|
||||
bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
|
||||
ConductorRemapping.remapChatController(
|
||||
router, conversationItem.user.id!!, conversation.token!!,
|
||||
bundle, false
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
@ -60,9 +60,8 @@ class ConversationsListViewModel constructor(
|
||||
private val conversationsLoadingLock = ReentrantLock()
|
||||
|
||||
var messageData: String? = null
|
||||
val searchQuery = MutableLiveData<String>()
|
||||
val networkStateLiveData: MutableLiveData<ConversationsListViewNetworkState> = MutableLiveData(ConversationsListViewNetworkState.LOADING)
|
||||
val currentUserAvatar: MutableLiveData<Drawable> = MutableLiveData(DisplayUtils.getRoundedDrawable(context.getDrawable(R.drawable.ic_settings_white_24dp)))
|
||||
val avatar: MutableLiveData<Drawable> = MutableLiveData(DisplayUtils.getRoundedDrawable(context.getDrawable(R.drawable.ic_settings_white_24dp)))
|
||||
val conversationsLiveData = Transformations.switchMap(globalService.currentUserLiveData) {
|
||||
if (networkStateLiveData.value != ConversationsListViewNetworkState.LOADING) {
|
||||
networkStateLiveData.postValue(ConversationsListViewNetworkState.LOADING)
|
||||
@ -163,12 +162,12 @@ class ConversationsListViewModel constructor(
|
||||
|
||||
operationUser?.let {
|
||||
viewModelScope.launch {
|
||||
val url = ApiUtils.getUrlForAvatarWithNameAndPixels(it.baseUrl, it.userId, 512)
|
||||
val url = ApiUtils.getUrlForAvatarWithNameAndPixels(it.baseUrl, it.userId, 256)
|
||||
val drawable = Coil.get((url)) {
|
||||
addHeader("Authorization", it.getCredentials())
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
currentUserAvatar.postValue(drawable)
|
||||
avatar.postValue(drawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,6 @@
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<include
|
||||
layout="@layout/view_states"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayoutView"
|
||||
android:layout_width="match_parent"
|
||||
@ -59,6 +54,4 @@
|
||||
app:srcCompat="@drawable/ic_add_white_24px"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<include layout="@layout/fast_scroller" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
37
app/src/main/res/layout/loading_state.xml
Normal file
37
app/src/main/res/layout/loading_state.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ /*
|
||||
~ * Nextcloud Talk application
|
||||
~ *
|
||||
~ * @author Mario Danic
|
||||
~ * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||
~ *
|
||||
~ * 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 <http://www.gnu.org/licenses/>.
|
||||
~ */
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loadingStateView"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="@dimen/activity_horizontal_margin"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:indeterminateTintMode="src_in" />
|
||||
</RelativeLayout>
|
48
app/src/main/res/layout/message_state.xml
Normal file
48
app/src/main/res/layout/message_state.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ /*
|
||||
~ * Nextcloud Talk application
|
||||
~ *
|
||||
~ * @author Mario Danic
|
||||
~ * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||
~ *
|
||||
~ * 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 <http://www.gnu.org/licenses/>.
|
||||
~ */
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageStateImageView"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_above="@id/messageStateTextView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:src="@drawable/ic_logo"
|
||||
android:tint="@color/colorPrimary"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageStateTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_margin="8dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/nc_conversations_empty"
|
||||
android:textSize="20sp"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loadingStateView"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="@dimen/activity_horizontal_margin"
|
||||
android:indeterminate="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:indeterminateTintMode="src_in"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/stateWithMessageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/errorStateImageView"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_above="@id/errorStateTextView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:src="@drawable/ic_announcement_white_24dp"
|
||||
android:tint="@color/colorPrimary"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorStateTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_margin="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -316,6 +316,8 @@
|
||||
<string name="nc_not_defined_error">Unknown error</string>
|
||||
<string name="nc_unauthorized_error">Unauthorized</string>
|
||||
|
||||
<string name="nc_oops">Ooops, something went wrong.</string>
|
||||
|
||||
<string name="nc_general_settings">General</string>
|
||||
<string name="nc_allow_guests">Allow guests</string>
|
||||
<string name="nc_last_moderator_title">Could not leave conversation</string>
|
||||
|
Loading…
Reference in New Issue
Block a user