supports change conversation avatar

This commit is contained in:
Mariotaku Lee 2017-02-26 21:49:31 +08:00
parent c4e64c9780
commit db37f9711a
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
4 changed files with 168 additions and 30 deletions

View File

@ -46,6 +46,11 @@ public class ParcelableMediaUpdate implements Parcelable {
@Nullable
public String alt_text;
@JsonField(name = "delete_on_success")
public boolean delete_on_success;
@JsonField(name = "delete_always")
public boolean delete_always;
public ParcelableMediaUpdate() {
}
@ -78,9 +83,10 @@ public class ParcelableMediaUpdate implements Parcelable {
@Override
public String toString() {
return "ParcelableMediaUpdate{" +
"uri='" + uri + '\'' +
"alt_text='" + alt_text + '\'' +
", uri='" + uri + '\'' +
", type=" + type +
", alt_text='" + alt_text + '\'' +
", delete_on_success=" + delete_on_success +
'}';
}

View File

@ -19,11 +19,14 @@
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.support.v4.app.DialogFragment
import android.support.v4.app.FragmentActivity
@ -38,30 +41,37 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.*
import android.widget.CompoundButton
import android.widget.EditText
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.useCursor
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.constant.IntentConstants
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.getDirectMessageMaxParticipants
import org.mariotaku.twidere.extension.model.displayAvatarTo
import org.mariotaku.twidere.extension.model.getSubtitle
import org.mariotaku.twidere.extension.model.getTitle
import org.mariotaku.twidere.extension.model.notificationDisabled
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
@ -72,7 +82,9 @@ import org.mariotaku.twidere.fragment.message.MessageConversationInfoFragment.Co
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.DestroyConversationTask
import org.mariotaku.twidere.task.twitter.message.SetConversationNotificationDisabledTask
@ -169,6 +181,12 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
performAddParticipant(user)
}
}
REQUEST_PICK_MEDIA -> {
if (resultCode == Activity.RESULT_OK && data != null) {
val uri = MediaPickerActivity.getMediaUris(data).firstOrNull() ?: return
performSetConversationAvatar(uri)
}
}
}
}
@ -246,7 +264,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
val task = DestroyConversationTask(context, accountKey, conversationId)
task.callback = callback@ { succeed ->
val f = weakThis.get() ?: return@callback
f.dismissAlertDialogThen("leave_conversation_progress") {
f.dismissDialogThen("leave_conversation_progress") {
if (succeed) {
activity?.setResult(RESULT_CLOSE)
activity?.finish()
@ -262,8 +280,8 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
val task = AddParticipantsTask(context, accountKey, conversationId, listOf(user))
task.callback = callback@ { succeed ->
val f = weakThis.get() ?: return@callback
f.dismissAlertDialogThen("add_participant_progress") {
loaderManager.restartLoader(0, null, this@MessageConversationInfoFragment)
f.dismissDialogThen("add_participant_progress") {
loaderManager.restartLoader(0, null, this)
}
}
TaskStarter.execute(task)
@ -275,8 +293,8 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
val task = SetConversationNotificationDisabledTask(context, accountKey, conversationId, disabled)
task.callback = callback@ { succeed ->
val f = weakThis.get() ?: return@callback
f.dismissAlertDialogThen("set_notifications_disabled_progress") {
loaderManager.restartLoader(0, null, this@MessageConversationInfoFragment)
f.dismissDialogThen("set_notifications_disabled_progress") {
loaderManager.restartLoader(0, null, this)
}
}
TaskStarter.execute(task)
@ -292,20 +310,111 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
}
}
"avatar" -> {
val intent = ThemedMediaPickerActivity.withThemed(context)
.allowMultiple(false)
.aspectRatio(1, 1)
.containsVideo(false)
.build()
startActivityForResult(intent, REQUEST_PICK_MEDIA)
}
}
}
private inline fun dismissAlertDialogThen(tag: String, crossinline action: BaseFragment.() -> Unit) {
executeAfterFragmentResumed { fragment ->
val df = fragment.childFragmentManager.findFragmentByTag(tag) as? DialogFragment
df?.dismiss()
action(fragment)
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)) {
val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java)
val media = arrayOf(ParcelableMediaUpdate().apply {
this.uri = uri.toString()
this.delete_always = true
})
var deleteAlways: List<UpdateStatusTask.MediaDeletionItem>? = null
try {
val uploadResult = UpdateStatusTask.uploadAllMediaShared(context,
fragment.mediaLoader, upload, account, media, null, true, null)
deleteAlways = uploadResult.deleteAlways
val avatarId = uploadResult.ids.first()
val result = microBlog.updateDmConversationAvatar(conversationId, avatarId).isSuccessful
uploadResult.deleteOnSuccess.forEach { it.delete(context) }
return@updateAction result
} catch (e: UpdateStatusTask.UploadException) {
e.deleteAlways?.forEach {
it.delete(context)
}
throw e
} finally {
deleteAlways?.forEach { it.delete(context) }
}
}
}
}
throw UnsupportedOperationException()
}, successAction = {
putNull(Conversations.CONVERSATION_AVATAR)
})
}
private inline fun performUpdateInfo(
tag: String,
crossinline updateAction: (MessageConversationInfoFragment, AccountDetails, MicroBlog) -> Boolean,
crossinline successAction: ContentValues.() -> 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.context, cls = MicroBlog::class.java)
if (!updateAction(fragment, account, microBlog)) throw MicroBlogException("Update failed")
}.then {
val fragment = weakThis.get() ?: throw InterruptedException()
val values = ContentValues().apply(successAction)
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.restartLoader(0, null, this)
}
}
}
class ConversationInfoLoader(
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) : AsyncTaskLoader<ParcelableMessageConversation?>(context) {
@ -563,7 +672,8 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
builder.setView(R.layout.dialog_edit_conversation_name)
builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(android.R.string.ok) { dialog, which ->
val editName = (dialog as Dialog).findViewById(R.id.editName) as EditText
(parentFragment as MessageConversationInfoFragment).performSetConversationName(editName.text.toString())
}
val dialog = builder.create()
dialog.setOnShowListener {
@ -574,6 +684,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
}
}
class DestroyConversationConfirmDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context)
@ -626,6 +737,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
companion object {
const val RESULT_CLOSE = 101
const val REQUEST_CONVERSATION_ADD_USER = 101
const val REQUEST_PICK_MEDIA = 102
}
}

