Improve party viewmodel

This commit is contained in:
Phillip Thelen 2018-11-08 16:35:19 +01:00
parent 38b90ed49c
commit c5bb9211d2
6 changed files with 309 additions and 15 deletions

View file

@ -69,6 +69,7 @@ import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment;
import com.habitrpg.android.habitica.ui.fragments.setup.WelcomeFragment;
import com.habitrpg.android.habitica.ui.fragments.skills.SkillTasksRecyclerViewFragment;
import com.habitrpg.android.habitica.ui.fragments.skills.SkillsFragment;
import com.habitrpg.android.habitica.ui.fragments.social.ChatFragment;
import com.habitrpg.android.habitica.ui.fragments.social.ChatListFragment;
import com.habitrpg.android.habitica.ui.fragments.social.GroupInformationFragment;
import com.habitrpg.android.habitica.ui.fragments.social.GuildFragment;
@ -299,4 +300,6 @@ public interface AppComponent {
void inject(@NotNull VerifyUsernameActivity verifyUsernameActivity);
void inject(@NotNull GroupViewModel viewModel);
void inject(@NotNull ChatFragment chatFragment);
}

View file

@ -0,0 +1,249 @@
package com.habitrpg.android.habitica.ui.fragments.social
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.RemoteConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.social.ChatRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Consumer
import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_chat.*
import kotlinx.android.synthetic.main.tavern_chat_new_entry_item.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@SuppressLint("ValidFragment")
class ChatFragment(private var viewModel: PartyViewModel) : BaseFragment(), SwipeRefreshLayout.OnRefreshListener {
@Inject
lateinit var configManager: RemoteConfigManager
private var isTavern: Boolean = false
internal var layoutManager: LinearLayoutManager? = null
internal var groupId: String? = null
private var user: User? = null
private var userId: String? = null
private var chatAdapter: ChatRecyclerViewAdapter? = null
private var navigatedOnceToFragment = false
private var isScrolledToTop = true
private var refreshDisposable: Disposable? = null
fun configure(groupId: String, user: User?, isTavern: Boolean) {
this.groupId = groupId
this.user = user
if (this.user != null) {
this.userId = this.user?.id
}
this.isTavern = isTavern
}
override fun injectFragment(component: AppComponent) {
component.inject(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
refreshLayout.setOnRefreshListener(this)
layoutManager = recyclerView.layoutManager as? androidx.recyclerview.widget.LinearLayoutManager
if (layoutManager == null) {
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
recyclerView.layoutManager = layoutManager
}
chatAdapter = ChatRecyclerViewAdapter(null, true, user, true, configManager.enableUsernameRelease())
chatAdapter.notNull {adapter ->
compositeSubscription.add(adapter.getUserLabelClickFlowable().subscribe(Consumer { userId ->
context.notNull { FullProfileActivity.open(it, userId) }
}, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe(Consumer { this.showDeleteConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe(Consumer { this.showFlagConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getReplyMessageEvents().subscribe(Consumer{ setReplyTo(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe(Consumer { this.copyMessageToClipboard(it) }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(adapter.getLikeMessageFlowable().subscribe(Consumer { viewModel.likeMessage(it) }, RxErrorHandler.handleEmptyError()))
}
chatBarView.sendAction = { sendChatMessage(it) }
chatBarView.maxChatLength = configManager.maxChatLength()
recyclerView.adapter = chatAdapter
recyclerView.itemAnimator = SafeDefaultItemAnimator()
compositeSubscription.add(viewModel.getChatMessages().firstElement().subscribe(Consumer<RealmResults<ChatMessage>> { this.setChatMessages(it) }, RxErrorHandler.handleEmptyError()))
if (user?.flags?.isCommunityGuidelinesAccepted == true) {
communityGuidelinesView.visibility = View.GONE
} else {
communityGuidelinesView.setOnClickListener { _ ->
val i = Intent(Intent.ACTION_VIEW)
i.data = "https://habitica.com/static/community-guidelines".toUri()
context?.startActivity(i)
viewModel.updateUser(user, "flags.communityGuidelinesAccepted", true)
}
}
recyclerView.addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: androidx.recyclerview.widget.RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
isScrolledToTop = layoutManager?.findFirstVisibleItemPosition() == 0
}
})
refresh(false)
}
override fun onDestroyView() {
super.onDestroyView()
stopAutoRefreshing()
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
startAutoRefreshing()
} else {
stopAutoRefreshing()
}
}
private fun startAutoRefreshing() {
if (refreshDisposable != null && refreshDisposable?.isDisposed != true) {
refreshDisposable?.dispose()
}
refreshDisposable = Observable.interval(30, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(Consumer {
refresh(false)
}, RxErrorHandler.handleEmptyError())
}
private fun stopAutoRefreshing() {
if (refreshDisposable?.isDisposed != true) {
refreshDisposable?.dispose()
refreshDisposable = null
}
}
private fun setReplyTo(username: String?) {
val previousText = chatEditText.text.toString()
if (previousText.contains("@$username")) {
return
}
chatEditText.setText("@$username $previousText", TextView.BufferType.EDITABLE)
}
override fun onRefresh() {
refresh(true)
}
private fun refresh(isUserInitiated: Boolean) {
if (isUserInitiated) {
refreshLayout.isRefreshing = true
}
viewModel.retrieveGroupChat {
refreshLayout?.isRefreshing = false
if (isScrolledToTop) {
recyclerView.scrollToPosition(0)
}
}
}
fun setNavigatedToFragment() {
navigatedOnceToFragment = true
markMessagesAsSeen()
}
private fun markMessagesAsSeen() {
if (navigatedOnceToFragment) {
viewModel.markMessagesSeen()
}
}
private fun copyMessageToClipboard(chatMessage: ChatMessage) {
val clipMan = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
val messageText = ClipData.newPlainText("Chat message", chatMessage.text)
clipMan?.primaryClip = messageText
val activity = activity as? MainActivity
if (activity != null) {
showSnackbar(activity.floatingMenuWrapper, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL)
}
}
private fun showFlagConfirmationDialog(chatMessage: ChatMessage) {
val context = context
if (context != null) {
val builder = AlertDialog.Builder(context)
builder.setMessage(R.string.chat_flag_confirmation)
.setPositiveButton(R.string.flag_confirm) { _, _ ->
viewModel.flagMessage(chatMessage) {
val activity = activity as? MainActivity
activity?.floatingMenuWrapper.notNull {
showSnackbar(it, "Flagged message by " + chatMessage.user, SnackbarDisplayType.NORMAL)
}
}
}
.setNegativeButton(R.string.action_cancel) { _, _ -> }
builder.show()
}
}
private fun showDeleteConfirmationDialog(chatMessage: ChatMessage) {
val context = context
if (context != null) {
AlertDialog.Builder(context)
.setTitle(R.string.confirm_delete_tag_title)
.setMessage(R.string.confirm_delete_tag_message)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.yes) { _, _ -> viewModel.deleteMessage(chatMessage) }
.setNegativeButton(android.R.string.no, null).show()
}
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("userId", this.userId)
outState.putString("groupId", this.groupId)
outState.putBoolean("isTavern", this.isTavern)
super.onSaveInstanceState(outState)
}
private fun setChatMessages(chatMessages: RealmResults<ChatMessage>) {
chatAdapter?.updateData(chatMessages)
recyclerView.scrollToPosition(0)
viewModel.gotNewMessages = true
markMessagesAsSeen()
}
private fun sendChatMessage(chatText: String) {
viewModel.postGroupChat(chatText) {
recyclerView?.scrollToPosition(0)
}
}
}

View file

@ -10,29 +10,27 @@ import androidx.fragment.app.FragmentPagerAdapter
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.viewpager.widget.ViewPager
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.activities.GroupFormActivity
import com.habitrpg.android.habitica.ui.activities.PartyInviteActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.fragments.social.ChatFragment
import com.habitrpg.android.habitica.ui.fragments.social.ChatListFragment
import com.habitrpg.android.habitica.ui.fragments.social.GroupInformationFragment
import com.habitrpg.android.habitica.ui.helpers.bindView
import com.habitrpg.android.habitica.ui.helpers.resetViews
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewType
import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
import io.reactivex.functions.Consumer
import java.util.*
class PartyFragment : BaseMainFragment() {
private val viewPager: ViewPager? by bindView(R.id.viewPager)
private var partyMemberListFragment: PartyMemberListFragment? = null
private var chatListFragment: ChatListFragment? = null
private var chatFragment: ChatFragment? = null
private var viewPagerAdapter: androidx.fragment.app.FragmentPagerAdapter? = null
private lateinit var viewModel: PartyViewModel
@ -99,7 +97,7 @@ class PartyFragment : BaseMainFragment() {
partyMemberListFragment?.setPartyId(group.id)
chatListFragment?.groupId = group.id
chatFragment?.groupId = group.id
this.activity?.invalidateOptionsMenu()
}
@ -219,13 +217,10 @@ class PartyFragment : BaseMainFragment() {
}
}
1 -> {
if (chatListFragment == null) {
chatListFragment = ChatListFragment()
if (user?.hasParty() == true) {
chatListFragment?.configure(user?.party?.id ?: "", user, false)
}
if (chatFragment == null) {
chatFragment = ChatFragment(viewModel)
}
fragment = chatListFragment
fragment = chatFragment
}
2 -> {
if (partyMemberListFragment == null) {
@ -264,13 +259,13 @@ class PartyFragment : BaseMainFragment() {
viewPager?.addOnPageChangeListener(object : androidx.viewpager.widget.ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
if (position == 1) {
chatListFragment?.setNavigatedToFragment()
chatFragment?.setNavigatedToFragment()
}
}
override fun onPageSelected(position: Int) {
if (position == 1) {
chatListFragment?.setNavigatedToFragment()
chatFragment?.setNavigatedToFragment()
}
}

View file

@ -48,4 +48,8 @@ abstract class BaseViewModel: ViewModel() {
private fun loadUserFromLocal() {
disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread()).subscribe(Consumer { user.value = it }, RxErrorHandler.handleEmptyError()))
}
fun updateUser(user: User?, path: String, value: Any) {
disposable.add(userRepository.updateUser(user, path, value).subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
}
}

View file

@ -11,6 +11,8 @@ import com.habitrpg.android.habitica.extensions.*
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
@ -41,6 +43,7 @@ open class GroupViewModel: BaseViewModel() {
}
protected val groupIDSubject = BehaviorSubject.create<Optional<String>>()
var gotNewMessages: Boolean = false
override fun inject(component: AppComponent) {
component.inject(this)
@ -112,4 +115,43 @@ open class GroupViewModel: BaseViewModel() {
fun rejectGroupInvite(groupID: String) {
disposable.add(socialRepository.rejectGroupInvite(groupID).subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
}
fun markMessagesSeen() {
groupIDSubject.value?.value.notNull {
if (groupViewType != GroupViewType.TAVERN && it.isNotEmpty() && gotNewMessages) {
socialRepository.markMessagesSeen(it)
}
}
}
fun likeMessage(message: ChatMessage) {
disposable.add(socialRepository.likeMessage(message).subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
}
fun flagMessage(chatMessage: ChatMessage, function: () -> Unit) {
disposable.add(socialRepository.flagMessage(chatMessage)
.subscribe(Consumer {
function()
}, RxErrorHandler.handleEmptyError()))
}
fun deleteMessage(chatMessage: ChatMessage) {
disposable.add(socialRepository.deleteMessage(chatMessage).subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
}
fun postGroupChat(chatText: String, onComplete: () -> Unit?) {
groupIDSubject.value?.value.notNull {
disposable.add(socialRepository.postGroupChat(it, chatText).subscribe(Consumer {
onComplete()
}, RxErrorHandler.handleEmptyError()))
}
}
fun retrieveGroupChat(onComplete: () -> Unit) {
groupIDSubject.value?.value.notNull {
disposable.add(socialRepository.retrieveGroupChat(it).subscribe(Consumer {
onComplete()
}, RxErrorHandler.handleEmptyError()))
}
}
}

View file

@ -2,16 +2,17 @@ package com.habitrpg.android.habitica.ui.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.inventory.Quest
import io.reactivex.functions.Consumer
import kotlinx.android.synthetic.main.fragment_chat.*
class PartyViewModel: GroupViewModel() {
private val quest: MutableLiveData<Quest?> = MutableLiveData()
private val quest = Transformations.map(getGroupData()) { it?.quest }
internal val isQuestActive: Boolean
get() = quest.value?.active == true