From 7d07c4f7a343091e10858d9f027059dffa88e5a1 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Sun, 16 Apr 2017 16:47:02 +0800 Subject: [PATCH] fixed #761 --- build.gradle | 2 +- .../mariotaku/twidere/TwidereConstants.java | 1 + .../twidere/constant/IntentConstants.java | 1 + .../twidere/model/AccountDetails.java | 3 + .../model/draft/UpdateStatusActionExtras.java | 11 + .../twidere/provider/TwidereDataStore.java | 3 + .../twidere/activity/ComposeActivityTest.kt | 188 ++++++++++++++++++ .../activity/ComposeActivityTestRule.kt | 38 ++++ .../extension/TestAccountExtensions.kt | 34 ++++ .../twidere/util/TestAccountUtils.kt | 60 ++++++ .../org/mariotaku/twidere/util/TestCommons.kt | 30 +++ .../raw/account_4223092274_twitter_com.json | 63 ++++++ .../parcelable_status_852737226718838790.json | 60 ++++++ .../model/AccountExtensionsDebug.kt | 45 +++++ .../util/stetho/AccountsDumperPlugin.kt | 18 +- .../twidere/model/tab/TabConfiguration.java | 6 +- .../twidere/model/util/AccountUtils.java | 2 + .../twidere/activity/ComposeActivity.kt | 57 ++++-- .../twidere/app/TwidereApplication.kt | 9 - .../text/twitter/ExtractorExtensions.kt | 6 +- .../text/twitter/ValidatorExtensions.kt | 9 +- .../twidere/fragment/AbsActivitiesFragment.kt | 7 +- .../twidere/fragment/AbsStatusesFragment.kt | 7 +- .../twidere/fragment/HomeTimelineFragment.kt | 26 ++- .../fragment/InteractionsTimelineFragment.kt | 83 ++++---- .../task/twitter/GetActivitiesAboutMeTask.kt | 30 +-- .../twidere/task/twitter/GetActivitiesTask.kt | 17 +- .../task/twitter/GetHomeTimelineTask.kt | 3 +- .../twidere/task/twitter/UpdateStatusTask.kt | 6 +- .../util/api/TwitterConverterFactory.kt | 11 + .../twidere/util/sync/TimelineSyncManager.kt | 10 +- .../twidere/view/holder/DraftViewHolder.kt | 5 + 32 files changed, 710 insertions(+), 141 deletions(-) create mode 100644 twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTest.kt create mode 100644 twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTestRule.kt create mode 100644 twidere/src/androidTest/kotlin/org/mariotaku/twidere/extension/TestAccountExtensions.kt create mode 100644 twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestAccountUtils.kt create mode 100644 twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestCommons.kt create mode 100644 twidere/src/androidTest/res/raw/account_4223092274_twitter_com.json create mode 100644 twidere/src/androidTest/res/raw/parcelable_status_852737226718838790.json create mode 100644 twidere/src/debug/kotlin/org/mariotaku/twidere/extensions/model/AccountExtensionsDebug.kt diff --git a/build.gradle b/build.gradle index 4f375bfda..9852e20f2 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ subprojects { Kotlin : '1.1.1', SupportLib : '25.3.1', MariotakuCommons : '0.9.13', - RestFu : '0.9.48', + RestFu : '0.9.49', ObjectCursor : '0.9.16', PlayServices : '10.2.1', MapsUtils : '0.4.4', diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java index 652f0bc50..d59cfc4db 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java @@ -49,6 +49,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst String ACCOUNT_USER_DATA_EXTRAS = "extras"; String ACCOUNT_USER_DATA_COLOR = "color"; String ACCOUNT_USER_DATA_POSITION = "position"; + String ACCOUNT_USER_DATA_TEST = "test"; String LOGTAG = TWIDERE_APP_NAME; diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java index a709774e6..0fdae43ac 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java @@ -221,4 +221,5 @@ public interface IntentConstants { String EXTRA_PLACE = "place"; String EXTRA_PLACE_NAME = "place_name"; String EXTRA_SCHEDULE_INFO = "schedule_info"; + String EXTRA_SAVE_DRAFT = "save_draft"; } diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/AccountDetails.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/AccountDetails.java index 53de4aab0..81e1a3c0e 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/AccountDetails.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/AccountDetails.java @@ -87,6 +87,9 @@ public class AccountDetails implements Parcelable, Comparable { @JsonField(name = "dummy") public boolean dummy; + @JsonField(name = "test") + public boolean test; + @JsonField(name = "credentials", typeConverter = JsonStringConverter.class) @ParcelableNoThanks String credentials_json; diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/draft/UpdateStatusActionExtras.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/draft/UpdateStatusActionExtras.java index 85be9f521..dc55c4790 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/draft/UpdateStatusActionExtras.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/draft/UpdateStatusActionExtras.java @@ -57,6 +57,9 @@ public class UpdateStatusActionExtras implements ActionExtras { @JsonField(name = "extended_reply_mode") @ParcelableThisPlease boolean extendedReplyMode; + @JsonField(name = "editing_text") + @ParcelableThisPlease + String editingText; public ParcelableStatus getInReplyToStatus() { return inReplyToStatus; @@ -114,6 +117,14 @@ public class UpdateStatusActionExtras implements ActionExtras { this.extendedReplyMode = extendedReplyMode; } + public String getEditingText() { + return editingText; + } + + public void setEditingText(final String editingText) { + this.editingText = editingText; + } + @Override public int describeContents() { return 0; diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java index e5177d492..a72881cee 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java @@ -24,6 +24,7 @@ package org.mariotaku.twidere.provider; import android.content.ContentResolver; import android.net.Uri; import android.provider.BaseColumns; +import android.support.annotation.NonNull; import org.mariotaku.twidere.model.DraftTableInfo; import org.mariotaku.twidere.model.FiltersData$BaseItemTableInfo; @@ -588,6 +589,7 @@ public interface TwidereDataStore { String TABLE_NAME = "statuses"; String CONTENT_PATH = TABLE_NAME; + @NonNull Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH); /** @@ -802,6 +804,7 @@ public interface TwidereDataStore { String CONTENT_PATH = "activities_about_me"; String TABLE_NAME = "activities_about_me"; + @NonNull Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH); } diff --git a/twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTest.kt b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTest.kt new file mode 100644 index 000000000..ea606572f --- /dev/null +++ b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTest.kt @@ -0,0 +1,188 @@ +/* + * 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.activity + +import android.annotation.SuppressLint +import android.content.Intent +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 +import kotlinx.android.synthetic.main.activity_compose.* +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mariotaku.twidere.constant.IntentConstants.* +import org.mariotaku.twidere.model.ParcelableStatus +import org.mariotaku.twidere.model.ParcelableStatusUpdate +import org.mariotaku.twidere.test.R +import org.mariotaku.twidere.util.getJsonResource + +/** + * Created by mariotaku on 2017/4/16. + */ +@RunWith(AndroidJUnit4::class) +@SuppressLint("SetTextI18n") +class ComposeActivityTest { + + @get:Rule + val activityRule = ComposeActivityTestRule(launchActivity = false) + + @Test + fun testReply() { + val context = InstrumentationRegistry.getContext() + val targetContext = InstrumentationRegistry.getTargetContext() + val status: ParcelableStatus = context.resources.getJsonResource(R.raw.parcelable_status_848051071444410368) + val intent = Intent(INTENT_ACTION_REPLY) + intent.setClass(targetContext, ComposeActivity::class.java) + intent.putExtra(EXTRA_STATUS, status) + intent.putExtra(EXTRA_SAVE_DRAFT, true) + val activity = activityRule.launchActivity(intent) + val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply { + isAccessible = true + } + activityRule.runOnUiThread { + activity.editText.setText("@t_deyarmin @nixcraft @mariotaku Test Reply") + } + val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate + Assert.assertEquals("Test Reply", statusUpdate.text) + assertExcludedMatches(emptyArray(), statusUpdate) + activity.finish() + } + + @Test + fun testReplyRemovedSomeMentions() { + val context = InstrumentationRegistry.getContext() + val targetContext = InstrumentationRegistry.getTargetContext() + val status: ParcelableStatus = context.resources.getJsonResource(R.raw.parcelable_status_848051071444410368) + val intent = Intent(INTENT_ACTION_REPLY) + intent.setClass(targetContext, ComposeActivity::class.java) + intent.putExtra(EXTRA_STATUS, status) + intent.putExtra(EXTRA_SAVE_DRAFT, true) + val activity = activityRule.launchActivity(intent) + val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply { + isAccessible = true + } + activityRule.runOnUiThread { + activity.editText.setText("@t_deyarmin Test Reply") + } + val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate + Assert.assertEquals("Test Reply", statusUpdate.text) + assertExcludedMatches(arrayOf("17484680", "57610574"), statusUpdate) + activity.finish() + } + + @Test + fun testReplyNoMentions() { + val context = InstrumentationRegistry.getContext() + val targetContext = InstrumentationRegistry.getTargetContext() + val status: ParcelableStatus = context.resources.getJsonResource(R.raw.parcelable_status_848051071444410368) + val intent = Intent(INTENT_ACTION_REPLY) + intent.setClass(targetContext, ComposeActivity::class.java) + intent.putExtra(EXTRA_STATUS, status) + intent.putExtra(EXTRA_SAVE_DRAFT, true) + val activity = activityRule.launchActivity(intent) + val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply { + isAccessible = true + } + activityRule.runOnUiThread { + activity.editText.setText("Test Reply") + } + val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate + Assert.assertEquals("Test Reply", statusUpdate.text) + Assert.assertEquals("https://twitter.com/t_deyarmin/status/847950697987493888", + statusUpdate.attachment_url) + assertExcludedMatches(emptyArray(), statusUpdate) + activity.finish() + } + + @Test + fun testReplySelf() { + val context = InstrumentationRegistry.getContext() + val targetContext = InstrumentationRegistry.getTargetContext() + val status: ParcelableStatus = context.resources.getJsonResource(R.raw.parcelable_status_852737226718838790) + val intent = Intent(INTENT_ACTION_REPLY) + intent.setClass(targetContext, ComposeActivity::class.java) + intent.putExtra(EXTRA_STATUS, status) + intent.putExtra(EXTRA_SAVE_DRAFT, true) + val activity = activityRule.launchActivity(intent) + val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply { + isAccessible = true + } + activityRule.runOnUiThread { + activity.editText.setText("@TwidereProject @mariotaku Test Reply") + } + val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate + Assert.assertEquals("Test Reply", statusUpdate.text) + assertExcludedMatches(emptyArray(), statusUpdate) + activity.finish() + } + + @Test + fun testReplySelfRemovedSomeMentions() { + val context = InstrumentationRegistry.getContext() + val targetContext = InstrumentationRegistry.getTargetContext() + val status: ParcelableStatus = context.resources.getJsonResource(R.raw.parcelable_status_852737226718838790) + val intent = Intent(INTENT_ACTION_REPLY) + intent.setClass(targetContext, ComposeActivity::class.java) + intent.putExtra(EXTRA_STATUS, status) + intent.putExtra(EXTRA_SAVE_DRAFT, true) + val activity = activityRule.launchActivity(intent) + val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply { + isAccessible = true + } + activityRule.runOnUiThread { + activity.editText.setText("@TwidereProject Test Reply") + } + val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate + Assert.assertEquals("Test Reply", statusUpdate.text) + assertExcludedMatches(arrayOf("57610574"), statusUpdate) + activity.finish() + } + + @Test + fun testReplySelfNoMentions() { + val context = InstrumentationRegistry.getContext() + val targetContext = InstrumentationRegistry.getTargetContext() + val status: ParcelableStatus = context.resources.getJsonResource(R.raw.parcelable_status_852737226718838790) + val intent = Intent(INTENT_ACTION_REPLY) + intent.setClass(targetContext, ComposeActivity::class.java) + intent.putExtra(EXTRA_STATUS, status) + intent.putExtra(EXTRA_SAVE_DRAFT, true) + val activity = activityRule.launchActivity(intent) + val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply { + isAccessible = true + } + activityRule.runOnUiThread { + activity.editText.setText("Test Reply") + } + val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate + Assert.assertEquals("Test Reply", statusUpdate.text) + assertExcludedMatches(arrayOf("583328497", "57610574"), statusUpdate) + activity.finish() + } + + private fun assertExcludedMatches(expectedIds: Array, statusUpdate: ParcelableStatusUpdate): Boolean { + return statusUpdate.excluded_reply_user_ids?.all { excludedId -> + expectedIds.any { expectation -> + expectation.equals(excludedId, ignoreCase = true) + } + } ?: expectedIds.isEmpty() + } +} diff --git a/twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTestRule.kt b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTestRule.kt new file mode 100644 index 000000000..e07a6d16f --- /dev/null +++ b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/activity/ComposeActivityTestRule.kt @@ -0,0 +1,38 @@ +/* + * 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.activity + +import android.support.test.rule.ActivityTestRule +import org.mariotaku.twidere.util.TestAccountUtils + +/** + * Created by mariotaku on 2017/4/16. + */ +class ComposeActivityTestRule(initialTouchMode: Boolean = false, launchActivity: Boolean = true) : + ActivityTestRule(ComposeActivity::class.java, initialTouchMode, launchActivity) { + + override fun beforeActivityLaunched() { + TestAccountUtils.insertTestAccounts() + } + + override fun afterActivityFinished() { + TestAccountUtils.removeTestAccounts() + } +} \ No newline at end of file diff --git a/twidere/src/androidTest/kotlin/org/mariotaku/twidere/extension/TestAccountExtensions.kt b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/extension/TestAccountExtensions.kt new file mode 100644 index 000000000..1126b31c7 --- /dev/null +++ b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/extension/TestAccountExtensions.kt @@ -0,0 +1,34 @@ +/* + * 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.extension + +import android.accounts.Account +import android.accounts.AccountManager +import org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_TEST +import org.mariotaku.twidere.extension.model.AccountDataQueue + + +fun Account.isTest(am: AccountManager): Boolean { + return AccountDataQueue.getUserData(am, this, ACCOUNT_USER_DATA_TEST)?.toBoolean() ?: true +} + +fun Account.setTest(am: AccountManager, test: Boolean) { + am.setUserData(this, ACCOUNT_USER_DATA_TEST, test.toString()) +} \ No newline at end of file diff --git a/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestAccountUtils.kt b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestAccountUtils.kt new file mode 100644 index 000000000..1dd4d5576 --- /dev/null +++ b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestAccountUtils.kt @@ -0,0 +1,60 @@ +/* + * 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 + +import android.accounts.AccountManager +import android.support.test.InstrumentationRegistry +import org.mariotaku.twidere.extensions.model.updateDetails +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.model.util.AccountUtils +import org.mariotaku.twidere.test.R +import org.mariotaku.twidere.util.support.removeAccountSupport + +/** + * Created by mariotaku on 2017/4/16. + */ +object TestAccountUtils { + + private val accountResources = intArrayOf(R.raw.account_4223092274_twitter_com) + + fun insertTestAccounts() { + val targetContext = InstrumentationRegistry.getTargetContext() + val context = InstrumentationRegistry.getContext() + val am = AccountManager.get(targetContext) + val existingAccounts = AccountUtils.getAllAccountDetails(am, false) + accountResources.forEach { resId -> + val details = context.resources.openRawResource(resId).use { + JsonSerializer.parse(it, AccountDetails::class.java) + } + if (existingAccounts.any { it.account == details.account || it.key == details.key }) { + return@forEach + } + am.addAccountExplicitly(details.account, null, null) + details.account.updateDetails(am, details) + } + } + + fun removeTestAccounts() { + val targetContext = InstrumentationRegistry.getTargetContext() + val am = AccountManager.get(targetContext) + val existingAccounts = AccountUtils.getAllAccountDetails(am, false) + existingAccounts.filter { it.test }.forEach { am.removeAccountSupport(it.account) } + } +} \ No newline at end of file diff --git a/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestCommons.kt b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestCommons.kt new file mode 100644 index 000000000..c17f11b78 --- /dev/null +++ b/twidere/src/androidTest/kotlin/org/mariotaku/twidere/util/TestCommons.kt @@ -0,0 +1,30 @@ +/* + * 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 + +import android.content.res.Resources +import android.support.annotation.RawRes + +/** + * Created by mariotaku on 2017/4/16. + */ +inline fun Resources.getJsonResource(@RawRes id: Int) = openRawResource(id).use { + JsonSerializer.parse(it, T::class.java) +} \ No newline at end of file diff --git a/twidere/src/androidTest/res/raw/account_4223092274_twitter_com.json b/twidere/src/androidTest/res/raw/account_4223092274_twitter_com.json new file mode 100644 index 000000000..d2a67a4c4 --- /dev/null +++ b/twidere/src/androidTest/res/raw/account_4223092274_twitter_com.json @@ -0,0 +1,63 @@ +{ + "account": { + "name": "TwidereTest@twitter.com", + "type": "org.mariotaku.twidere.account" + }, + "activated": false, + "color": "#F44336", + "credentials": { + "access_token": "4223092274-REDACTED", + "access_token_secret": "REDACTED", + "consumer_key": "REDACTED", + "consumer_secret": "REDACTED", + "same_oauth_signing_url": false, + "api_url_format": "https:\/\/[DOMAIN.]twitter.com\/", + "no_version_suffix": false + }, + "credentials_type": "oauth", + "dummy": false, + "test": true, + "extras": { + "official_credentials": false + }, + "key": "4223092274@twitter.com", + "position": 4, + "type": "twitter", + "user": { + "account_id": "4223092274@twitter.com", + "background_color": "#C0DEED", + "created_at": 1447415354000, + "description_plain": "Posting test tweets, do not follow", + "description_spans": [], + "description_unescaped": "Posting test tweets, do not follow", + "extras": { + "blocked_by": false, + "blocking": false, + "followed_by": false, + "groups_count": -1, + "muting": false, + "notifications_enabled": false + }, + "favorites_count": 0, + "followers_count": 1, + "friends_count": 2, + "is_basic": false, + "is_cache": true, + "is_follow_request_sent": false, + "is_following": false, + "is_protected": true, + "is_verified": false, + "id": "4223092274@twitter.com", + "link_color": "#1DA1F2", + "listed_count": 0, + "location": "", + "media_count": -1, + "name": "Twidere Test", + "position": 0, + "profile_background_url": "https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png", + "profile_image_url": "https:\/\/pbs.twimg.com\/profile_images\/665134634229874688\/gQA1EEUb_reasonably_small.png", + "screen_name": "TwidereTest", + "statuses_count": 7, + "text_color": "#333333" + } +} \ No newline at end of file diff --git a/twidere/src/androidTest/res/raw/parcelable_status_852737226718838790.json b/twidere/src/androidTest/res/raw/parcelable_status_852737226718838790.json new file mode 100644 index 000000000..51f5ef537 --- /dev/null +++ b/twidere/src/androidTest/res/raw/parcelable_status_852737226718838790.json @@ -0,0 +1,60 @@ +{ + "account_color": -769226, + "account_id": "4223092274@twitter.com", + "extras": { + "display_text_range": [ + 27, + 33 + ], + "support_entities": true, + "user_profile_image_url_fallback": "https://pbs.twimg.com/profile_images/665134634229874688/gQA1EEUb_normal.png" + }, + "favorite_count": 0, + "id": "852737226718838790", + "in_reply_to_name": "Twidere Project", + "in_reply_to_screen_name": "TwidereProject", + "in_reply_to_status_id": "852736831107944448", + "in_reply_to_user_id": "583328497@twitter.com", + "is_favorite": false, + "is_gap": false, + "is_possibly_sensitive": false, + "is_quote": false, + "is_retweet": false, + "lang": "zh", + "media": [], + "mentions": [ + { + "id": "583328497@twitter.com", + "name": "Twidere Project", + "screen_name": "TwidereProject" + }, + { + "id": "57610574@twitter.com", + "name": "宅撩一边捣鼓着Twidere一边", + "screen_name": "mariotaku" + } + ], + "position_key": 1492143372225, + "quoted_timestamp": 0, + "quoted_user_is_protected": false, + "quoted_user_is_verified": false, + "quoted_user_id": null, + "reply_count": -1, + "retweet_count": 0, + "retweet_timestamp": -1, + "retweeted": false, + "retweeted_by_user_id": null, + "sort_id": 852737226718838790, + "source": "Twidere for Android #7", + "spans": [], + "text_plain": "@TwidereProject @mariotaku 测试回复全部", + "text_unescaped": "@TwidereProject @mariotaku 测试回复全部", + "timestamp": 1492143372000, + "user_is_following": false, + "user_is_protected": true, + "user_is_verified": false, + "user_id": "4223092274@twitter.com", + "user_name": "Twidere Test", + "user_profile_image_url": "https://pbs.twimg.com/profile_images/665134634229874688/gQA1EEUb_reasonably_small.png", + "user_screen_name": "TwidereTest" +} \ No newline at end of file diff --git a/twidere/src/debug/kotlin/org/mariotaku/twidere/extensions/model/AccountExtensionsDebug.kt b/twidere/src/debug/kotlin/org/mariotaku/twidere/extensions/model/AccountExtensionsDebug.kt new file mode 100644 index 000000000..9d2f0ba6c --- /dev/null +++ b/twidere/src/debug/kotlin/org/mariotaku/twidere/extensions/model/AccountExtensionsDebug.kt @@ -0,0 +1,45 @@ +/* + * 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.extensions.model + +import android.accounts.Account +import android.accounts.AccountManager +import org.mariotaku.ktextension.HexColorFormat +import org.mariotaku.ktextension.toHexColor +import org.mariotaku.twidere.TwidereConstants.* +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.util.JsonSerializer + +/** + * Created by mariotaku on 2017/4/16. + */ +fun Account.updateDetails(am: AccountManager, details: AccountDetails) { + am.setUserData(this, ACCOUNT_USER_DATA_KEY, details.key.toString()) + am.setUserData(this, ACCOUNT_USER_DATA_TYPE, details.type) + am.setUserData(this, ACCOUNT_USER_DATA_CREDS_TYPE, details.credentials_type) + + am.setUserData(this, ACCOUNT_USER_DATA_ACTIVATED, details.activated.toString()) + am.setUserData(this, ACCOUNT_USER_DATA_TEST, details.test.toString()) + am.setUserData(this, ACCOUNT_USER_DATA_COLOR, toHexColor(details.color, format = HexColorFormat.RGB)) + + am.setUserData(this, ACCOUNT_USER_DATA_USER, JsonSerializer.serialize(details.user)) + am.setUserData(this, ACCOUNT_USER_DATA_EXTRAS, details.extras?.let { JsonSerializer.serialize(it) }) + am.setAuthToken(this, ACCOUNT_AUTH_TOKEN_TYPE, JsonSerializer.serialize(details.credentials)) +} 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 c1f594023..a6f329ebe 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 @@ -19,7 +19,6 @@ package org.mariotaku.twidere.util.stetho -import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.util.Base64 @@ -37,10 +36,8 @@ import com.jayway.jsonpath.spi.mapper.MappingProvider import org.apache.commons.cli.* import org.json.JSONArray import org.json.JSONObject -import org.mariotaku.ktextension.HexColorFormat import org.mariotaku.ktextension.subArray -import org.mariotaku.ktextension.toHexColor -import org.mariotaku.twidere.TwidereConstants.* +import org.mariotaku.twidere.extensions.model.updateDetails import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.util.AccountUtils @@ -281,19 +278,6 @@ class AccountsDumperPlugin(val context: Context) : DumperPlugin { return SecretKeySpec(factory.generateSecret(spec).encoded, "AES") } - private fun Account.updateDetails(am: AccountManager, details: AccountDetails) { - am.setUserData(this, ACCOUNT_USER_DATA_KEY, details.key.toString()) - am.setUserData(this, ACCOUNT_USER_DATA_TYPE, details.type) - am.setUserData(this, ACCOUNT_USER_DATA_CREDS_TYPE, details.credentials_type) - - am.setUserData(this, ACCOUNT_USER_DATA_ACTIVATED, true.toString()) - am.setUserData(this, ACCOUNT_USER_DATA_COLOR, toHexColor(details.color, format = HexColorFormat.RGB)) - - am.setUserData(this, ACCOUNT_USER_DATA_USER, JsonSerializer.serialize(details.user)) - am.setUserData(this, ACCOUNT_USER_DATA_EXTRAS, details.extras?.let { JsonSerializer.serialize(it) }) - am.setAuthToken(this, ACCOUNT_AUTH_TOKEN_TYPE, JsonSerializer.serialize(details.credentials)) - } - private fun AccountManager.docContext(forKey: String): DocumentContext { val accountKey = UserKey.valueOf(forKey) val details = AccountUtils.getAccountDetails(this, accountKey, true) ?: throw Utils.NoAccountException() diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/tab/TabConfiguration.java b/twidere/src/main/java/org/mariotaku/twidere/model/tab/TabConfiguration.java index 3706d4118..189355634 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/model/tab/TabConfiguration.java +++ b/twidere/src/main/java/org/mariotaku/twidere/model/tab/TabConfiguration.java @@ -50,6 +50,9 @@ public abstract class TabConfiguration { @AccountFlags public abstract int getAccountFlags(); + @NonNull + public abstract Class getFragmentClass(); + public boolean isSingleTab() { return false; } @@ -63,9 +66,6 @@ public abstract class TabConfiguration { return null; } - @NonNull - public abstract Class getFragmentClass(); - public boolean applyExtraConfigurationTo(@NonNull Tab tab, @NonNull ExtraConfiguration extraConf) { return true; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java b/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java index 9b764f232..e72685028 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java @@ -23,6 +23,7 @@ import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_CREDS_TYP import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_EXTRAS; import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_KEY; import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_POSITION; +import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_TEST; import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_TYPE; import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_USER; @@ -41,6 +42,7 @@ public class AccountUtils { ACCOUNT_USER_DATA_EXTRAS, ACCOUNT_USER_DATA_COLOR, ACCOUNT_USER_DATA_POSITION, + ACCOUNT_USER_DATA_TEST, }; @Nullable diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt index f9bde0027..807431394 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt @@ -329,7 +329,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener } override fun onDestroy() { - if (!shouldSkipDraft && hasComposingStatus() && isFinishing) { + if (!shouldSkipDraft && intent.getBooleanExtra(EXTRA_SAVE_DRAFT, true) + && hasComposingStatus() && isFinishing) { saveToDrafts() Toast.makeText(this, R.string.message_toast_status_saved_to_draft, Toast.LENGTH_SHORT).show() } else { @@ -737,11 +738,12 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener if (intent.action == INTENT_ACTION_EDIT_DRAFT) return true if (hasMedia) return true val text = editText.text?.toString().orEmpty() + if (text == originalText) return false val replyTextAndMentions = getTwitterReplyTextAndMentions(text) if (replyTextAndMentions != null) { return replyTextAndMentions.replyText.isNotEmpty() } - return text.isNotEmpty() && text != originalText + return text.isNotEmpty() } private fun confirmAndUpdateStatus() { @@ -1051,8 +1053,13 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener editText.append("@${status.user_screen_name} ") } - val selectionEnd = editText.length() - editText.setSelection(selectionStart, selectionEnd) + val text = intent.getStringExtra(EXTRA_TEXT) + if (text != null) { + editText.append(text) + } else { + val selectionEnd = editText.length() + editText.setSelection(selectionStart, selectionEnd) + } accountsAdapter.selectedAccountKeys = arrayOf(status.account_key) showReplyLabelAndHint(status) return true @@ -1060,19 +1067,24 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener private fun handleEditDraftIntent(draft: Draft?): Boolean { if (draft == null) return false + val extras = draft.action_extras as? UpdateStatusActionExtras + val media = draft.media draftUniqueId = draft.unique_id_non_null - editText.setText(draft.text) - val selectionEnd = editText.length() - editText.setSelection(selectionEnd) - accountsAdapter.selectedAccountKeys = draft.account_keys ?: emptyArray() - if (draft.media != null) { - addMedia(Arrays.asList(*draft.media)) - } recentLocation = draft.location - (draft.action_extras as? UpdateStatusActionExtras)?.let { - possiblySensitive = it.isPossiblySensitive - inReplyToStatus = it.inReplyToStatus + accountsAdapter.selectedAccountKeys = draft.account_keys ?: emptyArray() + + editText.setText(extras?.editingText ?: draft.text) + editText.setSelection(editText.length()) + + if (media != null) { + addMedia(Arrays.asList(*media)) } + + if (extras != null) { + possiblySensitive = extras.isPossiblySensitive + inReplyToStatus = extras.inReplyToStatus + } + val tag = Uri.withAppendedPath(Drafts.CONTENT_URI, draft._id.toString()).toString() notificationManager.cancel(tag, NOTIFICATION_ID_DRAFTS) return true @@ -1457,7 +1469,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true) val maxLength = statusTextCount.maxLength val inReplyTo = inReplyToStatus - val replyTextAndMentions = getTwitterReplyTextAndMentions(text) + val replyTextAndMentions = getTwitterReplyTextAndMentions(text, accounts) if (inReplyTo != null && replyTextAndMentions != null) { val (replyStartIndex, replyText, _, excludedMentions, replyToOriginalUser) = replyTextAndMentions @@ -1471,8 +1483,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener val replyToSelf = accounts.singleOrNull()?.key == inReplyTo.user_key // Fix status to at least make mentioned user know what status it is if (!replyToOriginalUser && !replyToSelf) { - update.attachment_url = LinkCreator.getTwitterStatusLink(inReplyTo.user_screen_name, - inReplyTo.id).toString() + update.attachment_url = LinkCreator.getStatusWebLink(inReplyTo).toString() } } else { if (text.isEmpty() && media.isEmpty()) throw NoContentException() @@ -1494,7 +1505,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener update.media = media update.in_reply_to_status = inReplyTo update.is_possibly_sensitive = possiblySensitive - update.draft_extras = update.updateStatusActionExtras() + update.draft_extras = update.updateStatusActionExtras().also { + it.editingText = text + } return update } @@ -1541,11 +1554,12 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener } } - private fun getTwitterReplyTextAndMentions(text: String = editText.text?.toString().orEmpty()): - ReplyTextAndMentions? { + private fun getTwitterReplyTextAndMentions(text: String = editText.text?.toString().orEmpty(), + accounts: Array = accountsAdapter.selectedAccounts): ReplyTextAndMentions? { val inReplyTo = inReplyToStatus ?: return null if (!ignoreMentions) return null - return extractor.extractReplyTextAndMentions(text, inReplyTo) + val account = accounts.singleOrNull() ?: return null + return extractor.extractReplyTextAndMentions(text, inReplyTo, account.key) } private fun saveToDrafts(): Uri? { @@ -2031,6 +2045,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener it.excludedReplyUserIds = excluded_reply_user_ids it.isExtendedReplyMode = extended_reply_mode } + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt index ba849340c..05f6ef4eb 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt @@ -34,8 +34,6 @@ import nl.komponents.kovenant.android.startKovenant import nl.komponents.kovenant.android.stopKovenant import nl.komponents.kovenant.task import okhttp3.Dns -import org.apache.commons.lang3.concurrent.ConcurrentUtils -import org.mariotaku.commons.logansquare.LoganSquareMapperFinder import org.mariotaku.kpreferences.KPreferences import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.set @@ -64,8 +62,6 @@ import org.mariotaku.twidere.util.refresh.AutoRefreshController import org.mariotaku.twidere.util.sync.DataSyncProvider import org.mariotaku.twidere.util.sync.SyncController import java.util.* -import java.util.concurrent.Callable -import java.util.concurrent.Future import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -124,11 +120,6 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis StrictModeUtils.detectAllVmPolicy() } super.onCreate() - LoganSquareMapperFinder.setDefaultExecutor(object : LoganSquareMapperFinder.FutureExecutor { - override fun submit(callable: Callable): Future { - return ConcurrentUtils.constantFuture(callable.call()) - } - }) applyLanguageSettings() startKovenant() initializeAsyncTask() diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ExtractorExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ExtractorExtensions.kt index 35bbde553..5892084ed 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ExtractorExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ExtractorExtensions.kt @@ -23,6 +23,7 @@ import com.twitter.Extractor import org.mariotaku.twidere.extension.model.replyMentions import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.ParcelableUserMention +import org.mariotaku.twidere.model.UserKey fun Extractor.extractMentionsAndNonMentionStartIndex(text: String, mentions: Array?): MentionsAndNonMentionStartIndex { var nextExpectedPos = 0 @@ -39,7 +40,8 @@ fun Extractor.extractMentionsAndNonMentionStartIndex(text: String, mentions: Arr return MentionsAndNonMentionStartIndex(entities, nextExpectedPos) } -fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableStatus): ReplyTextAndMentions { +fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableStatus, + accountKey: UserKey = inReplyTo.account_key): ReplyTextAndMentions { // First extract mentions and 'real text' start index val (textMentions, index) = extractMentionsAndNonMentionStartIndex(text, inReplyTo.replyMentions) @@ -72,7 +74,7 @@ fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableSta } } // Find reply text contains mention to `inReplyTo.user` - val mentioningUser = textMentions.any { + val mentioningUser = accountKey == inReplyTo.user_key || textMentions.any { it.value.equals(inReplyTo.user_screen_name, ignoreCase = true) } if (!mentioningUser) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ValidatorExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ValidatorExtensions.kt index 72ff3c991..90aa1638b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ValidatorExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/text/twitter/ValidatorExtensions.kt @@ -22,18 +22,21 @@ package org.mariotaku.twidere.extension.text.twitter import com.twitter.Extractor import com.twitter.Validator import org.mariotaku.twidere.model.ParcelableStatus +import org.mariotaku.twidere.model.UserKey /** * Created by mariotaku on 2017/3/31. */ -fun Validator.getTweetLength(text: String, ignoreMentions: Boolean, inReplyTo: ParcelableStatus?): Int { - if (!ignoreMentions || inReplyTo == null) { +fun Validator.getTweetLength(text: String, ignoreMentions: Boolean, inReplyTo: ParcelableStatus?, + accountKey: UserKey? = inReplyTo?.account_key): Int { + if (!ignoreMentions || inReplyTo == null || accountKey == null) { return getTweetLength(text) } - val (_, replyText, _, _, _) = InternalExtractor.extractReplyTextAndMentions(text, inReplyTo) + val (_, replyText, _, _, _) = InternalExtractor.extractReplyTextAndMentions(text, inReplyTo, + accountKey) return getTweetLength(replyText) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt index 54d7d7d02..cfc68ee6b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt @@ -424,6 +424,9 @@ abstract class AbsActivitiesFragment protected constructor() : @ReadPositionTag get() = null + protected open val timelineSyncTag: String? + get() = null + protected abstract fun hasMoreData(data: List?): Boolean protected abstract fun onCreateActivitiesLoader(context: Context, args: Bundle, @@ -442,7 +445,9 @@ abstract class AbsActivitiesFragment protected constructor() : if (readStateManager.setPosition(tag, item.timestamp)) { positionUpdated = true } - timelineSyncManager?.setPosition(positionTag, tag, item.position_key) + } + timelineSyncTag?.let { syncTag -> + timelineSyncManager?.setPosition(positionTag, syncTag, item.position_key) } currentReadPositionTag?.let { currentTag -> readStateManager.setPosition(currentTag, item.timestamp, true) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt index e552af779..00b55e682 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt @@ -102,6 +102,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment + timelineSyncManager?.setPosition(positionTag, syncTag, status.position_key) + } currentReadPositionTag?.let { readStateManager.setPosition(it, readPosition, true) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/HomeTimelineFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/HomeTimelineFragment.kt index ba457d8fb..810f5acb0 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/HomeTimelineFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/HomeTimelineFragment.kt @@ -19,13 +19,13 @@ package org.mariotaku.twidere.fragment -import android.net.Uri import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.TwidereConstants.NOTIFICATION_ID_HOME_TIMELINE import org.mariotaku.twidere.annotation.ReadPositionTag import org.mariotaku.twidere.constant.IntentConstants.EXTRA_EXTRAS import org.mariotaku.twidere.model.ParameterizedExpression import org.mariotaku.twidere.model.RefreshTaskParam +import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.tab.extra.HomeTabExtras import org.mariotaku.twidere.provider.TwidereDataStore.Statuses import org.mariotaku.twidere.util.DataStoreUtils @@ -37,17 +37,18 @@ import java.util.* */ class HomeTimelineFragment : CursorStatusesFragment() { - override val errorInfoKey: String - get() = ErrorInfoStore.KEY_HOME_TIMELINE + override val errorInfoKey = ErrorInfoStore.KEY_HOME_TIMELINE - override val contentUri: Uri - get() = Statuses.CONTENT_URI + override val contentUri = Statuses.CONTENT_URI - override val notificationType: Int - get() = NOTIFICATION_ID_HOME_TIMELINE + override val notificationType = NOTIFICATION_ID_HOME_TIMELINE - override val isFilterEnabled: Boolean - get() = true + override val isFilterEnabled = true + + override val readPositionTag = ReadPositionTag.HOME_TIMELINE + + override val timelineSyncTag: String? + get() = getTimelineSyncTag(accountKeys) override fun updateRefreshState() { val twitter = twitterWrapper @@ -86,6 +87,11 @@ class HomeTimelineFragment : CursorStatusesFragment() { return super.processWhere(where, whereArgs) } - override val readPositionTag: String = ReadPositionTag.HOME_TIMELINE + companion object { + fun getTimelineSyncTag(accountKeys: Array): String { + return "${ReadPositionTag.HOME_TIMELINE}_${accountKeys.sorted().joinToString(",")}" + } + + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/InteractionsTimelineFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/InteractionsTimelineFragment.kt index 2e89814ca..963fbc147 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/InteractionsTimelineFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/InteractionsTimelineFragment.kt @@ -20,7 +20,6 @@ package org.mariotaku.twidere.fragment import android.content.Context -import android.net.Uri import com.bumptech.glide.Glide import org.mariotaku.microblog.library.twitter.model.Activity import org.mariotaku.sqliteqb.library.Expression @@ -30,67 +29,69 @@ import org.mariotaku.twidere.annotation.ReadPositionTag import org.mariotaku.twidere.constant.IntentConstants.EXTRA_EXTRAS import org.mariotaku.twidere.model.ParameterizedExpression import org.mariotaku.twidere.model.RefreshTaskParam +import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.tab.extra.InteractionsTabExtras import org.mariotaku.twidere.provider.TwidereDataStore.Activities import org.mariotaku.twidere.util.ErrorInfoStore class InteractionsTimelineFragment : CursorActivitiesFragment() { + override val errorInfoKey = ErrorInfoStore.KEY_INTERACTIONS + + override val contentUri = Activities.AboutMe.CONTENT_URI + + override val notificationType = NOTIFICATION_ID_INTERACTIONS_TIMELINE + + override val isFilterEnabled = true + + @ReadPositionTag + override val readPositionTag = ReadPositionTag.ACTIVITIES_ABOUT_ME + + override val timelineSyncTag: String? + get() = getTimelineSyncTag(accountKeys) + + override fun onCreateAdapter(context: Context): ParcelableActivitiesAdapter { + val adapter = ParcelableActivitiesAdapter(context, Glide.with(this)) + val extras: InteractionsTabExtras? = arguments.getParcelable(EXTRA_EXTRAS) + if (extras != null) { + adapter.followingOnly = extras.isMyFollowingOnly + adapter.mentionsOnly = extras.isMentionsOnly + } + return adapter + } + override fun getActivities(param: RefreshTaskParam): Boolean { twitterWrapper.getActivitiesAboutMeAsync(param) return true } - override val errorInfoKey: String - get() = ErrorInfoStore.KEY_INTERACTIONS - - override val contentUri: Uri - get() = Activities.AboutMe.CONTENT_URI - - override val notificationType: Int - get() = NOTIFICATION_ID_INTERACTIONS_TIMELINE - - override val isFilterEnabled: Boolean - get() = true - override fun updateRefreshState() { } override fun processWhere(where: Expression, whereArgs: Array): ParameterizedExpression { val arguments = arguments - if (arguments != null) { - val extras = arguments.getParcelable(EXTRA_EXTRAS) - if (extras != null) { - val expressions = mutableListOf(where) - val combinedArgs = mutableListOf(*whereArgs) - if (extras.isMentionsOnly) { - expressions.add(Expression.inArgs(Activities.ACTION, 3)) - combinedArgs.addAll(arrayOf(Activity.Action.MENTION, Activity.Action.REPLY, Activity.Action.QUOTE)) - } - if (extras.isMyFollowingOnly) { - expressions.add(Expression.equals(Activities.HAS_FOLLOWING_SOURCE, 1)) - } - return ParameterizedExpression(Expression.and(*expressions.toTypedArray()), - combinedArgs.toTypedArray()) + val extras: InteractionsTabExtras? = arguments.getParcelable(EXTRA_EXTRAS) + if (extras != null) { + val expressions = mutableListOf(where) + val combinedArgs = mutableListOf(*whereArgs) + if (extras.isMentionsOnly) { + expressions.add(Expression.inArgs(Activities.ACTION, 3)) + combinedArgs.addAll(arrayOf(Activity.Action.MENTION, Activity.Action.REPLY, Activity.Action.QUOTE)) } + if (extras.isMyFollowingOnly) { + expressions.add(Expression.equals(Activities.HAS_FOLLOWING_SOURCE, 1)) + } + return ParameterizedExpression(Expression.and(*expressions.toTypedArray()), + combinedArgs.toTypedArray()) } return super.processWhere(where, whereArgs) } - override fun onCreateAdapter(context: Context): ParcelableActivitiesAdapter { - val adapter = ParcelableActivitiesAdapter(context, Glide.with(this)) - val arguments = arguments - if (arguments != null) { - val extras = arguments.getParcelable(EXTRA_EXTRAS) - if (extras != null) { - adapter.followingOnly = extras.isMyFollowingOnly - adapter.mentionsOnly = extras.isMentionsOnly - } + companion object { + + fun getTimelineSyncTag(accountKeys: Array): String { + return "${ReadPositionTag.ACTIVITIES_ABOUT_ME}_${accountKeys.sorted().joinToString(",")}" } - return adapter + } - - @ReadPositionTag - override val readPositionTag: String? = ReadPositionTag.ACTIVITIES_ABOUT_ME - } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt index 2792ddf94..9901c496e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt @@ -27,11 +27,12 @@ import org.mariotaku.microblog.library.twitter.model.* import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.annotation.ReadPositionTag import org.mariotaku.twidere.extension.model.isOfficial +import org.mariotaku.twidere.fragment.InteractionsTimelineFragment import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.provider.TwidereDataStore.Activities import org.mariotaku.twidere.util.ErrorInfoStore -import org.mariotaku.twidere.util.Utils +import java.io.IOException /** * Created by mariotaku on 16/2/11. @@ -44,21 +45,9 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) { override val contentUri: Uri get() = Activities.AboutMe.CONTENT_URI - override fun setLocalReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog) { - if (AccountType.TWITTER == details.type && details.isOfficial(context)) { - try { - val response = twitter.getActivitiesAboutMeUnread(true) - val tag = Utils.getReadPositionTagWithAccount(ReadPositionTag.ACTIVITIES_ABOUT_ME, - accountKey) - readStateManager.setPosition(tag, response.cursor, false) - } catch (e: MicroBlogException) { - // Ignore - } - } - } - @Throws(MicroBlogException::class) - override fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging): ResponseList { + override fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging): + ResponseList { if (details.isOfficial(context)) { return twitter.getActivitiesAboutMe(paging) } @@ -75,4 +64,15 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) { statuses.mapTo(activities) { InternalActivityCreator.status(it, details.key.id) } return activities } + + + override fun setLocalReadPosition(accountKeys: Array, saveReadPosition: BooleanArray) { + val manager = timelineSyncManagerFactory.get() ?: return + val tag = InteractionsTimelineFragment.getTimelineSyncTag(accountKeys) + try { + manager.blockingGetPosition(ReadPositionTag.ACTIVITIES_ABOUT_ME, tag) + } catch (e: IOException) { + return + } + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt index 74c6c1873..7883e21e8 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt @@ -53,11 +53,11 @@ abstract class GetActivitiesTask( val cr = context.contentResolver val result = ArrayList>() val loadItemLimit = preferences[loadItemLimitKey] - var saveReadPosition = false - for (i in accountKeys.indices) { - val accountKey = accountKeys[i] + val saveReadPosition = BooleanArray(accountKeys.size) + accountKeys.forEachIndexed { i, accountKey -> val noItemsBefore = DataStoreUtils.getActivitiesCount(context, contentUri, accountKey) <= 0 - val credentials = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: continue + val credentials = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, + true) ?: return@forEachIndexed val microBlog = credentials.newMicroBlogInstance(context = context, cls = MicroBlog::class.java) val paging = Paging() paging.count(loadItemLimit) @@ -79,7 +79,7 @@ abstract class GetActivitiesTask( paging.sinceId(sinceId) if (maxIds == null || maxId == null) { paging.setLatestResults(true) - saveReadPosition = true + saveReadPosition[i] = true } } } @@ -88,8 +88,8 @@ abstract class GetActivitiesTask( val activities = getActivities(microBlog, credentials, paging) val storeResult = storeActivities(cr, loadItemLimit, credentials, noItemsBefore, activities, sinceId, maxId, false) - if (saveReadPosition) { - setLocalReadPosition(accountKey, credentials, microBlog) + if (saveReadPosition[i]) { + } errorInfoStore.remove(errorInfoKey, accountKey) if (storeResult != 0) { @@ -106,6 +106,7 @@ abstract class GetActivitiesTask( result.add(TwitterListResponse(accountKey, e)) } } + setLocalReadPosition(accountKeys, saveReadPosition) return result } @@ -124,7 +125,7 @@ abstract class GetActivitiesTask( @Throws(MicroBlogException::class) protected abstract fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging): ResponseList - protected abstract fun setLocalReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog) + protected abstract fun setLocalReadPosition(accountKeys: Array, saveReadPosition: BooleanArray) private fun storeActivities(cr: ContentResolver, loadItemLimit: Int, details: AccountDetails, noItemsBefore: Boolean, activities: ResponseList, diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetHomeTimelineTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetHomeTimelineTask.kt index 1e36107f3..2de75b6d7 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetHomeTimelineTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetHomeTimelineTask.kt @@ -54,8 +54,7 @@ class GetHomeTimelineTask(context: Context) : GetStatusesTask(context) { val syncManager = timelineSyncManagerFactory.get() ?: return try { val tag = Utils.getReadPositionTagWithAccount(ReadPositionTag.HOME_TIMELINE, accountKey) - val positionKey = syncManager.blockingGetPosition(ReadPositionTag.HOME_TIMELINE, tag) - readStateManager + syncManager.blockingGetPosition(ReadPositionTag.HOME_TIMELINE, tag) }catch (e: IOException) { } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt index b6b88821a..c5bfeca55 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt @@ -227,7 +227,7 @@ class UpdateStatusTask( val textLimit = account.textLimit val ignoreMentions = account.type == AccountType.TWITTER if (textLimit >= 0 && validator.getTweetLength(text, ignoreMentions, - update.in_reply_to_status) <= textLimit) { + update.in_reply_to_status, account.key) <= textLimit) { continue } shortener.waitForService() @@ -399,7 +399,9 @@ class UpdateStatusTask( val details = statusUpdate.accounts[index] if (details.type == AccountType.TWITTER && statusUpdate.extended_reply_mode) { status.autoPopulateReplyMetadata(true) - status.excludeReplyUserIds(statusUpdate.excluded_reply_user_ids) + if (statusUpdate.excluded_reply_user_ids.isNotNullOrEmpty()) { + status.excludeReplyUserIds(statusUpdate.excluded_reply_user_ids) + } } } if (statusUpdate.repost_status_id != null) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/api/TwitterConverterFactory.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/api/TwitterConverterFactory.kt index 373ed305b..2b60e47dc 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/api/TwitterConverterFactory.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/api/TwitterConverterFactory.kt @@ -20,6 +20,9 @@ package org.mariotaku.twidere.util.api import android.support.v4.util.SimpleArrayMap +import com.bluelinelabs.logansquare.JsonMapper +import com.bluelinelabs.logansquare.ParameterizedType +import org.mariotaku.commons.logansquare.LoganSquareMapperFinder import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.twitter.model.ResponseCode @@ -49,6 +52,14 @@ object TwitterConverterFactory : LoganSquareConverterFactory responseConverters.put(OAuthToken::class.java, OAuthTokenResponseConverter()) } + override fun mapperFor(type: ParameterizedType): JsonMapper { + return LoganSquareMapperFinder.mapperFor(type) + } + + override fun mapperFor(type: Class): JsonMapper { + return LoganSquareMapperFinder.mapperFor(type) + } + @Throws(RestConverter.ConvertException::class) override fun forResponse(type: Type): RestConverter { val converter = responseConverters.get(type) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/sync/TimelineSyncManager.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/sync/TimelineSyncManager.kt index 2be1aadc2..e5adef114 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/sync/TimelineSyncManager.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/sync/TimelineSyncManager.kt @@ -33,17 +33,18 @@ import java.util.* abstract class TimelineSyncManager(val context: Context) { - private val internalMap = ArrayMap() + private val stagedCommits = ArrayMap() + private val cachedPositions = ArrayMap() fun setPosition(@ReadPositionTag positionTag: String, currentTag: String?, positionKey: Long) { - internalMap[TimelineKey(positionTag, currentTag)] = positionKey + stagedCommits[TimelineKey(positionTag, currentTag)] = positionKey } fun commit() { - val data = internalMap.map { (key, value) -> + val data = stagedCommits.map { (key, value) -> PositionData(key.positionTag, key.currentTag, value) }.toTypedArray() - internalMap.clear() + stagedCommits.clear() performSync(data) } @@ -79,5 +80,4 @@ abstract class TimelineSyncManager(val context: Context) { fun newFactory(): Factory = ServiceLoader.load(Factory::class.java).firstOrNull() ?: DummyFactory } - } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/DraftViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/DraftViewHolder.kt index 3f8223b36..cbcc8a6c0 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/DraftViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/DraftViewHolder.kt @@ -30,6 +30,7 @@ import org.mariotaku.twidere.extension.model.getActionName import org.mariotaku.twidere.model.Draft import org.mariotaku.twidere.model.ParcelableMedia import org.mariotaku.twidere.model.draft.StatusObjectActionExtras +import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.Utils @@ -50,6 +51,10 @@ class DraftViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { Draft.Action.UPDATE_STATUS, Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2, Draft.Action.REPLY, Draft.Action.QUOTE -> { val media = draft.media?.mapToArray(::ParcelableMedia) + val extras = draft.action_extras as? UpdateStatusActionExtras + if (extras != null) { + summaryText = extras.editingText + } mediaPreviewContainer.visibility = View.VISIBLE mediaPreviewContainer.displayMedia(requestManager = requestManager, media = media)