View File

@ -118,6 +118,9 @@ class UpdateStatusTask(
// Cleanup
pendingUpdate.deleteOnSuccess.forEach { item -> item.delete(context) }
} catch (e: UploadException) {
e.deleteAlways?.forEach { it.delete(context) }
throw e
} finally {
// Cleanup
pendingUpdate.deleteAlways.forEach { item -> item.delete(context) }
@ -288,10 +291,10 @@ class UpdateStatusTask(
}
val media = statusUpdate.media.first()
try {
return getBodyFromMedia(context, mediaLoader, Uri.parse(media.uri), sizeLimit, media.type,
false, ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(-1, position, length)
}).use { mediaBody ->
return getBodyFromMedia(context, mediaLoader, media, sizeLimit, false,
ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(-1, position, length)
}).use { mediaBody ->
val photoUpdate = PhotoStatusUpdate(mediaBody.body, pendingUpdate.overrideTexts[updateIndex])
return@use microBlog.uploadPhoto(photoUpdate)
}
@ -533,6 +536,8 @@ class UpdateStatusTask(
class UploadException : UpdateStatusException {
var deleteAlways: List<MediaDeletionItem>? = null
constructor() : super()
constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable)
@ -643,8 +648,8 @@ class UpdateStatusTask(
var body: MediaStreamBody? = null
try {
val sizeLimit = account.size_limit
body = getBodyFromMedia(context, mediaLoader, Uri.parse(media.uri), sizeLimit,
media.type, chucked, ContentLengthInputStream.ReadListener { length, position ->
body = getBodyFromMedia(context, mediaLoader, media, sizeLimit,
chucked, ContentLengthInputStream.ReadListener { length, position ->
callback?.onUploadingProgressChanged(index, position, length)
})
val mediaUploadEvent = MediaUploadEvent.create(context, media)
@ -660,9 +665,13 @@ class UpdateStatusTask(
mediaUploadEvent.markEnd()
HotMobiLogger.getInstance(context).log(mediaUploadEvent)
} catch (e: IOException) {
throw UploadException(e)
throw UploadException(e).apply {
this.deleteAlways = deleteAlways
}
} catch (e: MicroBlogException) {
throw UploadException(e)
throw UploadException(e).apply {
this.deleteAlways = deleteAlways
}
} finally {
Utils.closeSilently(body)
}
@ -684,13 +693,14 @@ class UpdateStatusTask(
fun getBodyFromMedia(
context: Context,
mediaLoader: MediaLoaderWrapper,
mediaUri: Uri,
media: ParcelableMediaUpdate,
sizeLimit: SizeLimit? = null,
@ParcelableMedia.Type type: Int,
chucked: Boolean,
readListener: ContentLengthInputStream.ReadListener
): MediaStreamBody {
val resolver = context.contentResolver
val mediaUri = Uri.parse(media.uri)
val type = media.type
val mediaType = resolver.getType(mediaUri) ?: run {
if (mediaUri.scheme == ContentResolver.SCHEME_FILE) {
mediaUri.lastPathSegment?.substringAfterLast(".")?.let { ext ->
@ -716,8 +726,13 @@ class UpdateStatusTask(
cis.setReadListener(readListener)
val mimeType = data?.type ?: mediaType ?: "application/octet-stream"
val body = FileBody(cis, "attachment", cis.length(), ContentType.parse(mimeType))
val deleteOnSuccess: MutableList<MediaDeletionItem> = mutableListOf(UriMediaDeletionItem(mediaUri))
val deleteOnSuccess: MutableList<MediaDeletionItem> = mutableListOf()
val deleteAlways: MutableList<MediaDeletionItem> = mutableListOf()
if (media.delete_always) {
deleteAlways.add(UriMediaDeletionItem(mediaUri))
} else if (media.delete_on_success) {
deleteOnSuccess.add(UriMediaDeletionItem(mediaUri))
}
data?.deleteOnSuccess?.addAllTo(deleteOnSuccess)
data?.deleteAlways?.addAllTo(deleteAlways)
return MediaStreamBody(body, data?.geometry, deleteOnSuccess, deleteAlways)

View File

@ -114,6 +114,11 @@ class SendMessageTask(
deleteOnSuccess = uploadResult.deleteOnSuccess
}
microBlog.sendDm(newDm)
} catch (e: UpdateStatusTask.UploadException) {
e.deleteAlways?.forEach {
it.delete(context)
}
throw e
} finally {
deleteOnSuccess?.forEach { it.delete(context) }
}