supports dm link click

fixed dm media preview
improved dm draft
This commit is contained in:
Mariotaku Lee 2017-02-14 14:11:25 +08:00
parent da54c028bd
commit 7bb29e84b5
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
17 changed files with 453 additions and 311 deletions

View File

@ -23,6 +23,7 @@ package org.mariotaku.microblog.library.twitter.api;
import org.mariotaku.microblog.library.MicroBlogException;
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.Paging;
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;
@POST("/dm/new.json")
ResponseCode 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;
DMResponse sendDm(@Param NewDm newDm) throws MicroBlogException;
@GET("/dm/user_inbox.json")
UserInbox getUserInbox(@Query Paging paging) throws MicroBlogException;

View File

@ -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];
}
};
}

View File

@ -68,6 +68,10 @@ public class ParcelableStatusUpdate implements Parcelable {
@JsonField(name = "draft_unique_id")
@ParcelableThisPlease
public String draft_unique_id;
@JsonField(name = "draft_action")
@ParcelableThisPlease
@Draft.Action
public String draft_action;
public ParcelableStatusUpdate() {
}

View File

@ -37,6 +37,9 @@ public class SendDirectMessageActionExtras implements ActionExtras {
@ParcelableThisPlease
@JsonField(name = "recipient_id")
String recipientId;
@ParcelableThisPlease
@JsonField(name = "conversation_id")
String conversationId;
public String getRecipientId() {
return recipientId;
@ -46,6 +49,14 @@ public class SendDirectMessageActionExtras implements ActionExtras {
this.recipientId = recipientId;
}
public String getConversationId() {
return conversationId;
}
public void setConversationId(final String conversationId) {
this.conversationId = conversationId;
}
@Override
public int describeContents() {
return 0;

View File

@ -3,10 +3,6 @@ package org.mariotaku.ktextension
import android.os.Bundle
import android.os.Parcelable
/**
* Created by mariotaku on 16/8/18.
*/
inline fun Bundle(action: Bundle.() -> Unit): Bundle {
val bundle = Bundle()
action(bundle)

View File

@ -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)
}

View File

@ -33,6 +33,7 @@ import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.extension.model.timestamp
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
import org.mariotaku.twidere.util.DirectMessageOnLinkClickHandler
import org.mariotaku.twidere.util.MediaLoadingHandler
import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.view.holder.message.AbsMessageViewHolder
@ -50,7 +51,7 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
val linkHighlightingStyle: Int = preferences[linkHighlightOptionKey]
val nameFirst: Boolean = preferences[nameFirstKey]
val linkify: TwidereLinkify = TwidereLinkify(null)
val linkify: TwidereLinkify = TwidereLinkify(DirectMessageOnLinkClickHandler(context, null, preferences))
val mediaLoadingHandler: MediaLoadingHandler = MediaLoadingHandler()
var messages: List<ParcelableMessage>? = null

View File

@ -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 floatingDetailedContentsKey = KBooleanKey("floating_detailed_contents", true)
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) {
override fun read(preferences: SharedPreferences): Int {

View File

@ -2,9 +2,8 @@ package org.mariotaku.twidere.model.util
import android.accounts.AccountManager
import android.content.Context
import org.mariotaku.ktextension.convert
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.ParcelableStatusUpdate
import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
@ -16,11 +15,9 @@ object ParcelableStatusUpdateUtils {
fun fromDraftItem(context: Context, draft: Draft): ParcelableStatusUpdate {
val statusUpdate = ParcelableStatusUpdate()
if (draft.account_keys != null) {
statusUpdate.accounts = AccountUtils.getAllAccountDetails(AccountManager.get(context), draft.account_keys!!, true)
} else {
statusUpdate.accounts = arrayOfNulls<AccountDetails>(0)
}
statusUpdate.accounts = draft.account_keys?.convert {
AccountUtils.getAllAccountDetails(AccountManager.get(context), it, true)
} ?: emptyArray()
statusUpdate.text = draft.text
statusUpdate.location = draft.location
statusUpdate.media = draft.media
@ -31,6 +28,7 @@ object ParcelableStatusUpdateUtils {
statusUpdate.display_coordinates = extra.displayCoordinates
statusUpdate.attachment_url = extra.attachmentUrl
}
statusUpdate.draft_action = draft.action_type
statusUpdate.draft_unique_id = draft.unique_id_non_null
return statusUpdate
}

View File

@ -19,6 +19,7 @@
package org.mariotaku.twidere.service
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.app.Notification
import android.app.Service
@ -42,10 +43,7 @@ import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.ManualTaskStarter
import org.mariotaku.ktextension.configure
import org.mariotaku.ktextension.toLong
import org.mariotaku.ktextension.toTypedArray
import org.mariotaku.ktextension.useCursor
import org.mariotaku.ktextension.*
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.TwitterUpload
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.draft.SendDirectMessageActionExtras
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.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.task.CreateFavoriteTask
import org.mariotaku.twidere.task.RetweetStatusTask
import org.mariotaku.twidere.task.SendMessageTask
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
import org.mariotaku.twidere.util.ContentValuesCreator
import org.mariotaku.twidere.util.NotificationManagerWrapper
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.deleteDrafts
@ -132,13 +130,21 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
when (draft.action_type) {
Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2,
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 -> {
val recipientId = (draft.action_extras as? SendDirectMessageActionExtras)?.recipientId ?: return
val accountKey = draft.account_keys?.firstOrNull() ?: return
val imageUri = draft.media.firstOrNull()?.uri
sendMessage(accountKey, recipientId, draft.text, imageUri)
val extras = draft.action_extras as? SendDirectMessageActionExtras ?: return
val message = ParcelableNewMessage().apply {
this.account = draft.account_keys?.firstOrNull()?.convert { key ->
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 -> {
performStatusAction(draft) { accountKey, status ->
@ -167,22 +173,18 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
}
private fun handleSendDirectMessageIntent(intent: Intent) {
val accountId = intent.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
val recipientId = intent.getStringExtra(EXTRA_RECIPIENT_ID)
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)
val message = intent.getParcelableExtra<ParcelableNewMessage>(EXTRA_MESSAGE) ?: return
sendMessage(message)
}
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 builder = Builder(this)
builder.setSmallIcon(R.drawable.ic_stat_send)
builder.setProgress(100, 0, true)
builder.setTicker(title)
builder.setContentTitle(title)
builder.setContentText(text)
builder.setContentText(message.text)
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
builder.setOngoing(true)
val notification = builder.build()
@ -192,12 +194,18 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
val result = ManualTaskStarter.invokeExecute(task)
invokeAfterExecute(task, result)
val resolver = contentResolver
if (result.hasData()) {
showOkMessage(R.string.message_direct_message_sent, false)
} else {
val values = ContentValuesCreator.createMessageDraft(accountId, recipientId, text, imageUri)
resolver.insert(Drafts.CONTENT_URI, values)
UpdateStatusTask.saveDraft(this, Draft.Action.SEND_DIRECT_MESSAGE) {
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)
}
stopForeground(false)
@ -216,10 +224,11 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
return
@Draft.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 builder = Builder(context)
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
@ -289,7 +298,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
}
})
task.callback = this
task.params = Pair(actionType, item)
task.params = item
invokeBeforeExecute(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,
builder: NotificationCompat.Builder, private val message: String) : ReadListener {
builder: NotificationCompat.Builder, private val message: String) : ReadListener {
var percent: Int = 0
@ -411,8 +420,8 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
private val BULK_SIZE = (128 * 1024).toLong() // 128KiB
private fun updateSendDirectMessageNotification(context: Context,
builder: NotificationCompat.Builder,
progress: Int, message: String?): Notification {
builder: NotificationCompat.Builder,
progress: Int, message: String?): Notification {
builder.setContentTitle(context.getString(R.string.sending_direct_message))
if (message != null) {
builder.setContentText(message)
@ -424,9 +433,9 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
}
private fun updateUpdateStatusNotification(context: Context,
builder: NotificationCompat.Builder,
progress: Int,
status: ParcelableStatusUpdate?): Notification {
builder: NotificationCompat.Builder,
progress: Int,
status: ParcelableStatusUpdate?): Notification {
builder.setContentTitle(context.getString(R.string.updating_status_notification))
if (status != null) {
builder.setContentText(status.text)
@ -438,7 +447,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
}
fun updateStatusesAsync(context: Context, @Draft.Action action: String,
vararg statuses: ParcelableStatusUpdate) {
vararg statuses: ParcelableStatusUpdate) {
val intent = Intent(context, LengthyOperationsService::class.java)
intent.action = INTENT_ACTION_UPDATE_STATUS
intent.putExtra(EXTRA_STATUSES, statuses)

View File

@ -50,7 +50,7 @@ class GetMessagesTask(
} catch (e: MicroBlogException) {
return@forEachIndexed
}
storeMessages(messages, details)
storeMessages(context, messages, details)
}
}
@ -59,7 +59,7 @@ class GetMessagesTask(
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) {
AccountType.FANFOU -> {
// Use fanfou DM api
@ -77,25 +77,25 @@ class GetMessagesTask(
}
private fun getTwitterOfficialMessages(microBlog: MicroBlog, details: AccountDetails,
param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
val conversationId = param.conversationId
if (conversationId == null) {
return getTwitterOfficialUserInbox(microBlog, details, param, index)
} 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
if (conversationId == null) {
return getFanfouConversations(microBlog, details, param, index)
} 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 sinceIds = if (param.hasSinceIds) param.sinceIds else null
@ -137,7 +137,7 @@ class GetMessagesTask(
conversationIds.add(ParcelableMessageUtils.outgoingConversationId(it.senderId, it.recipientId))
}
conversations.addLocalConversations(accountKey, conversationIds)
conversations.addLocalConversations(context, accountKey, conversationIds)
received.forEachIndexed { i, dm ->
val message = ParcelableMessageUtils.fromMessage(accountKey, dm, false,
@ -151,7 +151,7 @@ class GetMessagesTask(
insertMessages.add(message)
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,
@ -159,8 +159,7 @@ class GetMessagesTask(
}
private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
val accountKey = details.key
private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null
val cursor = if (param.hasCursors) param.cursors?.get(index) else null
val response = if (cursor != null) {
@ -173,64 +172,11 @@ class GetMessagesTask(
}).userInbox
}
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(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)
return createDatabaseUpdateData(context, details, response)
}
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 cursor = param.cursors?.get(index)
val page = cursor?.substringAfter("page:").toInt(-1)
@ -244,7 +190,7 @@ class GetMessagesTask(
val conversationIds = hashSetOf<String>()
result.mapTo(conversationIds) { "${accountKey.id}-${it.otherId}" }
conversations.addLocalConversations(accountKey, conversationIds)
conversations.addLocalConversations(context, accountKey, conversationIds)
result.forEachIndexed { i, item ->
val dm = item.dm
// Sender is our self, treat as outgoing message
@ -254,116 +200,10 @@ class GetMessagesTask(
setOf(dm.sender, dm.recipient))
mc.request_cursor = "page:$page"
}
return GetMessagesData(conversations.values, emptyList())
return DatabaseUpdateData(conversations.values, emptyList())
}
@SuppressLint("Recycle")
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(
data class DatabaseUpdateData(
val conversations: Collection<ParcelableMessageConversation>,
val messages: Collection<ParcelableMessage>,
val deleteConversations: List<String> = emptyList(),
@ -462,5 +302,175 @@ class GetMessagesTask(
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
}
}
}

View File

@ -1,17 +1,72 @@
package org.mariotaku.twidere.task
import android.content.Context
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.SingleResponse
import org.mariotaku.microblog.library.MicroBlog
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.
*/
class SendMessageTask(
context: Context
) : BaseAbstractTask<Unit, SingleResponse<ParcelableMessage>, Unit>(context) {
override fun doLongOperation(params: Unit?): SingleResponse<ParcelableMessage> {
return SingleResponse(UnsupportedOperationException())
) : ExceptionHandlingAbstractTask<ParcelableNewMessage, Unit, MicroBlogException, Unit>(context) {
override fun onExecute(params: ParcelableNewMessage) {
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))
}
}

View File

@ -62,14 +62,14 @@ import java.util.concurrent.TimeUnit
class UpdateStatusTask(
context: Context,
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 {
val draftId = saveDraft(params.first, params.second)
override fun doLongOperation(params: ParcelableStatusUpdate): UpdateStatusResult {
val draftId = saveDraft(params)
microBlogWrapper.addSendingDraftId(draftId)
try {
val result = doUpdateStatus(params.second, draftId)
deleteOrUpdateDraft(params.second, result, draftId)
val result = doUpdateStatus(params, draftId)
deleteOrUpdateDraft(params, result, draftId)
return result
} catch (e: UpdateStatusException) {
return UpdateStatusResult(e, draftId)
@ -85,16 +85,17 @@ class UpdateStatusTask(
override fun afterExecute(handler: Any?, result: UpdateStatusResult) {
stateCallback.afterExecute(result)
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 hasLocation = statusUpdate.location != null
val preciseLocation = statusUpdate.display_coordinates
Analyzer.log(UpdateStatus(result.accountTypes.firstOrNull(), actionType, mediaType,
hasLocation, preciseLocation, result.succeed, result.exceptions.firstOrNull() ?: result.exception))
Analyzer.log(UpdateStatus(result.accountTypes.firstOrNull(), statusUpdate.draft_action,
mediaType, hasLocation, preciseLocation, result.succeed,
result.exceptions.firstOrNull() ?: result.exception))
}
@Throws(UpdateStatusException::class)
@ -151,8 +152,8 @@ class UpdateStatusTask(
@Throws(UploadException::class)
private fun uploadMedia(uploader: MediaUploaderInterface?,
update: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate) {
update: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate) {
stateCallback.onStartUploadingMedia()
if (uploader == null) {
uploadMediaWithDefaultProvider(update, pendingUpdate)
@ -163,8 +164,8 @@ class UpdateStatusTask(
@Throws(UploadException::class)
private fun uploadMediaWithExtension(uploader: MediaUploaderInterface,
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
uploader.waitForService()
val media: Array<UploaderMediaItem>
try {
@ -201,8 +202,8 @@ class UpdateStatusTask(
@Throws(UpdateStatusException::class)
private fun shortenStatus(shortener: StatusShortenerInterface?,
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
if (shortener == null) return
stateCallback.onShorteningStatus()
val sharedShortened = HashMap<UserKey, StatusShortenResult>()
@ -280,8 +281,8 @@ class UpdateStatusTask(
@Throws(MicroBlogException::class, UploadException::class)
private fun fanfouUpdateStatusWithPhoto(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate, overrideText: String,
sizeLimit: SizeLimit, updateIndex: Int): Status {
pendingUpdate: PendingStatusUpdate, overrideText: String,
sizeLimit: SizeLimit, updateIndex: Int): Status {
if (statusUpdate.media.size > 1) {
throw MicroBlogException(context.getString(R.string.error_too_many_photos_fanfou))
}
@ -353,8 +354,8 @@ class UpdateStatusTask(
@Throws(MicroBlogException::class)
private fun twitterUpdateStatus(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate, overrideText: String,
index: Int): Status {
pendingUpdate: PendingStatusUpdate, overrideText: String,
index: Int): Status {
val status = StatusUpdate(overrideText)
if (statusUpdate.in_reply_to_status != null) {
status.inReplyToStatusId(statusUpdate.in_reply_to_status.id)
@ -500,7 +501,7 @@ class UpdateStatusTask(
@Throws(IOException::class, MicroBlogException::class)
private fun uploadMediaChucked(upload: TwitterUpload, body: Body,
ownerIds: Array<String>): MediaUploadResponse {
ownerIds: Array<String>): MediaUploadResponse {
val mediaType = body.contentType().contentType
val length = body.length()
val stream = body.stream()
@ -550,11 +551,10 @@ class UpdateStatusTask(
}
}
private fun saveDraft(@Draft.Action draftAction: String?, statusUpdate: ParcelableStatusUpdate): Long {
return saveDraft(context, draftAction) {
private fun saveDraft(statusUpdate: ParcelableStatusUpdate): Long {
return saveDraft(context, statusUpdate.draft_action ?: Draft.Action.UPDATE_STATUS) {
this.unique_id = statusUpdate.draft_unique_id ?: UUID.randomUUID().toString()
this.account_keys = statusUpdate.accounts.map { it.key }.toTypedArray()
this.action_type = draftAction ?: Draft.Action.UPDATE_STATUS
this.text = statusUpdate.text
this.location = statusUpdate.location
this.media = statusUpdate.media
@ -907,7 +907,7 @@ class UpdateStatusTask(
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()
draft.action_type = action
draft.timestamp = System.currentTimeMillis()

View File

@ -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(
context: Context,
private val accountKey: UserKey,

View File

@ -20,18 +20,19 @@
package org.mariotaku.twidere.util
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
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.SharedPreferenceConstants.KEY_PHISHING_LINK_WARNING
import org.mariotaku.twidere.constant.phishingLinksWaringKey
import org.mariotaku.twidere.fragment.PhishingLinkWarningDialogFragment
class DirectMessageOnLinkClickHandler(
context: Context,
manager: MultiSelectManager?,
preferences: SharedPreferencesWrapper
preferences: SharedPreferences
) : OnLinkClickHandler(context, manager, preferences) {
override val isPrivateData: Boolean
@ -43,8 +44,7 @@ class DirectMessageOnLinkClickHandler(
super.openLink(link)
return
}
val prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
if (context is FragmentActivity && prefs.getBoolean(KEY_PHISHING_LINK_WARNING, true)) {
if (context is FragmentActivity && preferences[phishingLinksWaringKey]) {
val fm = context.supportFragmentManager
val fragment = PhishingLinkWarningDialogFragment()
val args = Bundle()

View File

@ -62,11 +62,10 @@
app:caretWidth="@dimen/element_spacing_normal"
app:cornerRadius="2dp">
<LinearLayout
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/mediaPreview"
android:orientation="vertical"
android:padding="@dimen/element_spacing_normal">
<org.mariotaku.twidere.view.CardMediaContainer
@ -80,16 +79,16 @@
</org.mariotaku.twidere.view.CardMediaContainer>
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/mediaPreview"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary"
tools:text="@string/sample_status_text"/>
</LinearLayout>
</RelativeLayout>
</org.mariotaku.messagebubbleview.library.MessageBubbleView>
<org.mariotaku.twidere.view.FixedTextView