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 @Nullable
public String alt_text; public String alt_text;
@JsonField(name = "delete_on_success")
public boolean delete_on_success;
@JsonField(name = "delete_always")
public boolean delete_always;
public ParcelableMediaUpdate() { public ParcelableMediaUpdate() {
} }
@ -78,9 +83,10 @@ public class ParcelableMediaUpdate implements Parcelable {
@Override @Override
public String toString() { public String toString() {
return "ParcelableMediaUpdate{" + return "ParcelableMediaUpdate{" +
"uri='" + uri + '\'' + "alt_text='" + alt_text + '\'' +
", uri='" + uri + '\'' +
", type=" + type + ", type=" + type +
", alt_text='" + alt_text + '\'' + ", delete_on_success=" + delete_on_success +
'}'; '}';
} }

View File

@ -19,11 +19,14 @@
package org.mariotaku.twidere.fragment.message package org.mariotaku.twidere.fragment.message
import android.accounts.AccountManager
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.DialogFragment import android.support.v4.app.DialogFragment
import android.support.v4.app.FragmentActivity import android.support.v4.app.FragmentActivity
@ -38,30 +41,37 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.view.* import android.view.*
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.EditText
import kotlinx.android.synthetic.main.activity_home_content.view.* import kotlinx.android.synthetic.main.activity_home_content.view.*
import kotlinx.android.synthetic.main.fragment_messages_conversation_info.* import kotlinx.android.synthetic.main.fragment_messages_conversation_info.*
import kotlinx.android.synthetic.main.header_message_conversation_info.view.* import kotlinx.android.synthetic.main.header_message_conversation_info.view.*
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.* 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.abstask.library.TaskStarter
import org.mariotaku.chameleon.Chameleon import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.useCursor 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.sqliteqb.library.Expression
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.ThemedMediaPickerActivity
import org.mariotaku.twidere.activity.UserSelectorActivity import org.mariotaku.twidere.activity.UserSelectorActivity
import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter 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.IntentConstants.* import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.nameFirstKey import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.profileImageStyleKey import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.extension.applyTheme import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.getDirectMessageMaxParticipants import org.mariotaku.twidere.extension.getDirectMessageMaxParticipants
import org.mariotaku.twidere.extension.model.displayAvatarTo import org.mariotaku.twidere.extension.model.*
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.view.calculateSpaceItemHeight import org.mariotaku.twidere.extension.view.calculateSpaceItemHeight
import org.mariotaku.twidere.fragment.BaseDialogFragment import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.BaseFragment 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.*
import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType
import org.mariotaku.twidere.model.ParcelableMessageConversation.ExtrasType 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.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.AddParticipantsTask
import org.mariotaku.twidere.task.twitter.message.DestroyConversationTask import org.mariotaku.twidere.task.twitter.message.DestroyConversationTask
import org.mariotaku.twidere.task.twitter.message.SetConversationNotificationDisabledTask import org.mariotaku.twidere.task.twitter.message.SetConversationNotificationDisabledTask
@ -169,6 +181,12 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
performAddParticipant(user) 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) val task = DestroyConversationTask(context, accountKey, conversationId)
task.callback = callback@ { succeed -> task.callback = callback@ { succeed ->
val f = weakThis.get() ?: return@callback val f = weakThis.get() ?: return@callback
f.dismissAlertDialogThen("leave_conversation_progress") { f.dismissDialogThen("leave_conversation_progress") {
if (succeed) { if (succeed) {
activity?.setResult(RESULT_CLOSE) activity?.setResult(RESULT_CLOSE)
activity?.finish() activity?.finish()
@ -262,8 +280,8 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
val task = AddParticipantsTask(context, accountKey, conversationId, listOf(user)) val task = AddParticipantsTask(context, accountKey, conversationId, listOf(user))
task.callback = callback@ { succeed -> task.callback = callback@ { succeed ->
val f = weakThis.get() ?: return@callback val f = weakThis.get() ?: return@callback
f.dismissAlertDialogThen("add_participant_progress") { f.dismissDialogThen("add_participant_progress") {
loaderManager.restartLoader(0, null, this@MessageConversationInfoFragment) loaderManager.restartLoader(0, null, this)
} }
} }
TaskStarter.execute(task) TaskStarter.execute(task)
@ -275,8 +293,8 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
val task = SetConversationNotificationDisabledTask(context, accountKey, conversationId, disabled) val task = SetConversationNotificationDisabledTask(context, accountKey, conversationId, disabled)
task.callback = callback@ { succeed -> task.callback = callback@ { succeed ->
val f = weakThis.get() ?: return@callback val f = weakThis.get() ?: return@callback
f.dismissAlertDialogThen("set_notifications_disabled_progress") { f.dismissDialogThen("set_notifications_disabled_progress") {
loaderManager.restartLoader(0, null, this@MessageConversationInfoFragment) loaderManager.restartLoader(0, null, this)
} }
} }
TaskStarter.execute(task) TaskStarter.execute(task)
@ -292,20 +310,111 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
} }
} }
"avatar" -> { "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) { private fun performSetConversationName(name: String) {
executeAfterFragmentResumed { fragment -> val conversationId = this.conversationId
val df = fragment.childFragmentManager.findFragmentByTag(tag) as? DialogFragment performUpdateInfo("set_name_progress", updateAction = updateAction@ { fragment, account, microBlog ->
df?.dismiss() val context = fragment.context
action(fragment) 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, context: Context,
val accountKey: UserKey, val accountKey: UserKey,
val conversationId: String) : AsyncTaskLoader<ParcelableMessageConversation?>(context) { val conversationId: String) : AsyncTaskLoader<ParcelableMessageConversation?>(context) {
@ -563,7 +672,8 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
builder.setView(R.layout.dialog_edit_conversation_name) builder.setView(R.layout.dialog_edit_conversation_name)
builder.setNegativeButton(android.R.string.cancel, null) builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(android.R.string.ok) { dialog, which -> 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() val dialog = builder.create()
dialog.setOnShowListener { dialog.setOnShowListener {
@ -574,6 +684,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
} }
} }
class DestroyConversationConfirmDialogFragment : BaseDialogFragment() { class DestroyConversationConfirmDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context) val builder = AlertDialog.Builder(context)
@ -626,6 +737,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
companion object { companion object {
const val RESULT_CLOSE = 101 const val RESULT_CLOSE = 101
const val REQUEST_CONVERSATION_ADD_USER = 101 const val REQUEST_CONVERSATION_ADD_USER = 101
const val REQUEST_PICK_MEDIA = 102
} }
} }

View File

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

View File

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