supports dm link click
fixed dm media preview improved dm draft
This commit is contained in:
parent
da54c028bd
commit
7bb29e84b5
|
@ -23,6 +23,7 @@ package org.mariotaku.microblog.library.twitter.api;
|
||||||
|
|
||||||
import org.mariotaku.microblog.library.MicroBlogException;
|
import org.mariotaku.microblog.library.MicroBlogException;
|
||||||
import org.mariotaku.microblog.library.twitter.model.ConversationTimeline;
|
import org.mariotaku.microblog.library.twitter.model.ConversationTimeline;
|
||||||
|
import org.mariotaku.microblog.library.twitter.model.DMResponse;
|
||||||
import org.mariotaku.microblog.library.twitter.model.NewDm;
|
import org.mariotaku.microblog.library.twitter.model.NewDm;
|
||||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||||
import org.mariotaku.microblog.library.twitter.model.ResponseCode;
|
import org.mariotaku.microblog.library.twitter.model.ResponseCode;
|
||||||
|
@ -46,11 +47,7 @@ public interface PrivateDirectMessagesResources extends PrivateResources {
|
||||||
ResponseCode destroyDirectMessagesConversation(@Path("conversation_id") String conversationId) throws MicroBlogException;
|
ResponseCode destroyDirectMessagesConversation(@Path("conversation_id") String conversationId) throws MicroBlogException;
|
||||||
|
|
||||||
@POST("/dm/new.json")
|
@POST("/dm/new.json")
|
||||||
ResponseCode sendDm(@Param NewDm newDm) throws MicroBlogException;
|
DMResponse sendDm(@Param NewDm newDm) throws MicroBlogException;
|
||||||
|
|
||||||
@POST("/dm/conversation/{account_id}-{user_id}/delete.json")
|
|
||||||
@BodyType(BodyType.FORM)
|
|
||||||
ResponseCode destroyDirectMessagesConversation(@Path("account_id") String accountId, @Path("user_id") String userId) throws MicroBlogException;
|
|
||||||
|
|
||||||
@GET("/dm/user_inbox.json")
|
@GET("/dm/user_inbox.json")
|
||||||
UserInbox getUserInbox(@Query Paging paging) throws MicroBlogException;
|
UserInbox getUserInbox(@Query Paging paging) throws MicroBlogException;
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Twidere - Twitter client for Android
|
||||||
|
*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.mariotaku.twidere.model;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||||
|
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
|
||||||
|
import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mariotaku on 2017/2/14.
|
||||||
|
*/
|
||||||
|
@ParcelablePlease
|
||||||
|
@JsonObject
|
||||||
|
public class ParcelableNewMessage implements Parcelable {
|
||||||
|
|
||||||
|
@JsonField(name = "account")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
public AccountDetails account;
|
||||||
|
@JsonField(name = "conversation_id")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
public String conversation_id;
|
||||||
|
@JsonField(name = "recipient_id")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
public String recipient_id;
|
||||||
|
@JsonField(name = "text")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
public String text;
|
||||||
|
@JsonField(name = "media")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
public ParcelableMediaUpdate[] media;
|
||||||
|
@JsonField(name = "draft_unique_id")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
public String draft_unique_id;
|
||||||
|
@JsonField(name = "draft_action")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
@Draft.Action
|
||||||
|
public String draft_action;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
ParcelableNewMessageParcelablePlease.writeToParcel(this, dest, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ParcelableNewMessage> CREATOR = new Creator<ParcelableNewMessage>() {
|
||||||
|
public ParcelableNewMessage createFromParcel(Parcel source) {
|
||||||
|
ParcelableNewMessage target = new ParcelableNewMessage();
|
||||||
|
ParcelableNewMessageParcelablePlease.readFromParcel(target, source);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParcelableNewMessage[] newArray(int size) {
|
||||||
|
return new ParcelableNewMessage[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -68,6 +68,10 @@ public class ParcelableStatusUpdate implements Parcelable {
|
||||||
@JsonField(name = "draft_unique_id")
|
@JsonField(name = "draft_unique_id")
|
||||||
@ParcelableThisPlease
|
@ParcelableThisPlease
|
||||||
public String draft_unique_id;
|
public String draft_unique_id;
|
||||||
|
@JsonField(name = "draft_action")
|
||||||
|
@ParcelableThisPlease
|
||||||
|
@Draft.Action
|
||||||
|
public String draft_action;
|
||||||
|
|
||||||
public ParcelableStatusUpdate() {
|
public ParcelableStatusUpdate() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,9 @@ public class SendDirectMessageActionExtras implements ActionExtras {
|
||||||
@ParcelableThisPlease
|
@ParcelableThisPlease
|
||||||
@JsonField(name = "recipient_id")
|
@JsonField(name = "recipient_id")
|
||||||
String recipientId;
|
String recipientId;
|
||||||
|
@ParcelableThisPlease
|
||||||
|
@JsonField(name = "conversation_id")
|
||||||
|
String conversationId;
|
||||||
|
|
||||||
public String getRecipientId() {
|
public String getRecipientId() {
|
||||||
return recipientId;
|
return recipientId;
|
||||||
|
@ -46,6 +49,14 @@ public class SendDirectMessageActionExtras implements ActionExtras {
|
||||||
this.recipientId = recipientId;
|
this.recipientId = recipientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getConversationId() {
|
||||||
|
return conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConversationId(final String conversationId) {
|
||||||
|
this.conversationId = conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -3,10 +3,6 @@ package org.mariotaku.ktextension
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by mariotaku on 16/8/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
inline fun Bundle(action: Bundle.() -> Unit): Bundle {
|
inline fun Bundle(action: Bundle.() -> Unit): Bundle {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
action(bundle)
|
action(bundle)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.ktextension
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Parcelable
|
||||||
|
|
||||||
|
fun <T> Intent.getTypedArrayExtra(key: String, creator: Parcelable.Creator<T>): Array<T> {
|
||||||
|
return getParcelableArrayExtra(key).toTypedArray(creator)
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ import org.mariotaku.twidere.constant.nameFirstKey
|
||||||
import org.mariotaku.twidere.extension.model.timestamp
|
import org.mariotaku.twidere.extension.model.timestamp
|
||||||
import org.mariotaku.twidere.model.*
|
import org.mariotaku.twidere.model.*
|
||||||
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
|
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
|
||||||
|
import org.mariotaku.twidere.util.DirectMessageOnLinkClickHandler
|
||||||
import org.mariotaku.twidere.util.MediaLoadingHandler
|
import org.mariotaku.twidere.util.MediaLoadingHandler
|
||||||
import org.mariotaku.twidere.util.TwidereLinkify
|
import org.mariotaku.twidere.util.TwidereLinkify
|
||||||
import org.mariotaku.twidere.view.holder.message.AbsMessageViewHolder
|
import org.mariotaku.twidere.view.holder.message.AbsMessageViewHolder
|
||||||
|
@ -50,7 +51,7 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
|
||||||
val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
|
val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
|
||||||
val linkHighlightingStyle: Int = preferences[linkHighlightOptionKey]
|
val linkHighlightingStyle: Int = preferences[linkHighlightOptionKey]
|
||||||
val nameFirst: Boolean = preferences[nameFirstKey]
|
val nameFirst: Boolean = preferences[nameFirstKey]
|
||||||
val linkify: TwidereLinkify = TwidereLinkify(null)
|
val linkify: TwidereLinkify = TwidereLinkify(DirectMessageOnLinkClickHandler(context, null, preferences))
|
||||||
val mediaLoadingHandler: MediaLoadingHandler = MediaLoadingHandler()
|
val mediaLoadingHandler: MediaLoadingHandler = MediaLoadingHandler()
|
||||||
|
|
||||||
var messages: List<ParcelableMessage>? = null
|
var messages: List<ParcelableMessage>? = null
|
||||||
|
|
|
@ -66,6 +66,7 @@ val mediaPreloadOnWifiOnlyKey = KBooleanKey(KEY_PRELOAD_WIFI_ONLY, true)
|
||||||
val autoRefreshCompatibilityModeKey = KBooleanKey("auto_refresh_compatibility_mode", Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
val autoRefreshCompatibilityModeKey = KBooleanKey("auto_refresh_compatibility_mode", Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||||
val floatingDetailedContentsKey = KBooleanKey("floating_detailed_contents", true)
|
val floatingDetailedContentsKey = KBooleanKey("floating_detailed_contents", true)
|
||||||
val localTrendsWoeIdKey = KIntKey(KEY_LOCAL_TRENDS_WOEID, 1)
|
val localTrendsWoeIdKey = KIntKey(KEY_LOCAL_TRENDS_WOEID, 1)
|
||||||
|
val phishingLinksWaringKey = KBooleanKey(KEY_PHISHING_LINK_WARNING, true)
|
||||||
|
|
||||||
object themeBackgroundAlphaKey : KSimpleKey<Int>(KEY_THEME_BACKGROUND_ALPHA, 0xFF) {
|
object themeBackgroundAlphaKey : KSimpleKey<Int>(KEY_THEME_BACKGROUND_ALPHA, 0xFF) {
|
||||||
override fun read(preferences: SharedPreferences): Int {
|
override fun read(preferences: SharedPreferences): Int {
|
||||||
|
|
|
@ -2,9 +2,8 @@ package org.mariotaku.twidere.model.util
|
||||||
|
|
||||||
import android.accounts.AccountManager
|
import android.accounts.AccountManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.mariotaku.ktextension.convert
|
||||||
import org.mariotaku.twidere.extension.model.unique_id_non_null
|
import org.mariotaku.twidere.extension.model.unique_id_non_null
|
||||||
|
|
||||||
import org.mariotaku.twidere.model.AccountDetails
|
|
||||||
import org.mariotaku.twidere.model.Draft
|
import org.mariotaku.twidere.model.Draft
|
||||||
import org.mariotaku.twidere.model.ParcelableStatusUpdate
|
import org.mariotaku.twidere.model.ParcelableStatusUpdate
|
||||||
import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
|
import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
|
||||||
|
@ -16,11 +15,9 @@ object ParcelableStatusUpdateUtils {
|
||||||
|
|
||||||
fun fromDraftItem(context: Context, draft: Draft): ParcelableStatusUpdate {
|
fun fromDraftItem(context: Context, draft: Draft): ParcelableStatusUpdate {
|
||||||
val statusUpdate = ParcelableStatusUpdate()
|
val statusUpdate = ParcelableStatusUpdate()
|
||||||
if (draft.account_keys != null) {
|
statusUpdate.accounts = draft.account_keys?.convert {
|
||||||
statusUpdate.accounts = AccountUtils.getAllAccountDetails(AccountManager.get(context), draft.account_keys!!, true)
|
AccountUtils.getAllAccountDetails(AccountManager.get(context), it, true)
|
||||||
} else {
|
} ?: emptyArray()
|
||||||
statusUpdate.accounts = arrayOfNulls<AccountDetails>(0)
|
|
||||||
}
|
|
||||||
statusUpdate.text = draft.text
|
statusUpdate.text = draft.text
|
||||||
statusUpdate.location = draft.location
|
statusUpdate.location = draft.location
|
||||||
statusUpdate.media = draft.media
|
statusUpdate.media = draft.media
|
||||||
|
@ -31,6 +28,7 @@ object ParcelableStatusUpdateUtils {
|
||||||
statusUpdate.display_coordinates = extra.displayCoordinates
|
statusUpdate.display_coordinates = extra.displayCoordinates
|
||||||
statusUpdate.attachment_url = extra.attachmentUrl
|
statusUpdate.attachment_url = extra.attachmentUrl
|
||||||
}
|
}
|
||||||
|
statusUpdate.draft_action = draft.action_type
|
||||||
statusUpdate.draft_unique_id = draft.unique_id_non_null
|
statusUpdate.draft_unique_id = draft.unique_id_non_null
|
||||||
return statusUpdate
|
return statusUpdate
|
||||||
}
|
}
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.mariotaku.twidere.service
|
package org.mariotaku.twidere.service
|
||||||
|
|
||||||
|
import android.accounts.AccountManager
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
@ -42,10 +43,7 @@ import nl.komponents.kovenant.task
|
||||||
import nl.komponents.kovenant.ui.successUi
|
import nl.komponents.kovenant.ui.successUi
|
||||||
import org.mariotaku.abstask.library.AbstractTask
|
import org.mariotaku.abstask.library.AbstractTask
|
||||||
import org.mariotaku.abstask.library.ManualTaskStarter
|
import org.mariotaku.abstask.library.ManualTaskStarter
|
||||||
import org.mariotaku.ktextension.configure
|
import org.mariotaku.ktextension.*
|
||||||
import org.mariotaku.ktextension.toLong
|
|
||||||
import org.mariotaku.ktextension.toTypedArray
|
|
||||||
import org.mariotaku.ktextension.useCursor
|
|
||||||
import org.mariotaku.microblog.library.MicroBlogException
|
import org.mariotaku.microblog.library.MicroBlogException
|
||||||
import org.mariotaku.microblog.library.twitter.TwitterUpload
|
import org.mariotaku.microblog.library.twitter.TwitterUpload
|
||||||
import org.mariotaku.microblog.library.twitter.model.MediaUploadResponse
|
import org.mariotaku.microblog.library.twitter.model.MediaUploadResponse
|
||||||
|
@ -59,13 +57,13 @@ import org.mariotaku.twidere.TwidereConstants.*
|
||||||
import org.mariotaku.twidere.model.*
|
import org.mariotaku.twidere.model.*
|
||||||
import org.mariotaku.twidere.model.draft.SendDirectMessageActionExtras
|
import org.mariotaku.twidere.model.draft.SendDirectMessageActionExtras
|
||||||
import org.mariotaku.twidere.model.draft.StatusObjectExtras
|
import org.mariotaku.twidere.model.draft.StatusObjectExtras
|
||||||
|
import org.mariotaku.twidere.model.util.AccountUtils
|
||||||
import org.mariotaku.twidere.model.util.ParcelableStatusUpdateUtils
|
import org.mariotaku.twidere.model.util.ParcelableStatusUpdateUtils
|
||||||
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
|
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
|
||||||
import org.mariotaku.twidere.task.CreateFavoriteTask
|
import org.mariotaku.twidere.task.CreateFavoriteTask
|
||||||
import org.mariotaku.twidere.task.RetweetStatusTask
|
import org.mariotaku.twidere.task.RetweetStatusTask
|
||||||
import org.mariotaku.twidere.task.SendMessageTask
|
import org.mariotaku.twidere.task.SendMessageTask
|
||||||
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
|
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
|
||||||
import org.mariotaku.twidere.util.ContentValuesCreator
|
|
||||||
import org.mariotaku.twidere.util.NotificationManagerWrapper
|
import org.mariotaku.twidere.util.NotificationManagerWrapper
|
||||||
import org.mariotaku.twidere.util.Utils
|
import org.mariotaku.twidere.util.Utils
|
||||||
import org.mariotaku.twidere.util.deleteDrafts
|
import org.mariotaku.twidere.util.deleteDrafts
|
||||||
|
@ -132,13 +130,21 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
when (draft.action_type) {
|
when (draft.action_type) {
|
||||||
Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2,
|
Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2,
|
||||||
Draft.Action.UPDATE_STATUS, Draft.Action.REPLY, Draft.Action.QUOTE -> {
|
Draft.Action.UPDATE_STATUS, Draft.Action.REPLY, Draft.Action.QUOTE -> {
|
||||||
updateStatuses(draft.action_type, ParcelableStatusUpdateUtils.fromDraftItem(this, draft))
|
updateStatuses(ParcelableStatusUpdateUtils.fromDraftItem(this, draft))
|
||||||
}
|
}
|
||||||
Draft.Action.SEND_DIRECT_MESSAGE_COMPAT, Draft.Action.SEND_DIRECT_MESSAGE -> {
|
Draft.Action.SEND_DIRECT_MESSAGE_COMPAT, Draft.Action.SEND_DIRECT_MESSAGE -> {
|
||||||
val recipientId = (draft.action_extras as? SendDirectMessageActionExtras)?.recipientId ?: return
|
val extras = draft.action_extras as? SendDirectMessageActionExtras ?: return
|
||||||
val accountKey = draft.account_keys?.firstOrNull() ?: return
|
val message = ParcelableNewMessage().apply {
|
||||||
val imageUri = draft.media.firstOrNull()?.uri
|
this.account = draft.account_keys?.firstOrNull()?.convert { key ->
|
||||||
sendMessage(accountKey, recipientId, draft.text, imageUri)
|
val am = AccountManager.get(this@LengthyOperationsService)
|
||||||
|
return@convert AccountUtils.getAccountDetails(am, key, true)
|
||||||
|
}
|
||||||
|
this.text = draft.text
|
||||||
|
this.media = draft.media
|
||||||
|
this.recipient_id = extras.recipientId
|
||||||
|
this.conversation_id = extras.conversationId
|
||||||
|
}
|
||||||
|
sendMessage(message)
|
||||||
}
|
}
|
||||||
Draft.Action.FAVORITE -> {
|
Draft.Action.FAVORITE -> {
|
||||||
performStatusAction(draft) { accountKey, status ->
|
performStatusAction(draft) { accountKey, status ->
|
||||||
|
@ -167,22 +173,18 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendDirectMessageIntent(intent: Intent) {
|
private fun handleSendDirectMessageIntent(intent: Intent) {
|
||||||
val accountId = intent.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
|
val message = intent.getParcelableExtra<ParcelableNewMessage>(EXTRA_MESSAGE) ?: return
|
||||||
val recipientId = intent.getStringExtra(EXTRA_RECIPIENT_ID)
|
sendMessage(message)
|
||||||
val text = intent.getStringExtra(EXTRA_TEXT)
|
|
||||||
val imageUri = intent.getStringExtra(EXTRA_IMAGE_URI)
|
|
||||||
if (accountId == null || recipientId == null || text == null) return
|
|
||||||
sendMessage(accountId, recipientId, text, imageUri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendMessage(accountId: UserKey, recipientId: String, text: String, imageUri: String?) {
|
private fun sendMessage(message: ParcelableNewMessage) {
|
||||||
val title = getString(R.string.sending_direct_message)
|
val title = getString(R.string.sending_direct_message)
|
||||||
val builder = Builder(this)
|
val builder = Builder(this)
|
||||||
builder.setSmallIcon(R.drawable.ic_stat_send)
|
builder.setSmallIcon(R.drawable.ic_stat_send)
|
||||||
builder.setProgress(100, 0, true)
|
builder.setProgress(100, 0, true)
|
||||||
builder.setTicker(title)
|
builder.setTicker(title)
|
||||||
builder.setContentTitle(title)
|
builder.setContentTitle(title)
|
||||||
builder.setContentText(text)
|
builder.setContentText(message.text)
|
||||||
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||||
builder.setOngoing(true)
|
builder.setOngoing(true)
|
||||||
val notification = builder.build()
|
val notification = builder.build()
|
||||||
|
@ -192,12 +194,18 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
val result = ManualTaskStarter.invokeExecute(task)
|
val result = ManualTaskStarter.invokeExecute(task)
|
||||||
invokeAfterExecute(task, result)
|
invokeAfterExecute(task, result)
|
||||||
|
|
||||||
val resolver = contentResolver
|
|
||||||
if (result.hasData()) {
|
if (result.hasData()) {
|
||||||
showOkMessage(R.string.message_direct_message_sent, false)
|
showOkMessage(R.string.message_direct_message_sent, false)
|
||||||
} else {
|
} else {
|
||||||
val values = ContentValuesCreator.createMessageDraft(accountId, recipientId, text, imageUri)
|
UpdateStatusTask.saveDraft(this, Draft.Action.SEND_DIRECT_MESSAGE) {
|
||||||
resolver.insert(Drafts.CONTENT_URI, values)
|
account_keys = arrayOf(message.account.key)
|
||||||
|
text = message.text
|
||||||
|
media = message.media
|
||||||
|
action_extras = SendDirectMessageActionExtras().apply {
|
||||||
|
recipientId = message.recipient_id
|
||||||
|
conversationId = message.conversation_id
|
||||||
|
}
|
||||||
|
}
|
||||||
showErrorMessage(R.string.action_sending_direct_message, result.exception, true)
|
showErrorMessage(R.string.action_sending_direct_message, result.exception, true)
|
||||||
}
|
}
|
||||||
stopForeground(false)
|
stopForeground(false)
|
||||||
|
@ -216,10 +224,11 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
return
|
return
|
||||||
@Draft.Action
|
@Draft.Action
|
||||||
val actionType = intent.getStringExtra(EXTRA_ACTION)
|
val actionType = intent.getStringExtra(EXTRA_ACTION)
|
||||||
updateStatuses(actionType, *statuses)
|
statuses.forEach { it.draft_action = actionType }
|
||||||
|
updateStatuses(*statuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateStatuses(@Draft.Action actionType: String, vararg statuses: ParcelableStatusUpdate) {
|
private fun updateStatuses(vararg statuses: ParcelableStatusUpdate) {
|
||||||
val context = this
|
val context = this
|
||||||
val builder = Builder(context)
|
val builder = Builder(context)
|
||||||
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
|
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
|
||||||
|
@ -289,7 +298,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
task.callback = this
|
task.callback = this
|
||||||
task.params = Pair(actionType, item)
|
task.params = item
|
||||||
invokeBeforeExecute(task)
|
invokeBeforeExecute(task)
|
||||||
|
|
||||||
val result = ManualTaskStarter.invokeExecute(task)
|
val result = ManualTaskStarter.invokeExecute(task)
|
||||||
|
@ -387,7 +396,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MessageMediaUploadListener(private val context: Context, private val manager: NotificationManagerWrapper,
|
internal class MessageMediaUploadListener(private val context: Context, private val manager: NotificationManagerWrapper,
|
||||||
builder: NotificationCompat.Builder, private val message: String) : ReadListener {
|
builder: NotificationCompat.Builder, private val message: String) : ReadListener {
|
||||||
|
|
||||||
var percent: Int = 0
|
var percent: Int = 0
|
||||||
|
|
||||||
|
@ -411,8 +420,8 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
private val BULK_SIZE = (128 * 1024).toLong() // 128KiB
|
private val BULK_SIZE = (128 * 1024).toLong() // 128KiB
|
||||||
|
|
||||||
private fun updateSendDirectMessageNotification(context: Context,
|
private fun updateSendDirectMessageNotification(context: Context,
|
||||||
builder: NotificationCompat.Builder,
|
builder: NotificationCompat.Builder,
|
||||||
progress: Int, message: String?): Notification {
|
progress: Int, message: String?): Notification {
|
||||||
builder.setContentTitle(context.getString(R.string.sending_direct_message))
|
builder.setContentTitle(context.getString(R.string.sending_direct_message))
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
builder.setContentText(message)
|
builder.setContentText(message)
|
||||||
|
@ -424,9 +433,9 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateUpdateStatusNotification(context: Context,
|
private fun updateUpdateStatusNotification(context: Context,
|
||||||
builder: NotificationCompat.Builder,
|
builder: NotificationCompat.Builder,
|
||||||
progress: Int,
|
progress: Int,
|
||||||
status: ParcelableStatusUpdate?): Notification {
|
status: ParcelableStatusUpdate?): Notification {
|
||||||
builder.setContentTitle(context.getString(R.string.updating_status_notification))
|
builder.setContentTitle(context.getString(R.string.updating_status_notification))
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
builder.setContentText(status.text)
|
builder.setContentText(status.text)
|
||||||
|
@ -438,7 +447,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateStatusesAsync(context: Context, @Draft.Action action: String,
|
fun updateStatusesAsync(context: Context, @Draft.Action action: String,
|
||||||
vararg statuses: ParcelableStatusUpdate) {
|
vararg statuses: ParcelableStatusUpdate) {
|
||||||
val intent = Intent(context, LengthyOperationsService::class.java)
|
val intent = Intent(context, LengthyOperationsService::class.java)
|
||||||
intent.action = INTENT_ACTION_UPDATE_STATUS
|
intent.action = INTENT_ACTION_UPDATE_STATUS
|
||||||
intent.putExtra(EXTRA_STATUSES, statuses)
|
intent.putExtra(EXTRA_STATUSES, statuses)
|
||||||
|
|
|
@ -50,7 +50,7 @@ class GetMessagesTask(
|
||||||
} catch (e: MicroBlogException) {
|
} catch (e: MicroBlogException) {
|
||||||
return@forEachIndexed
|
return@forEachIndexed
|
||||||
}
|
}
|
||||||
storeMessages(messages, details)
|
storeMessages(context, messages, details)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class GetMessagesTask(
|
||||||
bus.post(GetMessagesTaskEvent(Messages.CONTENT_URI, false, null))
|
bus.post(GetMessagesTaskEvent(Messages.CONTENT_URI, false, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
private fun getMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||||
when (details.type) {
|
when (details.type) {
|
||||||
AccountType.FANFOU -> {
|
AccountType.FANFOU -> {
|
||||||
// Use fanfou DM api
|
// Use fanfou DM api
|
||||||
|
@ -77,25 +77,25 @@ class GetMessagesTask(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTwitterOfficialMessages(microBlog: MicroBlog, details: AccountDetails,
|
private fun getTwitterOfficialMessages(microBlog: MicroBlog, details: AccountDetails,
|
||||||
param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||||
val conversationId = param.conversationId
|
val conversationId = param.conversationId
|
||||||
if (conversationId == null) {
|
if (conversationId == null) {
|
||||||
return getTwitterOfficialUserInbox(microBlog, details, param, index)
|
return getTwitterOfficialUserInbox(microBlog, details, param, index)
|
||||||
} else {
|
} else {
|
||||||
return GetMessagesData(emptyList(), emptyList())
|
return DatabaseUpdateData(emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFanfouMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
private fun getFanfouMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||||
val conversationId = param.conversationId
|
val conversationId = param.conversationId
|
||||||
if (conversationId == null) {
|
if (conversationId == null) {
|
||||||
return getFanfouConversations(microBlog, details, param, index)
|
return getFanfouConversations(microBlog, details, param, index)
|
||||||
} else {
|
} else {
|
||||||
return GetMessagesData(emptyList(), emptyList())
|
return DatabaseUpdateData(emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDefaultMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
private fun getDefaultMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||||
val accountKey = details.key
|
val accountKey = details.key
|
||||||
|
|
||||||
val sinceIds = if (param.hasSinceIds) param.sinceIds else null
|
val sinceIds = if (param.hasSinceIds) param.sinceIds else null
|
||||||
|
@ -137,7 +137,7 @@ class GetMessagesTask(
|
||||||
conversationIds.add(ParcelableMessageUtils.outgoingConversationId(it.senderId, it.recipientId))
|
conversationIds.add(ParcelableMessageUtils.outgoingConversationId(it.senderId, it.recipientId))
|
||||||
}
|
}
|
||||||
|
|
||||||
conversations.addLocalConversations(accountKey, conversationIds)
|
conversations.addLocalConversations(context, accountKey, conversationIds)
|
||||||
|
|
||||||
received.forEachIndexed { i, dm ->
|
received.forEachIndexed { i, dm ->
|
||||||
val message = ParcelableMessageUtils.fromMessage(accountKey, dm, false,
|
val message = ParcelableMessageUtils.fromMessage(accountKey, dm, false,
|
||||||
|
@ -151,7 +151,7 @@ class GetMessagesTask(
|
||||||
insertMessages.add(message)
|
insertMessages.add(message)
|
||||||
conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient))
|
conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient))
|
||||||
}
|
}
|
||||||
return GetMessagesData(conversations.values, insertMessages)
|
return DatabaseUpdateData(conversations.values, insertMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTwitterOfficialConversation(microBlog: MicroBlog, details: AccountDetails,
|
private fun getTwitterOfficialConversation(microBlog: MicroBlog, details: AccountDetails,
|
||||||
|
@ -159,8 +159,7 @@ class GetMessagesTask(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||||
val accountKey = details.key
|
|
||||||
val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null
|
val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null
|
||||||
val cursor = if (param.hasCursors) param.cursors?.get(index) else null
|
val cursor = if (param.hasCursors) param.cursors?.get(index) else null
|
||||||
val response = if (cursor != null) {
|
val response = if (cursor != null) {
|
||||||
|
@ -173,64 +172,11 @@ class GetMessagesTask(
|
||||||
}).userInbox
|
}).userInbox
|
||||||
}
|
}
|
||||||
|
|
||||||
val respConversations = response.conversations.orEmpty()
|
return createDatabaseUpdateData(context, details, response)
|
||||||
val respEntries = response.entries.orEmpty()
|
|
||||||
val respUsers = response.users.orEmpty()
|
|
||||||
|
|
||||||
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
|
||||||
|
|
||||||
respConversations.keys.let {
|
|
||||||
conversations.addLocalConversations(accountKey, it)
|
|
||||||
}
|
|
||||||
val messages = ArrayList<ParcelableMessage>()
|
|
||||||
val messageDeletionsMap = HashMap<String, ArrayList<String>>()
|
|
||||||
val conversationDeletions = ArrayList<String>()
|
|
||||||
respEntries.mapNotNullTo(messages) { entry ->
|
|
||||||
when {
|
|
||||||
entry.messageDelete != null -> {
|
|
||||||
val list = messageDeletionsMap.getOrPut(entry.messageDelete.conversationId) { ArrayList<String>() }
|
|
||||||
entry.messageDelete.messages?.forEach {
|
|
||||||
list.add(it.messageId)
|
|
||||||
}
|
|
||||||
return@mapNotNullTo null
|
|
||||||
}
|
|
||||||
entry.removeConversation != null -> {
|
|
||||||
conversationDeletions.add(entry.removeConversation.conversationId)
|
|
||||||
return@mapNotNullTo null
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return@mapNotNullTo ParcelableMessageUtils.fromEntry(accountKey, entry, respUsers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val messagesMap = messages.groupBy(ParcelableMessage::conversation_id)
|
|
||||||
for ((k, v) in respConversations) {
|
|
||||||
val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue
|
|
||||||
val participants = respUsers.filterKeys { userId ->
|
|
||||||
v.participants.any { it.userId == userId }
|
|
||||||
}.values
|
|
||||||
val conversationType = when (v.type?.toUpperCase(Locale.US)) {
|
|
||||||
DMResponse.Conversation.Type.ONE_TO_ONE -> ConversationType.ONE_TO_ONE
|
|
||||||
DMResponse.Conversation.Type.GROUP_DM -> ConversationType.GROUP
|
|
||||||
else -> ConversationType.ONE_TO_ONE
|
|
||||||
}
|
|
||||||
val conversation = conversations.addConversation(k, details, message, participants,
|
|
||||||
conversationType)
|
|
||||||
conversation.conversation_name = v.name
|
|
||||||
conversation.conversation_avatar = v.avatarImageHttps
|
|
||||||
conversation.request_cursor = response.cursor
|
|
||||||
conversation.conversation_extras_type = ParcelableMessageConversation.ExtrasType.TWITTER_OFFICIAL
|
|
||||||
conversation.conversation_extras = TwitterOfficialConversationExtras().apply {
|
|
||||||
this.minEntryId = v.minEntryId
|
|
||||||
this.maxEntryId = v.maxEntryId
|
|
||||||
this.status = v.status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GetMessagesData(conversations.values, messages, conversationDeletions,
|
|
||||||
messageDeletionsMap, response.cursor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFanfouConversations(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
|
||||||
|
private fun getFanfouConversations(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||||
val accountKey = details.key
|
val accountKey = details.key
|
||||||
val cursor = param.cursors?.get(index)
|
val cursor = param.cursors?.get(index)
|
||||||
val page = cursor?.substringAfter("page:").toInt(-1)
|
val page = cursor?.substringAfter("page:").toInt(-1)
|
||||||
|
@ -244,7 +190,7 @@ class GetMessagesTask(
|
||||||
|
|
||||||
val conversationIds = hashSetOf<String>()
|
val conversationIds = hashSetOf<String>()
|
||||||
result.mapTo(conversationIds) { "${accountKey.id}-${it.otherId}" }
|
result.mapTo(conversationIds) { "${accountKey.id}-${it.otherId}" }
|
||||||
conversations.addLocalConversations(accountKey, conversationIds)
|
conversations.addLocalConversations(context, accountKey, conversationIds)
|
||||||
result.forEachIndexed { i, item ->
|
result.forEachIndexed { i, item ->
|
||||||
val dm = item.dm
|
val dm = item.dm
|
||||||
// Sender is our self, treat as outgoing message
|
// Sender is our self, treat as outgoing message
|
||||||
|
@ -254,116 +200,10 @@ class GetMessagesTask(
|
||||||
setOf(dm.sender, dm.recipient))
|
setOf(dm.sender, dm.recipient))
|
||||||
mc.request_cursor = "page:$page"
|
mc.request_cursor = "page:$page"
|
||||||
}
|
}
|
||||||
return GetMessagesData(conversations.values, emptyList())
|
return DatabaseUpdateData(conversations.values, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("Recycle")
|
data class DatabaseUpdateData(
|
||||||
private fun MutableMap<String, ParcelableMessageConversation>.addLocalConversations(accountKey: UserKey, conversationIds: Set<String>) {
|
|
||||||
val where = Expression.and(Expression.inArgs(Conversations.CONVERSATION_ID, conversationIds.size),
|
|
||||||
Expression.equalsArgs(Conversations.ACCOUNT_KEY)).sql
|
|
||||||
val whereArgs = conversationIds.toTypedArray() + accountKey.toString()
|
|
||||||
return context.contentResolver.query(Conversations.CONTENT_URI, Conversations.COLUMNS,
|
|
||||||
where, whereArgs, null).useCursor { cur ->
|
|
||||||
val indices = ParcelableMessageConversationCursorIndices(cur)
|
|
||||||
cur.moveToFirst()
|
|
||||||
while (!cur.isAfterLast) {
|
|
||||||
val conversationId = cur.getString(indices.id)
|
|
||||||
val timestamp = cur.getLong(indices.local_timestamp)
|
|
||||||
val conversation = this[conversationId] ?: run {
|
|
||||||
val obj = indices.newObject(cur)
|
|
||||||
this[conversationId] = obj
|
|
||||||
return@run obj
|
|
||||||
}
|
|
||||||
if (timestamp > conversation.local_timestamp) {
|
|
||||||
this[conversationId] = indices.newObject(cur)
|
|
||||||
}
|
|
||||||
indices.newObject(cur)
|
|
||||||
cur.moveToNext()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ParcelableMessageConversation.addParticipant(
|
|
||||||
accountKey: UserKey,
|
|
||||||
user: User
|
|
||||||
) {
|
|
||||||
val userKey = UserKeyUtils.fromUser(user)
|
|
||||||
val participants = this.participants
|
|
||||||
if (participants == null) {
|
|
||||||
this.participants = arrayOf(ParcelableUserUtils.fromUser(user, accountKey))
|
|
||||||
} else {
|
|
||||||
val index = participants.indexOfFirst { it.key == userKey }
|
|
||||||
if (index >= 0) {
|
|
||||||
participants[index] = ParcelableUserUtils.fromUser(user, accountKey)
|
|
||||||
} else {
|
|
||||||
this.participants = participants + ParcelableUserUtils.fromUser(user, accountKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MutableMap<String, ParcelableMessageConversation>.addConversation(
|
|
||||||
conversationId: String,
|
|
||||||
details: AccountDetails,
|
|
||||||
message: ParcelableMessage,
|
|
||||||
users: Collection<User>,
|
|
||||||
conversationType: String = ConversationType.ONE_TO_ONE
|
|
||||||
): ParcelableMessageConversation {
|
|
||||||
val conversation = this[conversationId] ?: run {
|
|
||||||
val obj = ParcelableMessageConversation()
|
|
||||||
obj.id = conversationId
|
|
||||||
obj.conversation_type = conversationType
|
|
||||||
obj.applyFrom(message, details)
|
|
||||||
this[conversationId] = obj
|
|
||||||
return@run obj
|
|
||||||
}
|
|
||||||
if (message.timestamp > conversation.timestamp) {
|
|
||||||
conversation.applyFrom(message, details)
|
|
||||||
}
|
|
||||||
users.forEach { user ->
|
|
||||||
conversation.addParticipant(details.key, user)
|
|
||||||
}
|
|
||||||
return conversation
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun storeMessages(data: GetMessagesData, details: AccountDetails) {
|
|
||||||
val resolver = context.contentResolver
|
|
||||||
val conversationsValues = data.conversations.map {
|
|
||||||
val values = ParcelableMessageConversationValuesCreator.create(it)
|
|
||||||
if (it._id > 0) {
|
|
||||||
values.put(Conversations._ID, it._id)
|
|
||||||
}
|
|
||||||
return@map values
|
|
||||||
}
|
|
||||||
val messagesValues = data.messages.map(ParcelableMessageValuesCreator::create)
|
|
||||||
|
|
||||||
for ((conversationId, messageIds) in data.deleteMessages) {
|
|
||||||
val where = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
|
|
||||||
Expression.equalsArgs(Messages.CONVERSATION_ID)).sql
|
|
||||||
val whereArgs = arrayOf(details.key.toString(), conversationId)
|
|
||||||
ContentResolverUtils.bulkDelete(resolver, Messages.CONTENT_URI, Messages.MESSAGE_ID,
|
|
||||||
false, messageIds, where, whereArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
val accountWhere = Expression.equalsArgs(Messages.ACCOUNT_KEY).sql
|
|
||||||
val accountWhereArgs = arrayOf(details.key.toString())
|
|
||||||
|
|
||||||
ContentResolverUtils.bulkDelete(resolver, Conversations.CONTENT_URI, Conversations.CONVERSATION_ID,
|
|
||||||
false, data.deleteConversations, accountWhere, accountWhereArgs)
|
|
||||||
ContentResolverUtils.bulkDelete(resolver, Messages.CONTENT_URI, Messages.CONVERSATION_ID,
|
|
||||||
false, data.deleteConversations, accountWhere, accountWhereArgs)
|
|
||||||
|
|
||||||
ContentResolverUtils.bulkInsert(resolver, Conversations.CONTENT_URI, conversationsValues)
|
|
||||||
ContentResolverUtils.bulkInsert(resolver, Messages.CONTENT_URI, messagesValues)
|
|
||||||
|
|
||||||
if (data.conversationRequestCursor != null) {
|
|
||||||
resolver.update(Conversations.CONTENT_URI, ContentValues().apply {
|
|
||||||
put(Conversations.REQUEST_CURSOR, data.conversationRequestCursor)
|
|
||||||
}, accountWhere, accountWhereArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data class GetMessagesData(
|
|
||||||
val conversations: Collection<ParcelableMessageConversation>,
|
val conversations: Collection<ParcelableMessageConversation>,
|
||||||
val messages: Collection<ParcelableMessage>,
|
val messages: Collection<ParcelableMessage>,
|
||||||
val deleteConversations: List<String> = emptyList(),
|
val deleteConversations: List<String> = emptyList(),
|
||||||
|
@ -462,5 +302,175 @@ class GetMessagesTask(
|
||||||
get() = getAccountKeys()
|
get() = getAccountKeys()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun createDatabaseUpdateData(context: Context, account: AccountDetails,
|
||||||
|
response: DMResponse): DatabaseUpdateData {
|
||||||
|
val respConversations = response.conversations.orEmpty()
|
||||||
|
val respEntries = response.entries.orEmpty()
|
||||||
|
val respUsers = response.users.orEmpty()
|
||||||
|
|
||||||
|
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
||||||
|
|
||||||
|
respConversations.keys.let {
|
||||||
|
conversations.addLocalConversations(context, account.key, it)
|
||||||
|
}
|
||||||
|
val messages = ArrayList<ParcelableMessage>()
|
||||||
|
val messageDeletionsMap = HashMap<String, ArrayList<String>>()
|
||||||
|
val conversationDeletions = ArrayList<String>()
|
||||||
|
respEntries.mapNotNullTo(messages) { entry ->
|
||||||
|
when {
|
||||||
|
entry.messageDelete != null -> {
|
||||||
|
val list = messageDeletionsMap.getOrPut(entry.messageDelete.conversationId) { ArrayList<String>() }
|
||||||
|
entry.messageDelete.messages?.forEach {
|
||||||
|
list.add(it.messageId)
|
||||||
|
}
|
||||||
|
return@mapNotNullTo null
|
||||||
|
}
|
||||||
|
entry.removeConversation != null -> {
|
||||||
|
conversationDeletions.add(entry.removeConversation.conversationId)
|
||||||
|
return@mapNotNullTo null
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return@mapNotNullTo ParcelableMessageUtils.fromEntry(account.key, entry, respUsers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val messagesMap = messages.groupBy(ParcelableMessage::conversation_id)
|
||||||
|
for ((k, v) in respConversations) {
|
||||||
|
val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue
|
||||||
|
val participants = respUsers.filterKeys { userId ->
|
||||||
|
v.participants.any { it.userId == userId }
|
||||||
|
}.values
|
||||||
|
val conversationType = when (v.type?.toUpperCase(Locale.US)) {
|
||||||
|
DMResponse.Conversation.Type.ONE_TO_ONE -> ConversationType.ONE_TO_ONE
|
||||||
|
DMResponse.Conversation.Type.GROUP_DM -> ConversationType.GROUP
|
||||||
|
else -> ConversationType.ONE_TO_ONE
|
||||||
|
}
|
||||||
|
val conversation = conversations.addConversation(k, account, message, participants,
|
||||||
|
conversationType)
|
||||||
|
conversation.conversation_name = v.name
|
||||||
|
conversation.conversation_avatar = v.avatarImageHttps
|
||||||
|
conversation.request_cursor = response.cursor
|
||||||
|
conversation.conversation_extras_type = ParcelableMessageConversation.ExtrasType.TWITTER_OFFICIAL
|
||||||
|
conversation.conversation_extras = TwitterOfficialConversationExtras().apply {
|
||||||
|
this.minEntryId = v.minEntryId
|
||||||
|
this.maxEntryId = v.maxEntryId
|
||||||
|
this.status = v.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DatabaseUpdateData(conversations.values, messages, conversationDeletions,
|
||||||
|
messageDeletionsMap, response.cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun storeMessages(context: Context, data: DatabaseUpdateData, details: AccountDetails) {
|
||||||
|
val resolver = context.contentResolver
|
||||||
|
val conversationsValues = data.conversations.map {
|
||||||
|
val values = ParcelableMessageConversationValuesCreator.create(it)
|
||||||
|
if (it._id > 0) {
|
||||||
|
values.put(Conversations._ID, it._id)
|
||||||
|
}
|
||||||
|
return@map values
|
||||||
|
}
|
||||||
|
val messagesValues = data.messages.map(ParcelableMessageValuesCreator::create)
|
||||||
|
|
||||||
|
for ((conversationId, messageIds) in data.deleteMessages) {
|
||||||
|
val where = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
|
||||||
|
Expression.equalsArgs(Messages.CONVERSATION_ID)).sql
|
||||||
|
val whereArgs = arrayOf(details.key.toString(), conversationId)
|
||||||
|
ContentResolverUtils.bulkDelete(resolver, Messages.CONTENT_URI, Messages.MESSAGE_ID,
|
||||||
|
false, messageIds, where, whereArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
val accountWhere = Expression.equalsArgs(Messages.ACCOUNT_KEY).sql
|
||||||
|
val accountWhereArgs = arrayOf(details.key.toString())
|
||||||
|
|
||||||
|
ContentResolverUtils.bulkDelete(resolver, Conversations.CONTENT_URI, Conversations.CONVERSATION_ID,
|
||||||
|
false, data.deleteConversations, accountWhere, accountWhereArgs)
|
||||||
|
ContentResolverUtils.bulkDelete(resolver, Messages.CONTENT_URI, Messages.CONVERSATION_ID,
|
||||||
|
false, data.deleteConversations, accountWhere, accountWhereArgs)
|
||||||
|
|
||||||
|
ContentResolverUtils.bulkInsert(resolver, Conversations.CONTENT_URI, conversationsValues)
|
||||||
|
ContentResolverUtils.bulkInsert(resolver, Messages.CONTENT_URI, messagesValues)
|
||||||
|
|
||||||
|
if (data.conversationRequestCursor != null) {
|
||||||
|
resolver.update(Conversations.CONTENT_URI, ContentValues().apply {
|
||||||
|
put(Conversations.REQUEST_CURSOR, data.conversationRequestCursor)
|
||||||
|
}, accountWhere, accountWhereArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("Recycle")
|
||||||
|
fun MutableMap<String, ParcelableMessageConversation>.addLocalConversations(context: Context,
|
||||||
|
accountKey: UserKey, conversationIds: Set<String>) {
|
||||||
|
val where = Expression.and(Expression.inArgs(Conversations.CONVERSATION_ID, conversationIds.size),
|
||||||
|
Expression.equalsArgs(Conversations.ACCOUNT_KEY)).sql
|
||||||
|
val whereArgs = conversationIds.toTypedArray() + accountKey.toString()
|
||||||
|
return context.contentResolver.query(Conversations.CONTENT_URI, Conversations.COLUMNS,
|
||||||
|
where, whereArgs, null).useCursor { cur ->
|
||||||
|
val indices = ParcelableMessageConversationCursorIndices(cur)
|
||||||
|
cur.moveToFirst()
|
||||||
|
while (!cur.isAfterLast) {
|
||||||
|
val conversationId = cur.getString(indices.id)
|
||||||
|
val timestamp = cur.getLong(indices.local_timestamp)
|
||||||
|
val conversation = this[conversationId] ?: run {
|
||||||
|
val obj = indices.newObject(cur)
|
||||||
|
this[conversationId] = obj
|
||||||
|
return@run obj
|
||||||
|
}
|
||||||
|
if (timestamp > conversation.local_timestamp) {
|
||||||
|
this[conversationId] = indices.newObject(cur)
|
||||||
|
}
|
||||||
|
indices.newObject(cur)
|
||||||
|
cur.moveToNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ParcelableMessageConversation.addParticipant(
|
||||||
|
accountKey: UserKey,
|
||||||
|
user: User
|
||||||
|
) {
|
||||||
|
val userKey = UserKeyUtils.fromUser(user)
|
||||||
|
val participants = this.participants
|
||||||
|
if (participants == null) {
|
||||||
|
this.participants = arrayOf(ParcelableUserUtils.fromUser(user, accountKey))
|
||||||
|
} else {
|
||||||
|
val index = participants.indexOfFirst { it.key == userKey }
|
||||||
|
if (index >= 0) {
|
||||||
|
participants[index] = ParcelableUserUtils.fromUser(user, accountKey)
|
||||||
|
} else {
|
||||||
|
this.participants = participants + ParcelableUserUtils.fromUser(user, accountKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableMap<String, ParcelableMessageConversation>.addConversation(
|
||||||
|
conversationId: String,
|
||||||
|
details: AccountDetails,
|
||||||
|
message: ParcelableMessage,
|
||||||
|
users: Collection<User>,
|
||||||
|
conversationType: String = ConversationType.ONE_TO_ONE
|
||||||
|
): ParcelableMessageConversation {
|
||||||
|
val conversation = this[conversationId] ?: run {
|
||||||
|
val obj = ParcelableMessageConversation()
|
||||||
|
obj.id = conversationId
|
||||||
|
obj.conversation_type = conversationType
|
||||||
|
obj.applyFrom(message, details)
|
||||||
|
this[conversationId] = obj
|
||||||
|
return@run obj
|
||||||
|
}
|
||||||
|
if (message.timestamp > conversation.timestamp) {
|
||||||
|
conversation.applyFrom(message, details)
|
||||||
|
}
|
||||||
|
users.forEach { user ->
|
||||||
|
conversation.addParticipant(details.key, user)
|
||||||
|
}
|
||||||
|
return conversation
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,72 @@
|
||||||
package org.mariotaku.twidere.task
|
package org.mariotaku.twidere.task
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.mariotaku.twidere.model.ParcelableMessage
|
import org.mariotaku.microblog.library.MicroBlog
|
||||||
import org.mariotaku.twidere.model.SingleResponse
|
import org.mariotaku.microblog.library.MicroBlogException
|
||||||
|
import org.mariotaku.microblog.library.twitter.model.DirectMessage
|
||||||
|
import org.mariotaku.microblog.library.twitter.model.NewDm
|
||||||
|
import org.mariotaku.twidere.annotation.AccountType
|
||||||
|
import org.mariotaku.twidere.extension.model.isOfficial
|
||||||
|
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||||
|
import org.mariotaku.twidere.model.AccountDetails
|
||||||
|
import org.mariotaku.twidere.model.ParcelableMessageConversation
|
||||||
|
import org.mariotaku.twidere.model.ParcelableNewMessage
|
||||||
|
import org.mariotaku.twidere.model.util.ParcelableMessageUtils
|
||||||
|
import org.mariotaku.twidere.task.GetMessagesTask.Companion.addConversation
|
||||||
|
import org.mariotaku.twidere.task.GetMessagesTask.Companion.addLocalConversations
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by mariotaku on 2017/2/8.
|
* Created by mariotaku on 2017/2/8.
|
||||||
*/
|
*/
|
||||||
class SendMessageTask(
|
class SendMessageTask(
|
||||||
context: Context
|
context: Context
|
||||||
) : BaseAbstractTask<Unit, SingleResponse<ParcelableMessage>, Unit>(context) {
|
) : ExceptionHandlingAbstractTask<ParcelableNewMessage, Unit, MicroBlogException, Unit>(context) {
|
||||||
override fun doLongOperation(params: Unit?): SingleResponse<ParcelableMessage> {
|
override fun onExecute(params: ParcelableNewMessage) {
|
||||||
return SingleResponse(UnsupportedOperationException())
|
val account = params.account
|
||||||
|
val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java)
|
||||||
|
val updateData = requestSendMessage(microBlog, account, params)
|
||||||
|
GetMessagesTask.storeMessages(context, updateData, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun requestSendMessage(microBlog: MicroBlog, account: AccountDetails, message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData {
|
||||||
|
when (account.type) {
|
||||||
|
AccountType.TWITTER -> {
|
||||||
|
if (account.isOfficial(context)) {
|
||||||
|
return sendTwitterOfficialDM(microBlog, account, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccountType.FANFOU -> {
|
||||||
|
return sendFanfouDM(microBlog, account, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sendDefaultDM(microBlog, account, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendTwitterOfficialDM(microBlog: MicroBlog, account: AccountDetails, message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData {
|
||||||
|
val response = microBlog.sendDm(NewDm().apply {
|
||||||
|
setConversationId(message.conversation_id)
|
||||||
|
setText(message.text)
|
||||||
|
})
|
||||||
|
return GetMessagesTask.createDatabaseUpdateData(context, account, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendFanfouDM(microBlog: MicroBlog, account: AccountDetails, message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData {
|
||||||
|
val response = microBlog.sendFanfouDirectMessage(message.recipient_id, message.text)
|
||||||
|
return createDatabaseUpdateData(account, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendDefaultDM(microBlog: MicroBlog, account: AccountDetails, message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData {
|
||||||
|
val response = microBlog.sendDirectMessage(message.recipient_id, message.text)
|
||||||
|
return createDatabaseUpdateData(account, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDatabaseUpdateData(details: AccountDetails, dm: DirectMessage): GetMessagesTask.DatabaseUpdateData {
|
||||||
|
val accountKey = details.key
|
||||||
|
val conversationIds = setOf(ParcelableMessageUtils.outgoingConversationId(dm.senderId, dm.recipientId))
|
||||||
|
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
||||||
|
conversations.addLocalConversations(context, accountKey, conversationIds)
|
||||||
|
val message = ParcelableMessageUtils.fromMessage(accountKey, dm, true)
|
||||||
|
conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient))
|
||||||
|
return GetMessagesTask.DatabaseUpdateData(conversations.values, listOf(message))
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -62,14 +62,14 @@ import java.util.concurrent.TimeUnit
|
||||||
class UpdateStatusTask(
|
class UpdateStatusTask(
|
||||||
context: Context,
|
context: Context,
|
||||||
internal val stateCallback: UpdateStatusTask.StateCallback
|
internal val stateCallback: UpdateStatusTask.StateCallback
|
||||||
) : BaseAbstractTask<Pair<String, ParcelableStatusUpdate>, UpdateStatusTask.UpdateStatusResult, Any?>(context) {
|
) : BaseAbstractTask<ParcelableStatusUpdate, UpdateStatusTask.UpdateStatusResult, Any?>(context) {
|
||||||
|
|
||||||
override fun doLongOperation(params: Pair<String, ParcelableStatusUpdate>): UpdateStatusResult {
|
override fun doLongOperation(params: ParcelableStatusUpdate): UpdateStatusResult {
|
||||||
val draftId = saveDraft(params.first, params.second)
|
val draftId = saveDraft(params)
|
||||||
microBlogWrapper.addSendingDraftId(draftId)
|
microBlogWrapper.addSendingDraftId(draftId)
|
||||||
try {
|
try {
|
||||||
val result = doUpdateStatus(params.second, draftId)
|
val result = doUpdateStatus(params, draftId)
|
||||||
deleteOrUpdateDraft(params.second, result, draftId)
|
deleteOrUpdateDraft(params, result, draftId)
|
||||||
return result
|
return result
|
||||||
} catch (e: UpdateStatusException) {
|
} catch (e: UpdateStatusException) {
|
||||||
return UpdateStatusResult(e, draftId)
|
return UpdateStatusResult(e, draftId)
|
||||||
|
@ -85,16 +85,17 @@ class UpdateStatusTask(
|
||||||
override fun afterExecute(handler: Any?, result: UpdateStatusResult) {
|
override fun afterExecute(handler: Any?, result: UpdateStatusResult) {
|
||||||
stateCallback.afterExecute(result)
|
stateCallback.afterExecute(result)
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
logUpdateStatus(params.first, params.second, result)
|
logUpdateStatus(params, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logUpdateStatus(actionType: String, statusUpdate: ParcelableStatusUpdate, result: UpdateStatusResult) {
|
private fun logUpdateStatus(statusUpdate: ParcelableStatusUpdate, result: UpdateStatusResult) {
|
||||||
val mediaType = statusUpdate.media?.firstOrNull()?.type ?: ParcelableMedia.Type.UNKNOWN
|
val mediaType = statusUpdate.media?.firstOrNull()?.type ?: ParcelableMedia.Type.UNKNOWN
|
||||||
val hasLocation = statusUpdate.location != null
|
val hasLocation = statusUpdate.location != null
|
||||||
val preciseLocation = statusUpdate.display_coordinates
|
val preciseLocation = statusUpdate.display_coordinates
|
||||||
Analyzer.log(UpdateStatus(result.accountTypes.firstOrNull(), actionType, mediaType,
|
Analyzer.log(UpdateStatus(result.accountTypes.firstOrNull(), statusUpdate.draft_action,
|
||||||
hasLocation, preciseLocation, result.succeed, result.exceptions.firstOrNull() ?: result.exception))
|
mediaType, hasLocation, preciseLocation, result.succeed,
|
||||||
|
result.exceptions.firstOrNull() ?: result.exception))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(UpdateStatusException::class)
|
@Throws(UpdateStatusException::class)
|
||||||
|
@ -151,8 +152,8 @@ class UpdateStatusTask(
|
||||||
|
|
||||||
@Throws(UploadException::class)
|
@Throws(UploadException::class)
|
||||||
private fun uploadMedia(uploader: MediaUploaderInterface?,
|
private fun uploadMedia(uploader: MediaUploaderInterface?,
|
||||||
update: ParcelableStatusUpdate,
|
update: ParcelableStatusUpdate,
|
||||||
pendingUpdate: PendingStatusUpdate) {
|
pendingUpdate: PendingStatusUpdate) {
|
||||||
stateCallback.onStartUploadingMedia()
|
stateCallback.onStartUploadingMedia()
|
||||||
if (uploader == null) {
|
if (uploader == null) {
|
||||||
uploadMediaWithDefaultProvider(update, pendingUpdate)
|
uploadMediaWithDefaultProvider(update, pendingUpdate)
|
||||||
|
@ -163,8 +164,8 @@ class UpdateStatusTask(
|
||||||
|
|
||||||
@Throws(UploadException::class)
|
@Throws(UploadException::class)
|
||||||
private fun uploadMediaWithExtension(uploader: MediaUploaderInterface,
|
private fun uploadMediaWithExtension(uploader: MediaUploaderInterface,
|
||||||
update: ParcelableStatusUpdate,
|
update: ParcelableStatusUpdate,
|
||||||
pending: PendingStatusUpdate) {
|
pending: PendingStatusUpdate) {
|
||||||
uploader.waitForService()
|
uploader.waitForService()
|
||||||
val media: Array<UploaderMediaItem>
|
val media: Array<UploaderMediaItem>
|
||||||
try {
|
try {
|
||||||
|
@ -201,8 +202,8 @@ class UpdateStatusTask(
|
||||||
|
|
||||||
@Throws(UpdateStatusException::class)
|
@Throws(UpdateStatusException::class)
|
||||||
private fun shortenStatus(shortener: StatusShortenerInterface?,
|
private fun shortenStatus(shortener: StatusShortenerInterface?,
|
||||||
update: ParcelableStatusUpdate,
|
update: ParcelableStatusUpdate,
|
||||||
pending: PendingStatusUpdate) {
|
pending: PendingStatusUpdate) {
|
||||||
if (shortener == null) return
|
if (shortener == null) return
|
||||||
stateCallback.onShorteningStatus()
|
stateCallback.onShorteningStatus()
|
||||||
val sharedShortened = HashMap<UserKey, StatusShortenResult>()
|
val sharedShortened = HashMap<UserKey, StatusShortenResult>()
|
||||||
|
@ -280,8 +281,8 @@ class UpdateStatusTask(
|
||||||
|
|
||||||
@Throws(MicroBlogException::class, UploadException::class)
|
@Throws(MicroBlogException::class, UploadException::class)
|
||||||
private fun fanfouUpdateStatusWithPhoto(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
|
private fun fanfouUpdateStatusWithPhoto(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
|
||||||
pendingUpdate: PendingStatusUpdate, overrideText: String,
|
pendingUpdate: PendingStatusUpdate, overrideText: String,
|
||||||
sizeLimit: SizeLimit, updateIndex: Int): Status {
|
sizeLimit: SizeLimit, updateIndex: Int): Status {
|
||||||
if (statusUpdate.media.size > 1) {
|
if (statusUpdate.media.size > 1) {
|
||||||
throw MicroBlogException(context.getString(R.string.error_too_many_photos_fanfou))
|
throw MicroBlogException(context.getString(R.string.error_too_many_photos_fanfou))
|
||||||
}
|
}
|
||||||
|
@ -353,8 +354,8 @@ class UpdateStatusTask(
|
||||||
|
|
||||||
@Throws(MicroBlogException::class)
|
@Throws(MicroBlogException::class)
|
||||||
private fun twitterUpdateStatus(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
|
private fun twitterUpdateStatus(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
|
||||||
pendingUpdate: PendingStatusUpdate, overrideText: String,
|
pendingUpdate: PendingStatusUpdate, overrideText: String,
|
||||||
index: Int): Status {
|
index: Int): Status {
|
||||||
val status = StatusUpdate(overrideText)
|
val status = StatusUpdate(overrideText)
|
||||||
if (statusUpdate.in_reply_to_status != null) {
|
if (statusUpdate.in_reply_to_status != null) {
|
||||||
status.inReplyToStatusId(statusUpdate.in_reply_to_status.id)
|
status.inReplyToStatusId(statusUpdate.in_reply_to_status.id)
|
||||||
|
@ -500,7 +501,7 @@ class UpdateStatusTask(
|
||||||
|
|
||||||
@Throws(IOException::class, MicroBlogException::class)
|
@Throws(IOException::class, MicroBlogException::class)
|
||||||
private fun uploadMediaChucked(upload: TwitterUpload, body: Body,
|
private fun uploadMediaChucked(upload: TwitterUpload, body: Body,
|
||||||
ownerIds: Array<String>): MediaUploadResponse {
|
ownerIds: Array<String>): MediaUploadResponse {
|
||||||
val mediaType = body.contentType().contentType
|
val mediaType = body.contentType().contentType
|
||||||
val length = body.length()
|
val length = body.length()
|
||||||
val stream = body.stream()
|
val stream = body.stream()
|
||||||
|
@ -550,11 +551,10 @@ class UpdateStatusTask(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveDraft(@Draft.Action draftAction: String?, statusUpdate: ParcelableStatusUpdate): Long {
|
private fun saveDraft(statusUpdate: ParcelableStatusUpdate): Long {
|
||||||
return saveDraft(context, draftAction) {
|
return saveDraft(context, statusUpdate.draft_action ?: Draft.Action.UPDATE_STATUS) {
|
||||||
this.unique_id = statusUpdate.draft_unique_id ?: UUID.randomUUID().toString()
|
this.unique_id = statusUpdate.draft_unique_id ?: UUID.randomUUID().toString()
|
||||||
this.account_keys = statusUpdate.accounts.map { it.key }.toTypedArray()
|
this.account_keys = statusUpdate.accounts.map { it.key }.toTypedArray()
|
||||||
this.action_type = draftAction ?: Draft.Action.UPDATE_STATUS
|
|
||||||
this.text = statusUpdate.text
|
this.text = statusUpdate.text
|
||||||
this.location = statusUpdate.location
|
this.location = statusUpdate.location
|
||||||
this.media = statusUpdate.media
|
this.media = statusUpdate.media
|
||||||
|
@ -907,7 +907,7 @@ class UpdateStatusTask(
|
||||||
val deleteAlways: List<MediaDeletionItem>?
|
val deleteAlways: List<MediaDeletionItem>?
|
||||||
)
|
)
|
||||||
|
|
||||||
fun saveDraft(context: Context, @Draft.Action action: String?, config: Draft.() -> Unit): Long {
|
fun saveDraft(context: Context, @Draft.Action action: String, config: Draft.() -> Unit): Long {
|
||||||
val draft = Draft()
|
val draft = Draft()
|
||||||
draft.action_type = action
|
draft.action_type = action
|
||||||
draft.timestamp = System.currentTimeMillis()
|
draft.timestamp = System.currentTimeMillis()
|
||||||
|
|
|
@ -649,55 +649,6 @@ class AsyncTwitterWrapper(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal inner class DestroyMessageConversationTask(private val mAccountKey: UserKey, private val mUserId: String) : ManagedAsyncTask<Any, Any, SingleResponse<Boolean>>(context) {
|
|
||||||
|
|
||||||
private fun deleteMessages(accountKey: UserKey, userId: String) {
|
|
||||||
val whereArgs = arrayOf(accountKey.toString(), userId)
|
|
||||||
resolver.delete(DirectMessages.Inbox.CONTENT_URI, Expression.and(
|
|
||||||
Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY),
|
|
||||||
Expression.equalsArgs(Inbox.SENDER_ID)
|
|
||||||
).sql, whereArgs)
|
|
||||||
resolver.delete(DirectMessages.Outbox.CONTENT_URI, Expression.and(
|
|
||||||
Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY),
|
|
||||||
Expression.equalsArgs(Outbox.RECIPIENT_ID)
|
|
||||||
).sql, whereArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isMessageNotFound(e: Exception?): Boolean {
|
|
||||||
if (e !is MicroBlogException) return false
|
|
||||||
return e.errorCode == ErrorInfo.PAGE_NOT_FOUND || e.statusCode == HttpResponseCode.NOT_FOUND
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun doInBackground(vararg args: Any): SingleResponse<Boolean> {
|
|
||||||
val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return SingleResponse(MicroBlogException("No account"))
|
|
||||||
try {
|
|
||||||
microBlog.destroyDirectMessagesConversation(mAccountKey.id, mUserId)
|
|
||||||
deleteMessages(mAccountKey, mUserId)
|
|
||||||
return SingleResponse(true)
|
|
||||||
} catch (e: MicroBlogException) {
|
|
||||||
if (isMessageNotFound(e)) {
|
|
||||||
deleteMessages(mAccountKey, mUserId)
|
|
||||||
}
|
|
||||||
return SingleResponse(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onPostExecute(result: SingleResponse<Boolean>) {
|
|
||||||
super.onPostExecute(result)
|
|
||||||
if (result.hasData() || isMessageNotFound(result.exception)) {
|
|
||||||
Utils.showInfoMessage(context, R.string.message_direct_message_deleted, false)
|
|
||||||
} else {
|
|
||||||
Utils.showErrorMessage(context, R.string.action_deleting, result.exception, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal inner class DestroySavedSearchTask(
|
internal inner class DestroySavedSearchTask(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val accountKey: UserKey,
|
private val accountKey: UserKey,
|
||||||
|
|
|
@ -20,18 +20,19 @@
|
||||||
package org.mariotaku.twidere.util
|
package org.mariotaku.twidere.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.FragmentActivity
|
import android.support.v4.app.FragmentActivity
|
||||||
import org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME
|
import org.mariotaku.kpreferences.get
|
||||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_URI
|
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_URI
|
||||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_PHISHING_LINK_WARNING
|
import org.mariotaku.twidere.constant.phishingLinksWaringKey
|
||||||
import org.mariotaku.twidere.fragment.PhishingLinkWarningDialogFragment
|
import org.mariotaku.twidere.fragment.PhishingLinkWarningDialogFragment
|
||||||
|
|
||||||
class DirectMessageOnLinkClickHandler(
|
class DirectMessageOnLinkClickHandler(
|
||||||
context: Context,
|
context: Context,
|
||||||
manager: MultiSelectManager?,
|
manager: MultiSelectManager?,
|
||||||
preferences: SharedPreferencesWrapper
|
preferences: SharedPreferences
|
||||||
) : OnLinkClickHandler(context, manager, preferences) {
|
) : OnLinkClickHandler(context, manager, preferences) {
|
||||||
|
|
||||||
override val isPrivateData: Boolean
|
override val isPrivateData: Boolean
|
||||||
|
@ -43,8 +44,7 @@ class DirectMessageOnLinkClickHandler(
|
||||||
super.openLink(link)
|
super.openLink(link)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
if (context is FragmentActivity && preferences[phishingLinksWaringKey]) {
|
||||||
if (context is FragmentActivity && prefs.getBoolean(KEY_PHISHING_LINK_WARNING, true)) {
|
|
||||||
val fm = context.supportFragmentManager
|
val fm = context.supportFragmentManager
|
||||||
val fragment = PhishingLinkWarningDialogFragment()
|
val fragment = PhishingLinkWarningDialogFragment()
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
|
|
|
@ -62,11 +62,10 @@
|
||||||
app:caretWidth="@dimen/element_spacing_normal"
|
app:caretWidth="@dimen/element_spacing_normal"
|
||||||
app:cornerRadius="2dp">
|
app:cornerRadius="2dp">
|
||||||
|
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/mediaPreview"
|
android:layout_below="@+id/mediaPreview"
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/element_spacing_normal">
|
android:padding="@dimen/element_spacing_normal">
|
||||||
|
|
||||||
<org.mariotaku.twidere.view.CardMediaContainer
|
<org.mariotaku.twidere.view.CardMediaContainer
|
||||||
|
@ -80,16 +79,16 @@
|
||||||
|
|
||||||
</org.mariotaku.twidere.view.CardMediaContainer>
|
</org.mariotaku.twidere.view.CardMediaContainer>
|
||||||
|
|
||||||
|
|
||||||
<org.mariotaku.twidere.view.TimelineContentTextView
|
<org.mariotaku.twidere.view.TimelineContentTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/mediaPreview"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
tools:text="@string/sample_status_text"/>
|
tools:text="@string/sample_status_text"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</RelativeLayout>
|
||||||
</org.mariotaku.messagebubbleview.library.MessageBubbleView>
|
</org.mariotaku.messagebubbleview.library.MessageBubbleView>
|
||||||
|
|
||||||
<org.mariotaku.twidere.view.FixedTextView
|
<org.mariotaku.twidere.view.FixedTextView
|
||||||
|
|
Loading…
Reference in New Issue