diff --git a/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/text/TwitterValidatorTest.kt b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/text/TwitterValidatorTest.kt new file mode 100644 index 000000000..af54a067f --- /dev/null +++ b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/text/TwitterValidatorTest.kt @@ -0,0 +1,33 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util.text + +import junit.framework.Assert +import org.junit.Test + +class TwitterValidatorTest { + private val validator = TwitterValidator() + @Test + fun getTweetLength() { + Assert.assertEquals(280, validator.getTweetLength("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi placerat sodales libero id imperdiet. Aliquam malesuada volutpat nisl, quis elementum est tristique eget. In rhoncus lacus non sollicitudin tincidunt. Curabitur a turpis fringilla, varius nisl at, aliquet dolor metus.")) + Assert.assertEquals(280, validator.getTweetLength("一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十")) + } + +} \ No newline at end of file diff --git a/twidere/src/debug/kotlin/org/mariotaku/twidere/util/stetho/AccountsDumperPlugin.kt b/twidere/src/debug/kotlin/org/mariotaku/twidere/util/stetho/AccountsDumperPlugin.kt index b7f853706..b070fc237 100644 --- a/twidere/src/debug/kotlin/org/mariotaku/twidere/util/stetho/AccountsDumperPlugin.kt +++ b/twidere/src/debug/kotlin/org/mariotaku/twidere/util/stetho/AccountsDumperPlugin.kt @@ -37,6 +37,7 @@ import org.apache.commons.cli.* import org.json.JSONArray import org.json.JSONObject import org.mariotaku.ktextension.subArray +import org.mariotaku.twidere.exception.AccountNotFoundException import org.mariotaku.twidere.exception.NoAccountException import org.mariotaku.twidere.extension.model.updateDetails import org.mariotaku.twidere.model.AccountDetails @@ -308,7 +309,8 @@ class AccountsDumperPlugin(val context: Context) : DumperPlugin { private fun AccountManager.docContext(forKey: String): DocumentContext { val accountKey = UserKey.valueOf(forKey) - val details = AccountUtils.getAccountDetails(this, accountKey, true) ?: throw NoAccountException() + val details = AccountUtils.getAccountDetails(this, accountKey, true) + ?: throw AccountNotFoundException() val configuration = Configuration.builder() .jsonProvider(JsonOrgJsonProvider()) .mappingProvider(AsIsMappingProvider()) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/dagger/module/ApplicationModule.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/dagger/module/ApplicationModule.kt index 87a10e750..e4d4a7492 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/dagger/module/ApplicationModule.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/dagger/module/ApplicationModule.kt @@ -64,6 +64,7 @@ import org.mariotaku.twidere.util.refresh.JobSchedulerAutoRefreshController import org.mariotaku.twidere.util.refresh.LegacyAutoRefreshController import org.mariotaku.twidere.util.schedule.StatusScheduleProvider import org.mariotaku.twidere.util.sync.* +import org.mariotaku.twidere.util.text.TwitterValidator import java.io.File import javax.inject.Singleton @@ -196,7 +197,7 @@ class ApplicationModule(private val application: Application) { @Provides @Singleton fun validator(): Validator { - return Validator() + return TwitterValidator() } @Provides diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserProfileEditorFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserProfileEditorFragment.kt index dac69ad65..8dbb1f752 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserProfileEditorFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserProfileEditorFragment.kt @@ -65,6 +65,7 @@ import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.util.ParcelableUserUtils import org.mariotaku.twidere.task.* import org.mariotaku.twidere.util.* +import org.mariotaku.twidere.util.text.TwitterValidator import org.mariotaku.twidere.view.iface.IExtendedView.OnSizeChangedListener class UserProfileEditorFragment : BaseFragment(), OnSizeChangedListener, @@ -88,7 +89,7 @@ class UserProfileEditorFragment : BaseFragment(), OnSizeChangedListener, return } - val lengthChecker = TwitterValidatorMETLengthChecker(Validator()) + val lengthChecker = TwitterValidatorMETLengthChecker(TwitterValidator()) editDescription.setLengthChecker(lengthChecker) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt index ee00e1c64..6f511afe0 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt @@ -89,11 +89,10 @@ import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType import org.mariotaku.twidere.model.ParcelableMessageConversation.ExtrasType import org.mariotaku.twidere.model.util.AccountUtils -import org.mariotaku.twidere.promise.MessageConversationPromises +import org.mariotaku.twidere.promise.MessagePromises import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.task.status.UpdateStatusTask import org.mariotaku.twidere.task.twitter.message.AddParticipantsTask -import org.mariotaku.twidere.task.twitter.message.ClearMessagesTask import org.mariotaku.twidere.task.twitter.message.SetConversationNotificationDisabledTask import org.mariotaku.twidere.util.IntentUtils import org.mariotaku.twidere.view.holder.SimpleUserViewHolder @@ -297,7 +296,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, private fun performDestroyConversation() { val weakThis by weak(this) - showProgressDialog("leave_conversation_progress") and MessageConversationPromises.destroyConversation(context, accountKey, conversationId).successUi { + showProgressDialog("leave_conversation_progress") and MessagePromises.destroyConversation(context, accountKey, conversationId).successUi { val f = weakThis ?: return@successUi f.dismissProgressDialog("leave_conversation_progress") f.activity?.setResult(RESULT_CLOSE) @@ -306,18 +305,15 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, } private fun performClearMessages() { - ProgressDialogFragment.show(childFragmentManager, "clear_messages_progress") - val weakThis = WeakReference(this) - val task = ClearMessagesTask(context, accountKey, conversationId) - task.callback = callback@ { succeed -> - val f = weakThis.get() ?: return@callback + val weakThis by weak(this) + showProgressDialog("clear_messages_progress") and MessagePromises.clearMessages(context, accountKey, conversationId).successUi { succeed -> + val f = weakThis ?: return@successUi f.dismissDialogThen("clear_messages_progress") { if (succeed) { activity?.finish() } } } - TaskStarter.execute(task) } private fun performAddParticipant(user: ParcelableUser) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesConversationFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesConversationFragment.kt index 1d9e767af..21d393fef 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesConversationFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesConversationFragment.kt @@ -81,10 +81,9 @@ import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationTyp import org.mariotaku.twidere.model.event.GetMessagesTaskEvent import org.mariotaku.twidere.model.event.SendMessageTaskEvent import org.mariotaku.twidere.model.util.AccountUtils +import org.mariotaku.twidere.promise.MessagePromises import org.mariotaku.twidere.provider.TwidereDataStore.Messages import org.mariotaku.twidere.service.LengthyOperationsService -import org.mariotaku.twidere.util.obtainMedia -import org.mariotaku.twidere.task.twitter.message.DestroyMessageTask import org.mariotaku.twidere.task.twitter.message.GetMessagesTask import org.mariotaku.twidere.task.twitter.message.MarkMessageReadTask import org.mariotaku.twidere.util.* @@ -351,9 +350,9 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment { - val task = DestroyMessageTask(context, message.account_key, + // TODO show progress + MessagePromises.destroyMessage(context, message.account_key, message.conversation_id, message.id) - TaskStarter.execute(task) } } return true diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/promise/MessageConversationPromises.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/promise/MessageConversationPromises.kt deleted file mode 100644 index 79f3d2487..000000000 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/promise/MessageConversationPromises.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2017 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.promise - -import android.accounts.AccountManager -import android.content.Context -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.task -import org.mariotaku.ktextension.weak -import org.mariotaku.microblog.library.MicroBlog -import org.mariotaku.microblog.library.MicroBlogException -import org.mariotaku.sqliteqb.library.Expression -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.UserKey -import org.mariotaku.twidere.model.util.AccountUtils -import org.mariotaku.twidere.provider.TwidereDataStore.Messages -import org.mariotaku.twidere.task.twitter.message.ClearMessagesTask -import org.mariotaku.twidere.util.DataStoreUtils - -object MessageConversationPromises { - - fun destroyConversation(context: Context, accountKey: UserKey, conversationId: String): Promise { - val weakContext by weak(context) - return task { - val taskContext = weakContext ?: throw InterruptedException() - val account = AccountUtils.getAccountDetails(AccountManager.get(taskContext), accountKey, true) ?: - throw MicroBlogException("No account") - val conversation = DataStoreUtils.findMessageConversation(taskContext, accountKey, conversationId) - - var deleteMessages = true - var deleteConversation = true - // Only perform real deletion when it's not temp conversation (stored locally) - if (conversation != null) when { - conversation.conversation_extras_type != ParcelableMessageConversation.ExtrasType.TWITTER_OFFICIAL -> { - deleteMessages = false - deleteConversation = ClearMessagesTask.clearMessages(taskContext, account, conversationId) - } - !conversation.is_temp -> if (!requestDestroyConversation(taskContext, account, conversationId)) { - return@task false - } - } - - if (deleteMessages) { - val deleteMessageWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), - Expression.equalsArgs(Messages.CONVERSATION_ID)).sql - val deleteMessageWhereArgs = arrayOf(accountKey.toString(), conversationId) - taskContext.contentResolver.delete(Messages.CONTENT_URI, deleteMessageWhere, deleteMessageWhereArgs) - } - if (deleteConversation) { - val deleteConversationWhere = Expression.and(Expression.equalsArgs(Messages.Conversations.ACCOUNT_KEY), - Expression.equalsArgs(Messages.Conversations.CONVERSATION_ID)).sql - val deleteConversationWhereArgs = arrayOf(accountKey.toString(), conversationId) - taskContext.contentResolver.delete(Messages.Conversations.CONTENT_URI, deleteConversationWhere, deleteConversationWhereArgs) - } - return@task true - } - } - - private fun requestDestroyConversation(context: Context, account: AccountDetails, conversationId: String): Boolean { - when (account.type) { - AccountType.TWITTER -> { - if (account.isOfficial(context)) { - val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java) - return twitter.deleteDmConversation(conversationId).isSuccessful - } - } - } - return false - } -} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/promise/MessagePromises.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/promise/MessagePromises.kt new file mode 100644 index 000000000..79910507c --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/promise/MessagePromises.kt @@ -0,0 +1,189 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.promise + +import android.accounts.AccountManager +import android.content.Context +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.task +import org.mariotaku.ktextension.weak +import org.mariotaku.microblog.library.MicroBlog +import org.mariotaku.microblog.library.MicroBlogException +import org.mariotaku.sqliteqb.library.Expression +import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.exception.AccountNotFoundException +import org.mariotaku.twidere.extension.model.isOfficial +import org.mariotaku.twidere.extension.model.newMicroBlogInstance +import org.mariotaku.twidere.extension.queryReference +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.model.ParcelableMessageConversation +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.util.AccountUtils +import org.mariotaku.twidere.provider.TwidereDataStore.Messages +import org.mariotaku.twidere.util.DataStoreUtils +import org.mariotaku.twidere.util.content.ContentResolverUtils +import org.mariotaku.twidere.util.updateItems + +object MessagePromises { + + fun destroyConversation(context: Context, accountKey: UserKey, conversationId: String): Promise { + val weakContext by weak(context) + return task { + val taskContext = weakContext ?: throw InterruptedException() + val account = AccountUtils.getAccountDetails(AccountManager.get(taskContext), accountKey, true) ?: + throw MicroBlogException("No account") + val conversation = DataStoreUtils.findMessageConversation(taskContext, accountKey, conversationId) + + var deleteMessages = true + var deleteConversation = true + // Only perform real deletion when it's not temp conversation (stored locally) + if (conversation != null) when { + conversation.conversation_extras_type != ParcelableMessageConversation.ExtrasType.TWITTER_OFFICIAL -> { + deleteMessages = false + deleteConversation = clearMessagesSync(taskContext, account, conversationId) + } + !conversation.is_temp -> if (!requestDestroyConversation(taskContext, account, conversationId)) { + return@task false + } + } + + if (deleteMessages) { + val deleteMessageWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), + Expression.equalsArgs(Messages.CONVERSATION_ID)).sql + val deleteMessageWhereArgs = arrayOf(accountKey.toString(), conversationId) + taskContext.contentResolver.delete(Messages.CONTENT_URI, deleteMessageWhere, deleteMessageWhereArgs) + } + if (deleteConversation) { + val deleteConversationWhere = Expression.and(Expression.equalsArgs(Messages.Conversations.ACCOUNT_KEY), + Expression.equalsArgs(Messages.Conversations.CONVERSATION_ID)).sql + val deleteConversationWhereArgs = arrayOf(accountKey.toString(), conversationId) + taskContext.contentResolver.delete(Messages.Conversations.CONTENT_URI, deleteConversationWhere, deleteConversationWhereArgs) + } + return@task true + } + } + + fun destroyMessage(context: Context, accountKey: UserKey, conversationId: String?, + messageId: String): Promise { + val weakContext by weak(context) + return task { + val taskContext = weakContext ?: throw InterruptedException() + val account = AccountUtils.getAccountDetails(AccountManager.get(taskContext), accountKey, + true) ?: throw AccountNotFoundException() + if (!requestDestroyMessage(taskContext, account, messageId)) { + return@task false + } + val deleteWhere: String + val deleteWhereArgs: Array + if (conversationId != null) { + deleteWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), + Expression.equalsArgs(Messages.CONVERSATION_ID), + Expression.equalsArgs(Messages.MESSAGE_ID)).sql + deleteWhereArgs = arrayOf(accountKey.toString(), conversationId, messageId) + } else { + deleteWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), + Expression.equalsArgs(Messages.MESSAGE_ID)).sql + deleteWhereArgs = arrayOf(accountKey.toString(), messageId) + } + taskContext.contentResolver.delete(Messages.CONTENT_URI, deleteWhere, deleteWhereArgs) + return@task true + } + } + + + fun clearMessages(context: Context, accountKey: UserKey, conversationId: String): Promise { + val weakContext by weak(context) + return task { + val taskContext = weakContext ?: throw InterruptedException() + val account = AccountUtils.getAccountDetails(AccountManager.get(taskContext), accountKey, + true) ?: throw AccountNotFoundException() + clearMessagesSync(taskContext, account, conversationId) + } + } + + private fun clearMessagesSync(context: Context, account: AccountDetails, conversationId: String): Boolean { + val messagesWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), + Expression.equalsArgs(Messages.CONVERSATION_ID)).sql + val messagesWhereArgs = arrayOf(account.key.toString(), conversationId) + val projection = arrayOf(Messages.MESSAGE_ID) + val messageIds = mutableListOf() + var allSuccess = true + context.contentResolver.queryReference(Messages.CONTENT_URI, projection, messagesWhere, + messagesWhereArgs, null)?.use { (cur) -> + cur.moveToFirst() + while (!cur.isAfterLast) { + val messageId = cur.getString(0) + try { + if (requestDestroyMessage(context, account, messageId)) { + messageIds.add(messageId) + } + } catch (e: MicroBlogException) { + allSuccess = false + // Ignore + } + cur.moveToNext() + } + } + ContentResolverUtils.bulkDelete(context.contentResolver, Messages.CONTENT_URI, + Messages.MESSAGE_ID, false, messageIds, messagesWhere, messagesWhereArgs) + val conversationWhere = Expression.and(Expression.equalsArgs(Messages.Conversations.ACCOUNT_KEY), + Expression.equalsArgs(Messages.Conversations.CONVERSATION_ID)).sql + val conversationWhereArgs = arrayOf(account.key.toString(), conversationId) + context.contentResolver.updateItems(Messages.Conversations.CONTENT_URI, + Messages.Conversations.COLUMNS, conversationWhere, conversationWhereArgs, + cls = ParcelableMessageConversation::class.java) { item -> + item.message_extras = null + item.message_type = null + item.message_timestamp = -1L + item.text_unescaped = null + item.media = null + return@updateItems item + } + return allSuccess + } + + private fun requestDestroyConversation(context: Context, account: AccountDetails, conversationId: String): Boolean { + when (account.type) { + AccountType.TWITTER -> { + if (account.isOfficial(context)) { + val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java) + return twitter.deleteDmConversation(conversationId).isSuccessful + } + } + } + return false + } + + private fun requestDestroyMessage(context: Context, account: AccountDetails, + messageId: String): Boolean { + when (account.type) { + AccountType.TWITTER -> { + val twitter = account.newMicroBlogInstance(context, cls = MicroBlog::class.java) + if (account.isOfficial(context)) { + return twitter.destroyDm(messageId).isSuccessful + } + twitter.destroyDirectMessage(messageId) + return true + } + } + return false + } + +} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UpdateStatusTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UpdateStatusTask.kt index cf31eff42..5e8689985 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UpdateStatusTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UpdateStatusTask.kt @@ -33,7 +33,6 @@ import android.support.annotation.WorkerThread import android.support.media.ExifInterface import android.text.TextUtils import android.webkit.MimeTypeMap -import com.twitter.Validator import net.ypresto.androidtranscoder.MediaTranscoder import net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets import org.mariotaku.ktextension.* @@ -74,6 +73,7 @@ import org.mariotaku.twidere.task.BaseAbstractTask import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.io.ContentLengthInputStream import org.mariotaku.twidere.util.premium.ExtraFeaturesService +import org.mariotaku.twidere.util.text.TwitterValidator import java.io.Closeable import java.io.File import java.io.FileNotFoundException @@ -241,7 +241,7 @@ class UpdateStatusTask( update: ParcelableStatusUpdate, pending: PendingStatusUpdate) { if (shortener == null) return - val validator = Validator() + val validator = TwitterValidator() stateCallback.onShorteningStatus() val sharedShortened = HashMap() for (i in 0 until pending.length) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetSavedSearchesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetSavedSearchesTask.kt index 409ddb601..2711cae8c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetSavedSearchesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetSavedSearchesTask.kt @@ -24,7 +24,7 @@ import nl.komponents.kovenant.Promise import nl.komponents.kovenant.all import nl.komponents.kovenant.task import org.mariotaku.sqliteqb.library.Expression -import org.mariotaku.twidere.exception.NoAccountException +import org.mariotaku.twidere.exception.AccountNotFoundException import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches import org.mariotaku.twidere.task.PromiseTask @@ -40,7 +40,7 @@ class GetSavedSearchesTask( return@map task { val cr = context.contentResolver val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?: - throw NoAccountException() + throw AccountNotFoundException() val searches = twitter.savedSearches val values = ContentValuesCreator.createSavedSearches(searches, accountKey) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/ClearMessagesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/ClearMessagesTask.kt deleted file mode 100644 index 72f6e78a0..000000000 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/ClearMessagesTask.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2017 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.task.twitter.message - -import android.accounts.AccountManager -import android.content.Context -import org.mariotaku.microblog.library.MicroBlog -import org.mariotaku.microblog.library.MicroBlogException -import org.mariotaku.sqliteqb.library.Expression -import org.mariotaku.twidere.extension.model.newMicroBlogInstance -import org.mariotaku.twidere.extension.queryReference -import org.mariotaku.twidere.model.AccountDetails -import org.mariotaku.twidere.model.ParcelableMessageConversation -import org.mariotaku.twidere.model.UserKey -import org.mariotaku.twidere.model.util.AccountUtils -import org.mariotaku.twidere.provider.TwidereDataStore.Messages -import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask -import org.mariotaku.twidere.util.content.ContentResolverUtils -import org.mariotaku.twidere.util.updateItems - -/** - * Created by mariotaku on 2017/2/16. - */ - -class ClearMessagesTask( - context: Context, - val accountKey: UserKey, - val conversationId: String -) : ExceptionHandlingAbstractTask Unit)?>(context) { - - override val exceptionClass = MicroBlogException::class.java - - override fun onExecute(params: Unit?): Boolean { - val account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: - throw MicroBlogException("No account") - clearMessages(context, account, conversationId) - return true - } - - override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Boolean?, exception: MicroBlogException?) { - callback?.invoke(result ?: false) - } - - companion object { - - fun clearMessages(context: Context, account: AccountDetails, conversationId: String): Boolean { - val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java) - val messagesWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), - Expression.equalsArgs(Messages.CONVERSATION_ID)).sql - val messagesWhereArgs = arrayOf(account.key.toString(), conversationId) - val projection = arrayOf(Messages.MESSAGE_ID) - val messageIds = mutableListOf() - var allSuccess = true - context.contentResolver.queryReference(Messages.CONTENT_URI, projection, messagesWhere, - messagesWhereArgs, null)?.use { (cur) -> - cur.moveToFirst() - while (!cur.isAfterLast) { - val messageId = cur.getString(0) - try { - if (DestroyMessageTask.performDestroyMessage(context, microBlog, account, - messageId)) { - messageIds.add(messageId) - } - } catch (e: MicroBlogException) { - allSuccess = false - // Ignore - } - cur.moveToNext() - } - } - ContentResolverUtils.bulkDelete(context.contentResolver, Messages.CONTENT_URI, - Messages.MESSAGE_ID, false, messageIds, messagesWhere, messagesWhereArgs) - val conversationWhere = Expression.and(Expression.equalsArgs(Messages.Conversations.ACCOUNT_KEY), - Expression.equalsArgs(Messages.Conversations.CONVERSATION_ID)).sql - val conversationWhereArgs = arrayOf(account.key.toString(), conversationId) - context.contentResolver.updateItems(Messages.Conversations.CONTENT_URI, - Messages.Conversations.COLUMNS, conversationWhere, conversationWhereArgs, - cls = ParcelableMessageConversation::class.java) { item -> - item.message_extras = null - item.message_type = null - item.message_timestamp = -1L - item.text_unescaped = null - item.media = null - return@updateItems item - } - return allSuccess - } - - } -} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyMessageTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyMessageTask.kt deleted file mode 100644 index dd0779e6a..000000000 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyMessageTask.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2017 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.task.twitter.message - -import android.accounts.AccountManager -import android.content.Context -import org.mariotaku.microblog.library.MicroBlog -import org.mariotaku.microblog.library.MicroBlogException -import org.mariotaku.sqliteqb.library.Expression -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.UserKey -import org.mariotaku.twidere.model.util.AccountUtils -import org.mariotaku.twidere.provider.TwidereDataStore.Messages -import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask - -/** - * Created by mariotaku on 2017/2/16. - */ - -class DestroyMessageTask( - context: Context, - val accountKey: UserKey, - val conversationId: String?, - val messageId: String -) : ExceptionHandlingAbstractTask(context) { - - override val exceptionClass = MicroBlogException::class.java - - override fun onExecute(params: Unit?): Boolean { - val account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: - throw MicroBlogException("No account") - val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java) - if (!performDestroyMessage(context, microBlog, account, messageId)) { - return false - } - val deleteWhere: String - val deleteWhereArgs: Array - if (conversationId != null) { - deleteWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), - Expression.equalsArgs(Messages.CONVERSATION_ID), - Expression.equalsArgs(Messages.MESSAGE_ID)).sql - deleteWhereArgs = arrayOf(accountKey.toString(), conversationId, messageId) - } else { - deleteWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY), - Expression.equalsArgs(Messages.MESSAGE_ID)).sql - deleteWhereArgs = arrayOf(accountKey.toString(), messageId) - } - context.contentResolver.delete(Messages.CONTENT_URI, deleteWhere, deleteWhereArgs) - return true - } - - companion object { - - internal fun performDestroyMessage(context: Context, microBlog: MicroBlog, - account: AccountDetails, messageId: String): Boolean { - when (account.type) { - AccountType.TWITTER -> { - if (account.isOfficial(context)) { - return microBlog.destroyDm(messageId).isSuccessful - } - } - } - microBlog.destroyDirectMessage(messageId) - return true - } - - } -} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/TwitterValidator.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/TwitterValidator.kt new file mode 100644 index 000000000..18c5d93f5 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/TwitterValidator.kt @@ -0,0 +1,89 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util.text + +import com.twitter.Extractor +import com.twitter.Validator +import java.text.Normalizer + +class TwitterValidator: Validator() { + val maxWeightedTweetLength: Int = 280 + + val defaultWeight: Int = 200 + + var ranges: Array = arrayOf( + WeightRange(0, 4351, 100), + WeightRange(8192, 8205, 100), + WeightRange(8208, 8223, 100), + WeightRange(8242, 8247, 100) + ) + + private val extractor = Extractor() + + override fun getTweetLength(text: String): Int { + val normalized = Normalizer.normalize(text, Normalizer.Form.NFC) + var weightedLength = 0 + val inputLength = normalized.length + + var charOffset = 0 + while (charOffset < inputLength) { + val codePoint = Character.codePointAt(normalized, charOffset) + weightedLength += weightForCodePoint(codePoint) + charOffset += Character.charCount(codePoint) + } + + var length = weightedLength / 100 + + for (urlEntity in extractor.extractURLsWithIndices(normalized)) { + length += urlEntity.start - urlEntity.end + length += if (urlEntity.value.toLowerCase().startsWith("https://")) shortUrlLengthHttps else shortUrlLength + } + + return length + } + + override fun isValidTweet(text: String?): Boolean { + if (text == null || text.isEmpty()) { + return false + } + + for (c in text.toCharArray()) { + if (c == '\uFFFE' || c == '\uFEFF' || // BOM + c == '\uFFFF' || // Special + c in '\u202A'..'\u202E') { // Direction change + return false + } + } + + return getTweetLength(text) <= maxWeightedTweetLength + } + + private fun weightForCodePoint(codePoint: Int): Int { + val range = ranges.find { codePoint in it } ?: return defaultWeight + return range.weight + } + + data class WeightRange(val start: Int, val end: Int, val weight: Int) { + operator fun contains(codePoint: Int): Boolean { + return codePoint in start..end + } + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/controller/twitter/card/CardPollViewController.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/controller/twitter/card/CardPollViewController.kt index e497078ed..e04d1233b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/controller/twitter/card/CardPollViewController.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/controller/twitter/card/CardPollViewController.kt @@ -38,7 +38,7 @@ import org.mariotaku.ktextension.weak import org.mariotaku.microblog.library.twitter.TwitterCaps import org.mariotaku.microblog.library.twitter.model.CardDataMap import org.mariotaku.twidere.R -import org.mariotaku.twidere.exception.NoAccountException +import org.mariotaku.twidere.exception.AccountNotFoundException import org.mariotaku.twidere.extension.model.* import org.mariotaku.twidere.model.ParcelableCardEntity import org.mariotaku.twidere.model.ParcelableStatus @@ -92,7 +92,7 @@ class CardPollViewController : ContainerView.ViewController() { task { val vc = weakThis ?: throw IllegalStateException() val details = AccountUtils.getAccountDetails(AccountManager.get(vc.context), - card.account_key, true) ?: throw NoAccountException() + card.account_key, true) ?: throw AccountNotFoundException() val caps = details.newMicroBlogInstance(vc.context, cls = TwitterCaps::class.java) val params = CardDataMap() params.putString("card_uri", card.url) @@ -188,7 +188,7 @@ class CardPollViewController : ContainerView.ViewController() { task { val vc = weakThis ?: throw InterruptedException() val details = AccountUtils.getAccountDetails(AccountManager.get(vc.context), - card.account_key, true) ?: throw NoAccountException() + card.account_key, true) ?: throw AccountNotFoundException() val caps = details.newMicroBlogInstance(vc.context, cls = TwitterCaps::class.java) val cardEntity = caps.sendPassThrough(cardData).card return@task cardEntity.toParcelable(card.account_key, details.type)