Lots of progress on reworked conversations

This commit is contained in:
Mario Danic 2020-01-03 22:58:01 +01:00
parent af04c95bab
commit 9d6feca3f9
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
16 changed files with 380 additions and 368 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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 modules 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'
}

View File

@ -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"
}

View File

@ -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())
}
}
}
}
}
}

View File

@ -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) {
}

View File

@ -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,11 +214,20 @@ 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 {
return model.displayName != null && Pattern.compile(
constraint, Pattern.CASE_INSENSITIVE or Pattern.LITERAL

View File

@ -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)) {

View File

@ -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()

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>