added remove media in dm and compose

This commit is contained in:
Mariotaku Lee 2017-02-15 13:32:45 +08:00
parent 177b682d84
commit 5e5aad22a9
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
11 changed files with 173 additions and 58 deletions

4
.gitignore vendored
View File

@ -25,4 +25,6 @@ Thumbs.db
# Private files
/signing.properties
/twidere.jks
/twidere.jks
/captures

View File

@ -157,7 +157,7 @@ dependencies {
compile 'com.bluelinelabs:logansquare:1.3.7'
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.2'
compile 'com.github.mariotaku:PickNCrop:0.9.17'
compile 'com.github.mariotaku:PickNCrop:0.9.19'
compile "com.github.mariotaku.RestFu:library:$mariotaku_restfu_version"
compile "com.github.mariotaku.RestFu:okhttp3:$mariotaku_restfu_version"
compile 'com.squareup.okhttp3:okhttp:3.5.0'

View File

@ -89,6 +89,7 @@ import org.mariotaku.twidere.preference.ServicePickerPreference
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.service.LengthyOperationsService
import org.mariotaku.twidere.task.compose.AbsAddMediaTask
import org.mariotaku.twidere.task.compose.AbsDeleteMediaTask
import org.mariotaku.twidere.text.MarkForDeleteSpan
import org.mariotaku.twidere.text.style.EmojiSpan
import org.mariotaku.twidere.util.*
@ -151,7 +152,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
REQUEST_TAKE_PHOTO, REQUEST_PICK_MEDIA -> {
if (resultCode == Activity.RESULT_OK && intent != null) {
val src = MediaPickerActivity.getMediaUris(intent)
TaskStarter.execute(AddMediaTask(this, src, true))
TaskStarter.execute(AddMediaTask(this, src, false, false))
}
}
REQUEST_EDIT_IMAGE -> {
@ -339,7 +340,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
IntentUtils.openDrafts(this)
}
R.id.delete -> {
AsyncTaskUtils.executeTask(DeleteMediaTask(this, media))
TaskStarter.execute(DeleteMediaTask(this, media))
}
R.id.toggle_sensitive -> {
if (!hasMedia()) return false
@ -807,21 +808,21 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val stream = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
if (stream != null) {
val src = arrayOf(stream)
TaskStarter.execute(AddMediaTask(this, src, false))
TaskStarter.execute(AddMediaTask(this, src, true, false))
}
} else if (Intent.ACTION_SEND_MULTIPLE == action) {
shouldSaveAccounts = false
val extraStream = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
if (extraStream != null) {
val src = extraStream.toTypedArray()
TaskStarter.execute(AddMediaTask(this, src, false))
TaskStarter.execute(AddMediaTask(this, src, true, false))
}
} else {
shouldSaveAccounts = !hasAccountIds
val data = intent.data
if (data != null) {
val src = arrayOf(data)
TaskStarter.execute(AddMediaTask(this, src, false))
TaskStarter.execute(AddMediaTask(this, src, true, false))
}
}
val extraSubject = intent.getCharSequenceExtra(Intent.EXTRA_SUBJECT)
@ -1507,8 +1508,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
internal class AddMediaTask(
activity: ComposeActivity,
sources: Array<Uri>,
copySrc: Boolean,
deleteSrc: Boolean
) : AbsAddMediaTask<ComposeActivity>(activity, sources, deleteSrc) {
) : AbsAddMediaTask<ComposeActivity>(activity, sources, copySrc, deleteSrc) {
init {
callback = activity
@ -1529,34 +1531,27 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
internal class DeleteMediaTask(activity: ComposeActivity, val media: Array<ParcelableMediaUpdate>) : AsyncTask<Any, Any, Boolean>() {
val activity: WeakReference<ComposeActivity>
internal class DeleteMediaTask(
activity: ComposeActivity,
val media: Array<ParcelableMediaUpdate>
) : AbsDeleteMediaTask<ComposeActivity>(activity, media.map { Uri.parse(it.uri) }.toTypedArray()) {
init {
this.activity = WeakReference(activity)
this.callback = activity
}
override fun doInBackground(vararg params: Any): Boolean {
media.forEach {
Utils.deleteMedia(activity.get(), Uri.parse(it.uri))
override fun beforeExecute() {
callback?.setProgressVisible(true)
}
override fun afterExecute(callback: ComposeActivity?, result: BooleanArray?) {
if (callback == null || result == null) return
callback.setProgressVisible(false)
callback.removeAllMedia(media.filterIndexed { i, media -> result[i] })
callback.setMenu()
if (result.any { false }) {
Toast.makeText(callback, R.string.message_toast_error_occurred, Toast.LENGTH_SHORT).show()
}
return true
}
override fun onPostExecute(result: Boolean) {
val activity = activity.get() ?: return
activity.setProgressVisible(false)
activity.removeAllMedia(Arrays.asList(*media))
activity.setMenu()
if (!result) {
Toast.makeText(activity, R.string.message_toast_error_occurred, Toast.LENGTH_SHORT).show()
}
}
override fun onPreExecute() {
val activity = activity.get() ?: return
activity.setProgressVisible(true)
}
}

View File

@ -20,6 +20,7 @@
package org.mariotaku.twidere.adapter
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import org.mariotaku.twidere.R
@ -84,7 +85,8 @@ class MediaPreviewAdapter(
}
interface Listener : SimpleItemTouchHelperCallback.OnStartDragListener {
fun onRemoveClick(position: Int, holder: MediaPreviewViewHolder)
fun onEditClick(position: Int, holder: MediaPreviewViewHolder)
fun onRemoveClick(position: Int, holder: MediaPreviewViewHolder) {}
fun onEditClick(position: Int, holder: MediaPreviewViewHolder) {}
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {}
}
}

View File

@ -25,6 +25,7 @@ import android.support.v4.app.Fragment
import android.support.v4.text.BidiFormatter
import com.squareup.otto.Bus
import org.mariotaku.twidere.fragment.iface.IBaseFragment
import org.mariotaku.twidere.model.DefaultFeatures
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
@ -61,6 +62,8 @@ open class BaseFragment : Fragment(), IBaseFragment<BaseFragment> {
lateinit var extraFeaturesService: ExtraFeaturesService
@Inject
lateinit var permissionsManager: PermissionsManager
@Inject
lateinit var defaultFeatures: DefaultFeatures
private val actionHelper = IBaseFragment.ActionHelper(this)

View File

@ -14,6 +14,7 @@ import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.fragment_messages_conversation.*
import org.mariotaku.abstask.library.TaskStarter
@ -31,6 +32,7 @@ import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_CONVERSATION_ID
import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.extension.model.isOfficial
import org.mariotaku.twidere.loader.ObjectCursorLoader
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType
@ -40,9 +42,11 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.service.LengthyOperationsService
import org.mariotaku.twidere.task.GetMessagesTask
import org.mariotaku.twidere.task.compose.AbsAddMediaTask
import org.mariotaku.twidere.task.compose.AbsDeleteMediaTask
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.PreviewGridItemDecoration
import org.mariotaku.twidere.view.holder.compose.MediaPreviewViewHolder
import java.util.concurrent.atomic.AtomicReference
class MessagesConversationFragment : AbsContentListRecyclerViewFragment<MessagesConversationAdapter>(),
@ -70,6 +74,10 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val account = this.account ?: run {
activity?.finish()
return
}
adapter.listener = object : MessagesConversationAdapter.Listener {
override fun onMediaClick(position: Int, media: ParcelableMedia, accountKey: UserKey?) {
val message = adapter.getMessage(position) ?: return
@ -80,6 +88,15 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
}
mediaPreviewAdapter = MediaPreviewAdapter(context)
mediaPreviewAdapter.listener = object : MediaPreviewAdapter.Listener {
override fun onRemoveClick(position: Int, holder: MediaPreviewViewHolder) {
val task = DeleteMediaTask(this@MessagesConversationFragment,
arrayOf(mediaPreviewAdapter.getItem(position)))
TaskStarter.execute(task)
}
}
attachedMediaPreview.layoutManager = FixedLinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
attachedMediaPreview.adapter = mediaPreviewAdapter
attachedMediaPreview.addItemDecoration(PreviewGridItemDecoration(resources.getDimensionPixelSize(R.dimen.element_spacing_small)))
@ -95,6 +112,12 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
refreshEnabled = false
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.NONE
if (account.isOfficial(context)) {
addMedia.visibility = View.VISIBLE
} else {
addMedia.visibility = View.GONE
}
updateMediaPreview()
loaderManager.initLoader(0, null, this)
@ -116,7 +139,7 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
REQUEST_PICK_MEDIA -> {
if (resultCode == Activity.RESULT_OK && data != null) {
val mediaUris = MediaPickerActivity.getMediaUris(data)
TaskStarter.execute(AddMediaTask(this, mediaUris, true))
TaskStarter.execute(AddMediaTask(this, mediaUris))
}
}
}
@ -179,13 +202,25 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
private fun performSendMessage() {
val conversation = adapter.conversation ?: return
val conversationAccount = this.account ?: return
if (editText.empty && adapter.itemCount == 0) {
editText.error = getString(R.string.hint_error_message_no_content)
return
}
if (conversationAccount.isOfficial(context)) {
if (adapter.itemCount > defaultFeatures.twitterDirectMessageMediaLimit) {
editText.error = getString(R.string.error_message_media_message_too_many)
return
} else {
editText.error = null
}
} else {
editText.error = getString(R.string.error_message_media_message_attachment_not_supported)
return
}
val text = editText.text.toString()
val message = ParcelableNewMessage().apply {
this.account = this@MessagesConversationFragment.account
this.account = conversationAccount
this.media = mediaPreviewAdapter.asList().toTypedArray()
this.conversation_id = conversation.id
this.recipient_ids = conversation.participants?.map {
@ -218,12 +253,18 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
updateMediaPreview()
}
private fun removeMedia(media: List<ParcelableMediaUpdate>) {
mediaPreviewAdapter.removeAll(media)
updateMediaPreview()
}
private fun updateMediaPreview() {
attachedMediaPreview.visibility = if (mediaPreviewAdapter.itemCount > 0) {
View.VISIBLE
} else {
View.GONE
}
editText.error = null
}
private fun setProgressVisible(visible: Boolean) {
@ -232,9 +273,8 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
internal class AddMediaTask(
fragment: MessagesConversationFragment,
sources: Array<Uri>,
deleteSrc: Boolean
) : AbsAddMediaTask<MessagesConversationFragment>(fragment.context, sources, deleteSrc) {
sources: Array<Uri>
) : AbsAddMediaTask<MessagesConversationFragment>(fragment.context, sources) {
init {
callback = fragment
@ -253,6 +293,32 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
}
internal class DeleteMediaTask(
fragment: MessagesConversationFragment,
val media: Array<ParcelableMediaUpdate>
) : AbsDeleteMediaTask<MessagesConversationFragment>(fragment.context,
media.map { Uri.parse(it.uri) }.toTypedArray()) {
init {
callback = fragment
}
override fun afterExecute(callback: MessagesConversationFragment?, result: BooleanArray?) {
if (callback == null || result == null) return
callback.setProgressVisible(false)
callback.removeMedia(media.filterIndexed { i, media -> result[i] })
if (result.any { false }) {
Toast.makeText(callback.context, R.string.message_toast_error_occurred, Toast.LENGTH_SHORT).show()
}
}
override fun beforeExecute() {
val fragment = callback ?: return
fragment.setProgressVisible(true)
}
}
internal class ConversationLoader(
context: Context,
val accountKey: UserKey,

View File

@ -62,8 +62,8 @@ class GetMessagesTask(
private fun getMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
when (details.type) {
AccountType.FANFOU -> {
// Use fanfou DM api
return getFanfouMessages(microBlog, details, param, index)
// Use fanfou DM api, disabled since it's conversation api is not suitable for paging
// return getFanfouMessages(microBlog, details, param, index)
}
AccountType.TWITTER -> {
// Use official DM api

View File

@ -35,7 +35,8 @@ import java.lang.ref.WeakReference
open class AbsAddMediaTask<Callback>(
context: Context,
val sources: Array<Uri>,
val deleteSrc: Boolean
val copySrc: Boolean = false,
val deleteSrc: Boolean = false
) : AbstractTask<Unit, List<ParcelableMediaUpdate>?, Callback>() {
private val contextRef = WeakReference(context)
@ -59,13 +60,17 @@ open class AbsAddMediaTask<Callback>(
val extension = sourceMimeType?.let { mimeType ->
MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
} ?: "tmp"
val destination = createTempImageUri(context, index, extension)
st = resolver.openInputStream(source)
os = resolver.openOutputStream(destination)
if (st == null || os == null) throw FileNotFoundException()
StreamUtils.copy(st, os, null, null)
if (deleteSrc) {
Utils.deleteMedia(context, source)
st = resolver.openInputStream(source) ?: throw FileNotFoundException("Unable to open $source")
val destination: Uri
if (copySrc) {
destination = createTempImageUri(context, index, extension)
os = resolver.openOutputStream(destination) ?: throw FileNotFoundException("Unable to open $destination")
StreamUtils.copy(st, os, null, null)
if (deleteSrc) {
Utils.deleteMedia(context, source)
}
} else {
destination = source
}
return@mapIndexedNotNull ParcelableMediaUpdate(destination.toString(), mediaType)
} catch (e: IOException) {

View File

@ -0,0 +1,41 @@
/*
* 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.task.compose
import android.content.Context
import android.net.Uri
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.twidere.util.Utils
import java.lang.ref.WeakReference
open class AbsDeleteMediaTask<Callback>(
context: Context,
val sources: Array<Uri>
) : AbstractTask<Unit, BooleanArray?, Callback>() {
private val contextRef = WeakReference(context)
val context: Context? get() = contextRef.get()
override fun doLongOperation(params: Unit?): BooleanArray? {
val context = contextRef.get() ?: return null
return BooleanArray(sources.size) { Utils.deleteMedia(context, sources[it]) }
}
}

View File

@ -47,11 +47,11 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="?android:colorBackground"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="horizontal"
android:outlineProvider="bounds"
android:paddingBottom="@dimen/element_spacing_normal"
android:paddingTop="@dimen/element_spacing_normal"
android:padding="@dimen/element_spacing_normal"
tools:ignore="UnusedAttribute">
<org.mariotaku.twidere.view.IconActionView
@ -60,25 +60,24 @@
android:layout_height="@dimen/element_size_normal"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginEnd="@dimen/element_spacing_normal"
android:layout_marginRight="@dimen/element_spacing_normal"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/add_image"
android:scaleType="centerInside"
android:src="@drawable/ic_action_gallery"
android:visibility="visible"
android:visibility="gone"
app:iabColor="?android:textColorSecondary"
tools:tint="?android:textColorSecondary"/>
tools:tint="?android:textColorSecondary"
tools:visibility="visible"/>
<FrameLayout
android:id="@+id/sendMessageContainer"
android:layout_width="@dimen/element_size_normal"
android:layout_height="@dimen/element_size_normal"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="@dimen/element_spacing_normal"
android:layout_marginLeft="@dimen/element_spacing_minus_normal"
android:layout_marginRight="@dimen/element_spacing_normal"
android:layout_marginStart="@dimen/element_spacing_minus_normal">
android:layout_alignParentRight="true">
<org.mariotaku.twidere.view.IconActionView
android:id="@+id/sendMessage"

View File

@ -425,7 +425,9 @@
<string name="error_message_device_incompatible">This device is not compatible with Twidere, upgrade to latest Android OS is recommended.\nYou can send information below to help me report this issue to device manufacturer.</string>
<string name="error_message_media_upload_failed">Media upload failed.</string>
<string name="error_message_media_uploader_not_found">Media uploader not found, maybe it was uninstalled.</string>
<string name="error_message_message_too_long">Message too long.</string>
<string name="error_message_media_message_attachment_not_supported">Attach media to DM is not supported</string>
<string name="error_message_media_message_too_many">Too many media</string>
<string name="error_message_message_too_long">Message too long</string>
<string name="error_message_no_content">No content</string>
<string
name="error_message_rate_limit">Twitter\'s rate limit exceeded, please retry <xliff:g id="time">%s</xliff:g>