This commit is contained in:
Mariotaku Lee 2017-04-16 16:47:02 +08:00
parent d5d0e9bfc6
commit 7d07c4f7a3
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
32 changed files with 710 additions and 141 deletions

View File

@ -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',

View File

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

View File

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

View File

@ -87,6 +87,9 @@ public class AccountDetails implements Parcelable, Comparable<AccountDetails> {
@JsonField(name = "dummy")
public boolean dummy;
@JsonField(name = "test")
public boolean test;
@JsonField(name = "credentials", typeConverter = JsonStringConverter.class)
@ParcelableNoThanks
String credentials_json;

View File

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

View File

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

View File

@ -0,0 +1,188 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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<String>, statusUpdate: ParcelableStatusUpdate): Boolean {
return statusUpdate.excluded_reply_user_ids?.all { excludedId ->
expectedIds.any { expectation ->
expectation.equals(excludedId, ignoreCase = true)
}
} ?: expectedIds.isEmpty()
}
}

View File

@ -0,0 +1,38 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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>(ComposeActivity::class.java, initialTouchMode, launchActivity) {
override fun beforeActivityLaunched() {
TestAccountUtils.insertTestAccounts()
}
override fun afterActivityFinished() {
TestAccountUtils.removeTestAccounts()
}
}

View File

@ -0,0 +1,34 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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())
}

View File

@ -0,0 +1,60 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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) }
}
}

View File

@ -0,0 +1,30 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util
import android.content.res.Resources
import android.support.annotation.RawRes
/**
* Created by mariotaku on 2017/4/16.
*/
inline fun <reified T> Resources.getJsonResource(@RawRes id: Int) = openRawResource(id).use {
JsonSerializer.parse(it, T::class.java)
}

View File

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

View File

@ -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": "<a href=\"https://github.com/mariotaku/twidere/\" rel=\"nofollow\">Twidere for Android #7</a>",
"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"
}

View File

@ -0,0 +1,45 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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))
}

View File

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

View File

@ -50,6 +50,9 @@ public abstract class TabConfiguration {
@AccountFlags
public abstract int getAccountFlags();
@NonNull
public abstract Class<? extends Fragment> getFragmentClass();
public boolean isSingleTab() {
return false;
}
@ -63,9 +66,6 @@ public abstract class TabConfiguration {
return null;
}
@NonNull
public abstract Class<? extends Fragment> getFragmentClass();
public boolean applyExtraConfigurationTo(@NonNull Tab tab, @NonNull ExtraConfiguration extraConf) {
return true;
}

View File

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

View File

@ -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<AccountDetails> = 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
}
}
}

View File

@ -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 <T> submit(callable: Callable<T>): Future<T> {
return ConcurrentUtils.constantFuture(callable.call())
}
})
applyLanguageSettings()
startKovenant()
initializeAsyncTask()

View File

@ -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<ParcelableUserMention>?): 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) {

View File

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

View File

@ -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<ParcelableActivity>?): 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)

View File

@ -102,6 +102,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
protected open val readPositionTag: String?
get() = null
protected open val timelineSyncTag: String?
get() = null
protected open val readPositionTagWithArguments: String?
get() = readPositionTag
@ -477,9 +480,11 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
val tag = Utils.getReadPositionTagWithAccount(it, accountKey)
readStateManager.setPosition(tag, readPosition)
timelineSyncManager?.setPosition(positionTag, tag, status.position_key)
}
}
timelineSyncTag?.let { syncTag ->
timelineSyncManager?.setPosition(positionTag, syncTag, status.position_key)
}
currentReadPositionTag?.let {
readStateManager.setPosition(it, readPosition, true)
}

View File

@ -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<UserKey>): String {
return "${ReadPositionTag.HOME_TIMELINE}_${accountKeys.sorted().joinToString(",")}"
}
}
}

View File

@ -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<String>): ParameterizedExpression {
val arguments = arguments
if (arguments != null) {
val extras = arguments.getParcelable<InteractionsTabExtras>(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<InteractionsTabExtras>(EXTRA_EXTRAS)
if (extras != null) {
adapter.followingOnly = extras.isMyFollowingOnly
adapter.mentionsOnly = extras.isMentionsOnly
}
companion object {
fun getTimelineSyncTag(accountKeys: Array<UserKey>): String {
return "${ReadPositionTag.ACTIVITIES_ABOUT_ME}_${accountKeys.sorted().joinToString(",")}"
}
return adapter
}
@ReadPositionTag
override val readPositionTag: String? = ReadPositionTag.ACTIVITIES_ABOUT_ME
}

View File

@ -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<Activity> {
override fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging):
ResponseList<Activity> {
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<UserKey>, saveReadPosition: BooleanArray) {
val manager = timelineSyncManagerFactory.get() ?: return
val tag = InteractionsTimelineFragment.getTimelineSyncTag(accountKeys)
try {
manager.blockingGetPosition(ReadPositionTag.ACTIVITIES_ABOUT_ME, tag)
} catch (e: IOException) {
return
}
}
}

View File

@ -53,11 +53,11 @@ abstract class GetActivitiesTask(
val cr = context.contentResolver
val result = ArrayList<TwitterListResponse<Activity>>()
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<Activity>
protected abstract fun setLocalReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog)
protected abstract fun setLocalReadPosition(accountKeys: Array<UserKey>, saveReadPosition: BooleanArray)
private fun storeActivities(cr: ContentResolver, loadItemLimit: Int, details: AccountDetails,
noItemsBefore: Boolean, activities: ResponseList<Activity>,

View File

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

View File

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

View File

@ -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<MicroBlogException>
responseConverters.put(OAuthToken::class.java, OAuthTokenResponseConverter())
}
override fun <T : Any?> mapperFor(type: ParameterizedType<T>): JsonMapper<T> {
return LoganSquareMapperFinder.mapperFor(type)
}
override fun <T : Any?> mapperFor(type: Class<T>): JsonMapper<T> {
return LoganSquareMapperFinder.mapperFor(type)
}
@Throws(RestConverter.ConvertException::class)
override fun forResponse(type: Type): RestConverter<HttpResponse, *, MicroBlogException> {
val converter = responseConverters.get(type)

View File

@ -33,17 +33,18 @@ import java.util.*
abstract class TimelineSyncManager(val context: Context) {
private val internalMap = ArrayMap<TimelineKey, Long>()
private val stagedCommits = ArrayMap<TimelineKey, Long>()
private val cachedPositions = ArrayMap<TimelineKey, Long>()
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
}
}

View File

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