Twidere-App-Android-Twitter.../twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFrag...

835 lines
35 KiB
Kotlin

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.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 org.mariotaku.twidere.fragment.message
import android.accounts.AccountManager
import android.app.Activity
import android.app.Dialog
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.view.*
import android.widget.CompoundButton
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.loader.app.LoaderManager
import androidx.loader.content.FixedAsyncTaskLoader
import androidx.loader.content.Loader
import androidx.recyclerview.widget.FixedLinearLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import kotlinx.android.synthetic.main.activity_home_content.view.*
import kotlinx.android.synthetic.main.fragment_messages_conversation_info.*
import kotlinx.android.synthetic.main.header_message_conversation_info.view.*
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.*
import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.alwaysUi
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.mapToArray
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.ktextension.setItemTitle
import org.mariotaku.ktextension.spannable
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.TwitterUpload
import org.mariotaku.pickncrop.library.MediaPickerActivity
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.ThemedMediaPickerActivity
import org.mariotaku.twidere.activity.UserSelectorActivity
import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.annotation.ImageShapeStyle
import org.mariotaku.twidere.annotation.ProfileImageSize
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.exception.UnsupportedCountIndexException
import org.mariotaku.twidere.extension.*
import org.mariotaku.twidere.extension.model.*
import org.mariotaku.twidere.extension.view.calculateSpaceItemHeight
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.BaseFragment
import org.mariotaku.twidere.fragment.ProgressDialogFragment
import org.mariotaku.twidere.fragment.iface.IToolBarSupportFragment
import org.mariotaku.twidere.fragment.message.MessageConversationInfoFragment.ConversationInfoAdapter.Companion.VIEW_TYPE_BOTTOM_SPACE
import org.mariotaku.twidere.fragment.message.MessageConversationInfoFragment.ConversationInfoAdapter.Companion.VIEW_TYPE_HEADER
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType
import org.mariotaku.twidere.model.ParcelableMessageConversation.ExtrasType
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
import org.mariotaku.twidere.task.twitter.message.AddParticipantsTask
import org.mariotaku.twidere.task.twitter.message.ClearMessagesTask
import org.mariotaku.twidere.task.twitter.message.DestroyConversationTask
import org.mariotaku.twidere.task.twitter.message.SetConversationNotificationDisabledTask
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.view.holder.SimpleUserViewHolder
import java.lang.ref.WeakReference
/**
* Created by mariotaku on 2017/2/15.
*/
class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
LoaderManager.LoaderCallbacks<ParcelableMessageConversation?> {
private val accountKey: UserKey get() = arguments?.getParcelable(EXTRA_ACCOUNT_KEY)!!
private val conversationId: String get() = arguments?.getString(EXTRA_CONVERSATION_ID)!!
private lateinit var adapter: ConversationInfoAdapter
private lateinit var itemDecoration: ConversationInfoDecoration
override val controlBarHeight: Int get() = toolbar.measuredHeight
override var controlBarOffset: Float = 0f
override val toolbar: Toolbar
get() = toolbarLayout.toolbar
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val context = context ?: return
setHasOptionsMenu(true)
val activity = this.activity
if (activity is AppCompatActivity) {
activity.supportActionBar?.setDisplayShowTitleEnabled(false)
}
val theme = Chameleon.getOverrideTheme(context, activity)
adapter = ConversationInfoAdapter(context, requestManager)
adapter.listener = object : ConversationInfoAdapter.Listener {
override fun onUserClick(position: Int) {
val user = adapter.getUser(position) ?: return
startActivity(IntentUtils.userProfile(user))
}
override fun onAddUserClick(position: Int) {
val conversation = adapter.conversation ?: return
val intent = Intent(INTENT_ACTION_SELECT_USER)
intent.putExtra(EXTRA_ACCOUNT_KEY, conversation.account_key)
intent.setClass(context, UserSelectorActivity::class.java)
startActivityForResult(intent, REQUEST_CONVERSATION_ADD_USER)
}
override fun onDisableNotificationChanged(disabled: Boolean) {
performSetNotificationDisabled(disabled)
}
}
itemDecoration = ConversationInfoDecoration(adapter,
resources.getDimensionPixelSize(R.dimen.element_spacing_large)
)
recyclerView.adapter = adapter
recyclerView.layoutManager = LayoutManager(context)
recyclerView.addItemDecoration(itemDecoration)
val profileImageStyle = preferences[profileImageStyleKey]
appBarIcon.style = profileImageStyle
conversationAvatar.style = profileImageStyle
toolbarLayout.setStatusBarScrimColor(theme.statusBarColor)
coordinatorLayout.setStatusBarBackgroundColor(theme.statusBarColor)
val avatarBackground = ChameleonUtils.getColorDependent(theme.colorToolbar)
appBarIcon.setShapeBackground(avatarBackground)
appBarTitle.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
appBarSubtitle.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
conversationAvatar.setShapeBackground(avatarBackground)
conversationTitle.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
conversationSubtitle.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
editButton.setOnClickListener {
executeAfterFragmentResumed { fragment ->
val df = EditInfoDialogFragment()
df.show(fragment.childFragmentManager, "edit_info")
}
}
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CONVERSATION_ADD_USER -> {
if (resultCode == Activity.RESULT_OK && data != null) {
data.getParcelableExtra<ParcelableUser>(EXTRA_USER)?.let { user ->
performAddParticipant(user)
}
}
}
REQUEST_PICK_MEDIA -> {
when (resultCode) {
Activity.RESULT_OK -> {
val uri = MediaPickerActivity.getMediaUris(data).firstOrNull() ?: return
performSetConversationAvatar(uri)
}
RESULT_CODE_REMOVE_CONVERSATION_AVATAR -> {
performSetConversationAvatar(null)
}
}
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_messages_conversation_info, container, false)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_messages_conversation_info, menu)
}
override fun onApplySystemWindowInsets(insets: Rect) {
// No-op
}
override fun onPrepareOptionsMenu(menu: Menu) {
menu.setItemAvailability(R.id.clear_messages, true)
menu.setItemAvailability(R.id.leave_conversation, true)
if (adapter.conversation?.conversation_extras_type == ExtrasType.TWITTER_OFFICIAL) {
menu.setItemTitle(R.id.leave_conversation, R.string.action_leave_conversation)
} else {
menu.setItemTitle(R.id.leave_conversation, R.string.action_clear_messages_and_delete_conversation)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.leave_conversation -> {
val df = DestroyConversationConfirmDialogFragment()
df.show(childFragmentManager, "destroy_conversation_confirm")
return true
}
R.id.clear_messages -> {
val df = ClearMessagesConfirmDialogFragment()
df.show(childFragmentManager, "clear_messages_confirm")
return true
}
}
return false
}
override fun setupWindow(activity: FragmentActivity): Boolean {
return false
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<ParcelableMessageConversation?> {
return ConversationInfoLoader(requireContext(), accountKey, conversationId)
}
override fun onLoaderReset(loader: Loader<ParcelableMessageConversation?>) {
}
override fun onLoadFinished(loader: Loader<ParcelableMessageConversation?>, data: ParcelableMessageConversation?) {
if (data == null) {
activity?.finish()
return
}
val context = context ?: return
val name = data.getTitle(context, userColorNameManager, preferences[nameFirstKey]).first
val summary = data.getSubtitle(context)
@ImageShapeStyle val profileImageStyle = preferences[profileImageStyleKey]
requestManager.loadProfileImage(context, data, profileImageStyle).into(conversationAvatar)
requestManager.loadProfileImage(context, data, profileImageStyle, 0f,
0f, ProfileImageSize.REASONABLY_SMALL).into(appBarIcon)
appBarTitle.spannable = name
conversationTitle.spannable = name
if (summary != null) {
appBarSubtitle.visibility = View.VISIBLE
conversationSubtitle.visibility = View.VISIBLE
appBarSubtitle.spannable = summary
conversationSubtitle.spannable = summary
} else {
appBarSubtitle.visibility = View.GONE
conversationSubtitle.visibility = View.GONE
}
if (data.conversation_extras_type == ExtrasType.TWITTER_OFFICIAL
&& data.conversation_type == ConversationType.GROUP) {
editButton.visibility = View.VISIBLE
adapter.showButtonSpace = true
} else {
editButton.visibility = View.GONE
adapter.showButtonSpace = false
}
adapter.conversation = data
activity?.invalidateOptionsMenu()
}
private fun performDestroyConversation() {
ProgressDialogFragment.show(childFragmentManager, "leave_conversation_progress")
val weakThis = WeakReference(this)
val task = DestroyConversationTask(requireContext(), accountKey, conversationId)
task.callback = callback@{ succeed ->
val f = weakThis.get() ?: return@callback
f.dismissDialogThen("leave_conversation_progress") {
if (succeed) {
activity?.setResult(RESULT_CLOSE)
activity?.finish()
}
}
}
TaskStarter.execute(task)
}
private fun performClearMessages() {
ProgressDialogFragment.show(childFragmentManager, "clear_messages_progress")
val weakThis = WeakReference(this)
val task = ClearMessagesTask(requireContext(), accountKey, conversationId)
task.callback = callback@{ succeed ->
val f = weakThis.get() ?: return@callback
f.dismissDialogThen("clear_messages_progress") {
if (succeed) {
activity?.finish()
}
}
}
TaskStarter.execute(task)
}
private fun performAddParticipant(user: ParcelableUser) {
ProgressDialogFragment.show(childFragmentManager, "add_participant_progress")
val weakThis = WeakReference(this)
val task = AddParticipantsTask(requireContext(), accountKey, conversationId, listOf(user))
task.callback = callback@ {
val f = weakThis.get() ?: return@callback
f.dismissDialogThen("add_participant_progress") {
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
}
TaskStarter.execute(task)
}
private fun performSetNotificationDisabled(disabled: Boolean) {
ProgressDialogFragment.show(childFragmentManager, "set_notifications_disabled_progress")
val weakThis = WeakReference(this)
val task = SetConversationNotificationDisabledTask(requireContext(), accountKey, conversationId, disabled)
task.callback = callback@{ _ ->
val f = weakThis.get() ?: return@callback
f.dismissDialogThen("set_notifications_disabled_progress") {
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
}
TaskStarter.execute(task)
}
private fun openEditAction(type: String) {
when (type) {
"name" -> {
executeAfterFragmentResumed { fragment ->
val df = EditNameDialogFragment()
df.show(fragment.childFragmentManager, "edit_name")
}
}
"avatar" -> {
val intent = context?.let {
ThemedMediaPickerActivity.withThemed(it)
.allowMultiple(false)
.aspectRatio(1, 1)
.containsVideo(false)
.addEntry(getString(R.string.action_remove_conversation_avatar),
"remove_avatar", RESULT_CODE_REMOVE_CONVERSATION_AVATAR)
.build()
}
startActivityForResult(intent, REQUEST_PICK_MEDIA)
}
}
}
private fun performSetConversationName(name: String) {
val conversationId = this.conversationId
performUpdateInfo("set_name_progress", updateAction = updateAction@{ fragment, account, microBlog ->
val context = fragment.context
when (account.type) {
AccountType.TWITTER -> {
if (account.isOfficial(context)) {
return@updateAction microBlog.updateDmConversationName(conversationId, name).isSuccessful
}
}
}
throw UnsupportedOperationException()
}, successAction = {
put(Conversations.CONVERSATION_NAME, name)
})
}
private fun performSetConversationAvatar(uri: Uri?) {
val conversationId = this.conversationId
performUpdateInfo("set_avatar_progress", updateAction = updateAction@{ fragment, account, microBlog ->
val context = fragment.context
when (account.type) {
AccountType.TWITTER -> {
if (account.isOfficial(context) && context != null) {
val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java)
if (uri == null) {
val result = microBlog.updateDmConversationAvatar(conversationId, null)
if (result.isSuccessful) {
val dmResponse = microBlog.getDmConversation(conversationId, null).conversationTimeline
return@updateAction dmResponse.conversations[conversationId]?.avatarImageHttps
}
throw MicroBlogException("Error ${result.responseCode}")
}
var deleteAlways: List<UpdateStatusTask.MediaDeletionItem>? = null
try {
val media = arrayOf(ParcelableMediaUpdate().apply {
this.uri = uri.toString()
this.delete_always = true
})
val uploadResult = UpdateStatusTask.uploadMicroBlogMediaShared(context,
upload, account, media, null, null, true, null)
deleteAlways = uploadResult.deleteAlways
val avatarId = uploadResult.ids.first()
val result = microBlog.updateDmConversationAvatar(conversationId, avatarId)
if (result.isSuccessful) {
uploadResult.deleteOnSuccess.forEach { it.delete(context) }
val dmResponse = microBlog.getDmConversation(conversationId, null).conversationTimeline
return@updateAction dmResponse.conversations[conversationId]?.avatarImageHttps
}
throw MicroBlogException("Error ${result.responseCode}")
} catch (e: UpdateStatusTask.UploadException) {
e.deleteAlways?.forEach {
it.delete(context)
}
throw e
} finally {
deleteAlways?.forEach { it.delete(context) }
}
}
}
}
throw UnsupportedOperationException()
}, successAction = { uri ->
put(Conversations.CONVERSATION_AVATAR, uri)
})
}
private inline fun <T> performUpdateInfo(
tag: String,
crossinline updateAction: (MessageConversationInfoFragment, AccountDetails, MicroBlog) -> T,
crossinline successAction: ContentValues.(T) -> Unit
) {
ProgressDialogFragment.show(childFragmentManager, tag)
val weakThis = WeakReference(this)
val accountKey = this.accountKey
val conversationId = this.conversationId
task {
val fragment = weakThis.get() ?: throw InterruptedException()
val account = AccountUtils.getAccountDetails(AccountManager.get(fragment.context),
accountKey, true) ?: throw MicroBlogException("No account")
val microBlog = account.newMicroBlogInstance(fragment.requireContext(), cls = MicroBlog::class.java)
return@task updateAction(fragment, account, microBlog)
}.then { result ->
val fragment = weakThis.get() ?: throw InterruptedException()
val values = ContentValues().apply { successAction(result) }
val where = Expression.and(Expression.equalsArgs(Conversations.ACCOUNT_KEY),
Expression.equalsArgs(Conversations.CONVERSATION_ID)).sql
val whereArgs = arrayOf(accountKey.toString(), conversationId)
fragment.context?.contentResolver?.update(Conversations.CONTENT_URI, values, where,
whereArgs)
}.alwaysUi {
val fragment = weakThis.get() ?: return@alwaysUi
fragment.dismissDialogThen(tag) {
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
}
}
private inline fun dismissDialogThen(tag: String, crossinline action: MessageConversationInfoFragment.() -> Unit) {
executeAfterFragmentResumed { fragment ->
val df = fragment.childFragmentManager.findFragmentByTag(tag) as? DialogFragment
df?.dismiss()
action(fragment as MessageConversationInfoFragment)
}
}
internal class ConversationInfoLoader(
context: Context,
val accountKey: UserKey,
val conversationId: String
) : FixedAsyncTaskLoader<ParcelableMessageConversation?>(context) {
override fun loadInBackground(): ParcelableMessageConversation? {
val where = Expression.and(Expression.equalsArgs(Conversations.ACCOUNT_KEY),
Expression.equalsArgs(Conversations.CONVERSATION_ID)).sql
val whereArgs = arrayOf(accountKey.toString(), conversationId)
context.contentResolver.queryReference(Conversations.CONTENT_URI, Conversations.COLUMNS, where,
whereArgs, null)?.use { (cur) ->
if (cur.moveToFirst()) {
val indices = ObjectCursor.indicesFrom(cur, ParcelableMessageConversation::class.java)
return indices.newObject(cur)
}
}
return null
}
override fun onStartLoading() {
forceLoad()
}
}
class ConversationInfoAdapter(
context: Context,
requestManager: RequestManager
) : BaseRecyclerViewAdapter<RecyclerView.ViewHolder>(context, requestManager),
IItemCountsAdapter {
private val inflater = LayoutInflater.from(context)
override val itemCounts: ItemCounts = ItemCounts(5)
var listener: Listener? = null
var conversation: ParcelableMessageConversation? = null
set(value) {
field = value
updateItemCounts()
notifyDataSetChanged()
}
var showButtonSpace: Boolean = false
set(value) {
field = value
updateItemCounts()
notifyDataSetChanged()
}
init {
setHasStableIds(true)
}
override fun getItemCount(): Int {
return itemCounts.itemCount
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
VIEW_TYPE_HEADER -> {
(holder as HeaderViewHolder).display(this.conversation!!)
}
VIEW_TYPE_USER -> {
val participantIdx = position - itemCounts.getItemStartPosition(ITEM_INDEX_ITEM)
val user = getUser(position)!!
(holder as UserViewHolder).display(user, participantIdx == 0)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when (viewType) {
VIEW_TYPE_TOP_SPACE -> {
val view = inflater.inflate(R.layout.header_message_conversation_info_button_space, parent, false)
return SpaceViewHolder(view)
}
VIEW_TYPE_HEADER -> {
val view = inflater.inflate(HeaderViewHolder.layoutResource, parent, false)
return HeaderViewHolder(view, this)
}
VIEW_TYPE_USER -> {
val view = inflater.inflate(R.layout.list_item_conversation_info_user, parent, false)
return UserViewHolder(view, this)
}
VIEW_TYPE_ADD_USER -> {
val view = inflater.inflate(R.layout.list_item_conversation_info_add_user, parent, false)
return AddUserViewHolder(view, this)
}
VIEW_TYPE_BOTTOM_SPACE -> {
val view = inflater.inflate(R.layout.list_item_conversation_info_space, parent, false)
return SpaceViewHolder(view)
}
}
throw UnsupportedOperationException()
}
override fun getItemViewType(position: Int): Int {
return when (val countIndex = itemCounts.getItemCountIndex(position)) {
ITEM_INDEX_TOP_SPACE -> VIEW_TYPE_TOP_SPACE
ITEM_INDEX_HEADER -> VIEW_TYPE_HEADER
ITEM_INDEX_ITEM -> VIEW_TYPE_USER
ITEM_INDEX_ADD_USER -> VIEW_TYPE_ADD_USER
ITEM_INDEX_SPACE -> VIEW_TYPE_BOTTOM_SPACE
else -> throw UnsupportedCountIndexException(countIndex, position)
}
}
override fun getItemId(position: Int): Long {
return when (val countIndex = itemCounts.getItemCountIndex(position)) {
ITEM_INDEX_ITEM -> {
val user = getUser(position)!!
(countIndex.toLong() shl 32) or user.hashCode().toLong()
}
else -> {
(countIndex.toLong() shl 32) or getItemViewType(position).toLong()
}
}
}
fun getUser(position: Int): ParcelableUser? {
val itemPos = position - itemCounts.getItemStartPosition(ITEM_INDEX_ITEM)
return conversation?.participants?.getOrNull(itemPos)
}
private fun updateItemCounts() {
val conversation = this.conversation ?: run {
itemCounts.clear()
return
}
val participantsSize = conversation.participants.size
itemCounts[ITEM_INDEX_TOP_SPACE] = if (showButtonSpace) 1 else 0
itemCounts[ITEM_INDEX_HEADER] = 1
itemCounts[ITEM_INDEX_ITEM] = participantsSize
when (conversation.conversation_type) {
ConversationType.GROUP -> {
if (participantsSize < defaultFeatures.getDirectMessageMaxParticipants(conversation.conversation_extras_type)) {
itemCounts[ITEM_INDEX_ADD_USER] = 1
} else {
itemCounts[ITEM_INDEX_ADD_USER] = 0
}
}
else -> {
itemCounts[ITEM_INDEX_ADD_USER] = 0
}
}
itemCounts[ITEM_INDEX_SPACE] = 1
}
interface Listener {
fun onUserClick(position: Int) {}
fun onAddUserClick(position: Int) {}
fun onDisableNotificationChanged(disabled: Boolean) {}
}
companion object {
internal const val ITEM_INDEX_TOP_SPACE = 0
internal const val ITEM_INDEX_HEADER = 1
internal const val ITEM_INDEX_ITEM = 2
internal const val ITEM_INDEX_ADD_USER = 3
internal const val ITEM_INDEX_SPACE = 4
internal const val VIEW_TYPE_TOP_SPACE = 0
internal const val VIEW_TYPE_HEADER = 1
internal const val VIEW_TYPE_USER = 2
internal const val VIEW_TYPE_ADD_USER = 3
internal const val VIEW_TYPE_BOTTOM_SPACE = 4
}
}
internal class SpaceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
internal class AddUserViewHolder(itemView: View, adapter: ConversationInfoAdapter) : RecyclerView.ViewHolder(itemView) {
private val itemContent = itemView.findViewById<View>(R.id.itemContent)
init {
itemContent.setOnClickListener {
adapter.listener?.onAddUserClick(layoutPosition)
}
}
}
internal class UserViewHolder(
itemView: View,
adapter: ConversationInfoAdapter
) : SimpleUserViewHolder<ConversationInfoAdapter>(itemView, adapter) {
private val headerIcon = itemView.findViewById<View>(R.id.headerIcon)
private val itemContent = itemView.findViewById<View>(R.id.itemContent)
init {
itemContent.setOnClickListener {
adapter.listener?.onUserClick(layoutPosition)
}
}
fun display(user: ParcelableUser, displayHeaderIcon: Boolean) {
super.displayUser(user)
headerIcon.visibility = if (displayHeaderIcon) View.VISIBLE else View.INVISIBLE
}
}
internal class HeaderViewHolder(itemView: View, adapter: ConversationInfoAdapter) : RecyclerView.ViewHolder(itemView) {
private val muteSwitch = itemView.muteNotifications
private val listener = CompoundButton.OnCheckedChangeListener { _, checked ->
adapter.listener?.onDisableNotificationChanged(checked)
}
fun display(conversation: ParcelableMessageConversation) {
muteSwitch.setOnCheckedChangeListener(null)
muteSwitch.isChecked = conversation.notificationDisabled
muteSwitch.setOnCheckedChangeListener(listener)
}
companion object {
const val layoutResource = R.layout.header_message_conversation_info
}
}
internal class LayoutManager(
context: Context
) : FixedLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) {
override fun getDecoratedMeasuredHeight(child: View): Int {
if (getItemViewType(child) == VIEW_TYPE_BOTTOM_SPACE) {
val height = calculateSpaceItemHeight(child, VIEW_TYPE_BOTTOM_SPACE, VIEW_TYPE_HEADER)
if (height >= 0) {
return height
}
}
return super.getDecoratedMeasuredHeight(child)
}
}
class EditInfoDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val actions = arrayOf(Action(getString(R.string.action_edit_conversation_name), "name"),
Action(getString(R.string.action_edit_conversation_avatar), "avatar"))
val builder = AlertDialog.Builder(requireContext())
builder.setItems(actions.mapToArray(Action::title)) { _, which ->
val action = actions[which]
(parentFragment as MessageConversationInfoFragment).openEditAction(action.type)
}
val dialog = builder.create()
dialog.onShow { it.applyTheme() }
return dialog
}
data class Action(val title: String, val type: String)
}
class EditNameDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
builder.setView(R.layout.dialog_edit_conversation_name)
builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(android.R.string.ok) { dialog, _ ->
val editName = (dialog as Dialog).findViewById<EditText>(R.id.editName)
(parentFragment as MessageConversationInfoFragment).performSetConversationName(editName.text.toString())
}
val dialog = builder.create()
dialog.onShow { it.applyTheme() }
return dialog
}
}
class DestroyConversationConfirmDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
builder.setMessage(R.string.message_destroy_conversation_confirm)
builder.setPositiveButton(R.string.action_leave_conversation) { _, _ ->
(parentFragment as MessageConversationInfoFragment).performDestroyConversation()
}
builder.setNegativeButton(android.R.string.cancel, null)
val dialog = builder.create()
dialog.onShow { it.applyTheme() }
return dialog
}
}
class ClearMessagesConfirmDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
builder.setMessage(R.string.message_clear_messages_confirm)
builder.setPositiveButton(R.string.action_clear_messages) { _, _ ->
(parentFragment as MessageConversationInfoFragment).performClearMessages()
}
builder.setNegativeButton(android.R.string.cancel, null)
val dialog = builder.create()
dialog.onShow { it.applyTheme() }
return dialog
}
}
internal class ConversationInfoDecoration(
val adapter: ConversationInfoAdapter,
val typeSpacing: Int
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildLayoutPosition(view)
if (position < 0) return
val itemCounts = adapter.itemCounts
when (val countIndex = itemCounts.getItemCountIndex(position)) {
ConversationInfoAdapter.ITEM_INDEX_TOP_SPACE,
ConversationInfoAdapter.ITEM_INDEX_SPACE,
ConversationInfoAdapter.ITEM_INDEX_ADD_USER -> {
outRect.setEmpty()
}
else -> {
// Previous item is space or first item
if (position == 0 || itemCounts.getItemCountIndex(position - 1)
== ConversationInfoAdapter.ITEM_INDEX_TOP_SPACE) {
outRect.setEmpty()
} else if (itemCounts.getItemStartPosition(countIndex) == position) {
outRect.set(0, typeSpacing, 0, 0)
} else {
outRect.setEmpty()
}
}
}
}
}
companion object {
const val RESULT_CLOSE = 101
const val REQUEST_CONVERSATION_ADD_USER = 101
const val REQUEST_PICK_MEDIA = 102
const val RESULT_CODE_REMOVE_CONVERSATION_AVATAR = 10
}
}