mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-16 19:50:53 +01:00
refactoring
improved compose reply
This commit is contained in:
parent
56c65af196
commit
5f58d0edce
@ -294,6 +294,7 @@ public interface SharedPreferenceConstants {
|
||||
|
||||
String KEY_RANDOMIZE_ACCOUNT_NAME = "randomize_account_name";
|
||||
String KEY_DEFAULT_AUTO_REFRESH = "default_auto_refresh";
|
||||
String KEY_SYNC_PROVIDER_TYPE = "sync_provider_type";
|
||||
|
||||
String KEY_MEDIA_LINK_COUNTS_IN_STATUS = "media_link_counts_in_status";
|
||||
String KEY_DROPBOX_ACCESS_TOKEN = "dropbox_access_token";
|
||||
|
16
twidere/proguard-rules.pro
vendored
16
twidere/proguard-rules.pro
vendored
@ -65,13 +65,21 @@
|
||||
<init>(android.content.Context);
|
||||
}
|
||||
|
||||
# Essential components
|
||||
-keep class * extends org.mariotaku.twidere.util.Analyzer
|
||||
-keep class * extends org.mariotaku.twidere.util.MapFragmentFactory
|
||||
-keep class * extends org.mariotaku.twidere.util.gifshare.GifShareProvider.Factory
|
||||
-keep class * extends org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
-keep class * extends org.mariotaku.twidere.util.schedule.StatusScheduleProvider.Factory
|
||||
-keep class * extends org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
|
||||
-keep class * extends org.mariotaku.twidere.util.twitter.card.TwitterCardViewFactory
|
||||
|
||||
# Extra feature service
|
||||
-keep class * extends org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
|
||||
# Extra feature component factories
|
||||
-keep class * extends org.mariotaku.twidere.util.gifshare.GifShareProvider.Factory
|
||||
-keep class * extends org.mariotaku.twidere.util.schedule.StatusScheduleProvider.Factory
|
||||
-keep class * extends org.mariotaku.twidere.util.sync.DataSyncProvider.Factory
|
||||
-keep class * extends org.mariotaku.twidere.util.sync.TimelineSyncManager.Factory
|
||||
|
||||
# View components
|
||||
-keep class * extends org.mariotaku.twidere.util.view.AppBarChildBehavior.ChildTransformation
|
||||
|
||||
-keepclassmembers class * {
|
||||
|
@ -9,9 +9,11 @@ import android.support.annotation.StringDef;
|
||||
ReadPositionTag.ACTIVITIES_ABOUT_ME,
|
||||
ReadPositionTag.HOME_TIMELINE,
|
||||
ReadPositionTag.DIRECT_MESSAGES,
|
||||
ReadPositionTag.CUSTOM_TIMELINE,
|
||||
})
|
||||
public @interface ReadPositionTag {
|
||||
String HOME_TIMELINE = "home_timeline";
|
||||
String ACTIVITIES_ABOUT_ME = "activities_about_me";
|
||||
String DIRECT_MESSAGES = "direct_messages";
|
||||
String CUSTOM_TIMELINE = "custom_timeline";
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 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.content;
|
||||
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
|
||||
import org.mariotaku.twidere.TwidereConstants;
|
||||
|
||||
public final class SupportActivityReloadCursorObserver extends ContentObserver implements TwidereConstants {
|
||||
|
||||
private final FragmentActivity mActivity;
|
||||
private final int mLoaderId;
|
||||
private final LoaderCallbacks<Cursor> mCallback;
|
||||
|
||||
public SupportActivityReloadCursorObserver(final FragmentActivity activity, final int loaderId,
|
||||
final LoaderCallbacks<Cursor> callback) {
|
||||
super(createHandler());
|
||||
mActivity = activity;
|
||||
mLoaderId = loaderId;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(final boolean selfChange) {
|
||||
onChange(selfChange, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(final boolean selfChange, final Uri uri) {
|
||||
if (mActivity == null || mActivity.isFinishing()) return;
|
||||
// Handle change.
|
||||
mActivity.getSupportLoaderManager().restartLoader(mLoaderId, null, mCallback);
|
||||
}
|
||||
|
||||
private static Handler createHandler() {
|
||||
if (Thread.currentThread().getId() != 1) return null;
|
||||
return new Handler();
|
||||
}
|
||||
}
|
@ -71,6 +71,8 @@ import org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
import org.mariotaku.twidere.util.schedule.StatusScheduleProvider
|
||||
import org.mariotaku.twidere.util.support.ActivitySupport
|
||||
import org.mariotaku.twidere.util.support.ActivitySupport.TaskDescriptionCompat
|
||||
import org.mariotaku.twidere.util.sync.DataSyncProvider.Factory
|
||||
import org.mariotaku.twidere.util.sync.TimelineSyncManager
|
||||
import org.mariotaku.twidere.util.theme.TwidereAppearanceCreator
|
||||
import org.mariotaku.twidere.util.theme.getCurrentThemeResource
|
||||
import org.mariotaku.twidere.view.iface.IExtendedView.OnFitSystemWindowsListener
|
||||
@ -106,6 +108,8 @@ open class BaseActivity : ChameleonActivity(), IBaseActivity<BaseActivity>, IThe
|
||||
@Inject
|
||||
lateinit var statusScheduleProviderFactory: StatusScheduleProvider.Factory
|
||||
@Inject
|
||||
lateinit var timelineSyncManagerFactory: TimelineSyncManager.Factory
|
||||
@Inject
|
||||
lateinit var gifShareProviderFactory: GifShareProvider.Factory
|
||||
@Inject
|
||||
lateinit var defaultFeatures: DefaultFeatures
|
||||
@ -115,6 +119,9 @@ open class BaseActivity : ChameleonActivity(), IBaseActivity<BaseActivity>, IThe
|
||||
protected val statusScheduleProvider: StatusScheduleProvider?
|
||||
get() = statusScheduleProviderFactory.newInstance(this)
|
||||
|
||||
protected val timelineSyncManager: TimelineSyncManager?
|
||||
get() = timelineSyncManagerFactory.get()
|
||||
|
||||
protected val gifShareProvider: GifShareProvider?
|
||||
get() = gifShareProviderFactory.newInstance(this)
|
||||
|
||||
|
@ -77,7 +77,6 @@ import org.mariotaku.twidere.extension.loadProfileImage
|
||||
import org.mariotaku.twidere.extension.model.textLimit
|
||||
import org.mariotaku.twidere.extension.model.unique_id_non_null
|
||||
import org.mariotaku.twidere.extension.text.twitter.extractReplyTextAndMentions
|
||||
import org.mariotaku.twidere.extension.text.twitter.getTweetLength
|
||||
import org.mariotaku.twidere.extension.withAppendedPath
|
||||
import org.mariotaku.twidere.fragment.*
|
||||
import org.mariotaku.twidere.fragment.PermissionRequestDialog.PermissionRequestCancelCallback
|
||||
@ -151,6 +150,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
private var draftUniqueId: String? = null
|
||||
private var shouldSkipDraft: Boolean = false
|
||||
private var ignoreMentions: Boolean = false
|
||||
private var replyToSelf: Boolean = false
|
||||
private var scheduleInfo: ScheduleInfo? = null
|
||||
set(value) {
|
||||
field = value
|
||||
@ -203,6 +203,23 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
accountSelectorButton.setOnClickListener(this)
|
||||
replyLabel.setOnClickListener(this)
|
||||
|
||||
hintLabel.text = HtmlSpanBuilder.fromHtml(getString(R.string.hint_status_reply_to_user_removed)).apply {
|
||||
val dialogSpan = getSpans(0, length, URLSpan::class.java).firstOrNull {
|
||||
"#dialog" == it.url
|
||||
}
|
||||
if (dialogSpan != null) {
|
||||
val spanStart = getSpanStart(dialogSpan)
|
||||
val spanEnd = getSpanEnd(dialogSpan)
|
||||
removeSpan(dialogSpan)
|
||||
setSpan(object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
MessageDialogFragment.show(supportFragmentManager,
|
||||
message = getString(R.string.message_status_reply_to_user_removed_explanation),
|
||||
tag = "status_reply_to_user_removed_explanation")
|
||||
}
|
||||
}, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
hintLabel.movementMethod = LinkMovementMethod.getInstance()
|
||||
hintLabel.linksClickable = true
|
||||
|
||||
@ -1097,14 +1114,14 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
val details = AccountUtils.getAccountDetails(am, status.account_key, false) ?: return false
|
||||
val accountUser = details.user
|
||||
val mentions = ArrayList<String>()
|
||||
if (details.type == AccountType.TWITTER) {
|
||||
// For Twitter, status user should always be the first
|
||||
var selectionStart: Int = 0
|
||||
if (accountUser.key != status.user_key) {
|
||||
editText.append("@${status.user_screen_name} ")
|
||||
} else if (accountUser.key != status.user_key) {
|
||||
// If replying status from current user, just exclude it's screen name from selection.
|
||||
selectionStart = editText.length()
|
||||
} else if (details.type == AccountType.TWITTER) {
|
||||
// For Twitter, mention to self will be selected
|
||||
editText.append("@${status.user_screen_name} ")
|
||||
}
|
||||
var selectionStart = editText.length()
|
||||
if (status.is_retweet && !TextUtils.isEmpty(status.retweeted_by_user_screen_name)) {
|
||||
mentions.add(status.retweeted_by_user_screen_name)
|
||||
}
|
||||
@ -1129,10 +1146,6 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
}
|
||||
|
||||
mentions.distinctBy { it.toLowerCase(Locale.US) }.filterNot {
|
||||
if (details.type == AccountType.TWITTER && it.equals(accountUser.screen_name,
|
||||
ignoreCase = true)) {
|
||||
return@filterNot true
|
||||
}
|
||||
return@filterNot it.equals(status.user_screen_name, ignoreCase = true)
|
||||
}.forEach { editText.append("@$it ") }
|
||||
|
||||
@ -1202,6 +1215,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
editText.accountKey = accounts.firstOrNull()?.key ?: Utils.getDefaultAccountKey(this)
|
||||
statusTextCount.maxLength = accounts.textLimit
|
||||
ignoreMentions = accounts.all { it.type == AccountType.TWITTER }
|
||||
replyToSelf = accounts.singleOrNull()?.let { it.key == inReplyToStatus?.user_key } ?: false
|
||||
setMenu()
|
||||
}
|
||||
|
||||
@ -1455,8 +1469,28 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
val accountKeys = accountsAdapter.selectedAccountKeys
|
||||
val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true)
|
||||
val ignoreMentions = accounts.all { it.type == AccountType.TWITTER }
|
||||
val tweetLength = validator.getTweetLength(text, ignoreMentions, inReplyToStatus)
|
||||
val inReplyTo = inReplyToStatus
|
||||
val maxLength = statusTextCount.maxLength
|
||||
val tweetLength: Int
|
||||
val exceededStartIndex: Int
|
||||
if (inReplyTo != null && ignoreMentions) {
|
||||
val (replyStartIndex, replyText) = extractor.extractReplyTextAndMentions(text,
|
||||
inReplyTo)
|
||||
tweetLength = validator.getTweetLength(replyText)
|
||||
if (tweetLength > maxLength) {
|
||||
exceededStartIndex = replyStartIndex + replyText.offsetByCodePoints(0, maxLength)
|
||||
} else {
|
||||
exceededStartIndex = -1
|
||||
}
|
||||
} else {
|
||||
tweetLength = validator.getTweetLength(text)
|
||||
if (tweetLength > maxLength) {
|
||||
exceededStartIndex = text.offsetByCodePoints(0, maxLength)
|
||||
} else {
|
||||
exceededStartIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
if (accountsAdapter.isSelectionEmpty) {
|
||||
editText.error = getString(R.string.message_toast_no_account_selected)
|
||||
return
|
||||
@ -1465,8 +1499,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
return
|
||||
} else if (maxLength > 0 && !statusShortenerUsed && tweetLength > maxLength) {
|
||||
editText.error = getString(R.string.error_message_status_too_long)
|
||||
val textLength = editText.length()
|
||||
editText.setSelection(textLength - (tweetLength - maxLength), textLength)
|
||||
if (exceededStartIndex >= 0) {
|
||||
editText.setSelection(exceededStartIndex, editText.length())
|
||||
}
|
||||
return
|
||||
}
|
||||
val attachLocation = kPreferences[attachLocationKey]
|
||||
@ -1522,30 +1557,13 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
val mentionColor = ThemeUtils.getColorFromAttribute(this, android.R.attr.textColorSecondary, 0)
|
||||
if (inReplyTo != null && ignoreMentions) {
|
||||
val textAndMentions = extractor.extractReplyTextAndMentions(text, inReplyTo)
|
||||
if (textAndMentions.replyToOriginalUser) {
|
||||
if (textAndMentions.replyToOriginalUser || replyToSelf) {
|
||||
hintLabel.visibility = View.GONE
|
||||
editable.clearSpans(MentionColorSpan::class.java)
|
||||
editable.setSpan(MentionColorSpan(mentionColor), 0, textAndMentions.replyStartIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
} else {
|
||||
hintLabel.visibility = View.VISIBLE
|
||||
hintLabel.text = HtmlSpanBuilder.fromHtml(getString(R.string.hint_status_reply_to_user_removed)).apply {
|
||||
val dialogSpan = getSpans(0, length, URLSpan::class.java).firstOrNull {
|
||||
"#dialog" == it.url
|
||||
}
|
||||
if (dialogSpan != null) {
|
||||
val spanStart = getSpanStart(dialogSpan)
|
||||
val spanEnd = getSpanEnd(dialogSpan)
|
||||
removeSpan(dialogSpan)
|
||||
setSpan(object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
MessageDialogFragment.show(supportFragmentManager,
|
||||
message = getString(R.string.message_status_reply_to_user_removed_explanation),
|
||||
tag = "status_reply_to_user_removed_explanation")
|
||||
}
|
||||
}, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
editable.clearSpans(MentionColorSpan::class.java)
|
||||
}
|
||||
statusTextCount.textCount = validator.getTweetLength(textAndMentions.replyText)
|
||||
|
@ -302,8 +302,8 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
|
||||
readStateManager.unregisterOnSharedPreferenceChangeListener(readStateChangeListener)
|
||||
bus.unregister(this)
|
||||
AccountManager.get(this).removeOnAccountsUpdatedListenerSafe(accountUpdatedListener)
|
||||
preferences.edit().putInt(SharedPreferenceConstants.KEY_SAVED_TAB_POSITION, mainPager.currentItem).apply()
|
||||
|
||||
preferences.edit().putInt(KEY_SAVED_TAB_POSITION, mainPager.currentItem).apply()
|
||||
timelineSyncManager?.commit()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,8 @@ class DummyItemAdapter(
|
||||
override var simpleLayout: Boolean = false
|
||||
override var showFollow: Boolean = true
|
||||
|
||||
private var showCardActions: Boolean = false
|
||||
var showCardActions: Boolean = false
|
||||
|
||||
private var showingActionCardPosition = RecyclerView.NO_POSITION
|
||||
|
||||
init {
|
||||
@ -68,11 +69,6 @@ class DummyItemAdapter(
|
||||
updateOptions()
|
||||
}
|
||||
|
||||
fun setShouldShowAccountsColor(shouldShowAccountsColor: Boolean) {
|
||||
this.showAccountsColor = shouldShowAccountsColor
|
||||
}
|
||||
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return 0
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ import org.mariotaku.twidere.util.media.ThumborWrapper
|
||||
import org.mariotaku.twidere.util.net.TwidereDns
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
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
|
||||
@ -151,6 +152,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
||||
loadDefaultFeatures()
|
||||
|
||||
Analyzer.preferencesChanged(sharedPreferences)
|
||||
DataSyncProvider.Factory.notifyUpdate(this)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
|
@ -18,10 +18,9 @@ import org.mariotaku.twidere.extension.getNonEmptyString
|
||||
import org.mariotaku.twidere.model.CustomAPIConfig
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.account.cred.Credentials
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderInfo
|
||||
import org.mariotaku.twidere.model.timeline.UserTimelineFilter
|
||||
import org.mariotaku.twidere.preference.ThemeBackgroundPreference
|
||||
import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
|
||||
import org.mariotaku.twidere.util.sync.DataSyncProvider
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -207,19 +206,19 @@ object defaultAPIConfigKey : KPreferenceKey<CustomAPIConfig> {
|
||||
|
||||
}
|
||||
|
||||
object dataSyncProviderInfoKey : KPreferenceKey<SyncProviderInfo?> {
|
||||
object dataSyncProviderInfoKey : KPreferenceKey<DataSyncProvider?> {
|
||||
private const val PROVIDER_TYPE_KEY = "sync_provider_type"
|
||||
|
||||
override fun contains(preferences: SharedPreferences): Boolean {
|
||||
return read(preferences) != null
|
||||
}
|
||||
|
||||
override fun read(preferences: SharedPreferences): SyncProviderInfo? {
|
||||
override fun read(preferences: SharedPreferences): DataSyncProvider? {
|
||||
val type = preferences.getString(PROVIDER_TYPE_KEY, null) ?: return null
|
||||
return SyncProviderInfoFactory.getInfoForType(type, preferences)
|
||||
return DataSyncProvider.Factory.createForType(type, preferences)
|
||||
}
|
||||
|
||||
override fun write(editor: SharedPreferences.Editor, value: SyncProviderInfo?): Boolean {
|
||||
override fun write(editor: SharedPreferences.Editor, value: DataSyncProvider?): Boolean {
|
||||
if (value == null) {
|
||||
editor.remove(PROVIDER_TYPE_KEY)
|
||||
} else {
|
||||
|
@ -1,8 +1,10 @@
|
||||
package org.mariotaku.twidere.extension.model
|
||||
|
||||
import org.mariotaku.ktextension.addAllTo
|
||||
import org.mariotaku.microblog.library.twitter.model.Status
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.ParcelableUserMention
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
|
||||
|
||||
@ -15,7 +17,7 @@ val ParcelableStatus.media_type: Int
|
||||
val ParcelableStatus.user: ParcelableUser
|
||||
get() = ParcelableUser(account_key, user_key, user_name, user_screen_name, user_profile_image_url)
|
||||
|
||||
val ParcelableStatus.referenced_users: Array<ParcelableUser>
|
||||
val ParcelableStatus.referencedUsers: Array<ParcelableUser>
|
||||
get() {
|
||||
val resultList = mutableSetOf(user)
|
||||
if (quoted_user_key != null) {
|
||||
@ -33,7 +35,24 @@ val ParcelableStatus.referenced_users: Array<ParcelableUser>
|
||||
return resultList.toTypedArray()
|
||||
}
|
||||
|
||||
val ParcelableStatus.replyMentions: Array<ParcelableUserMention>
|
||||
get() {
|
||||
val result = ArrayList<ParcelableUserMention>()
|
||||
result.add(parcelableUserMention(user_key, user_name, user_screen_name))
|
||||
if (is_retweet) retweeted_by_user_key?.let { key ->
|
||||
result.add(parcelableUserMention(key, retweeted_by_user_name,
|
||||
retweeted_by_user_screen_name))
|
||||
}
|
||||
mentions?.addAllTo(result)
|
||||
return result.toTypedArray()
|
||||
}
|
||||
|
||||
fun Array<Status>.toParcelables(accountKey: UserKey, accountType: String, profileImageSize: String) = Array(size) { i ->
|
||||
ParcelableStatusUtils.fromStatus(this[i], accountKey, accountType, false, profileImageSize)
|
||||
}
|
||||
|
||||
private fun parcelableUserMention(key: UserKey, name: String, screenName: String) = ParcelableUserMention().also {
|
||||
it.key = key
|
||||
it.name = name
|
||||
it.screen_name = screenName
|
||||
}
|
@ -20,25 +20,28 @@
|
||||
package org.mariotaku.twidere.extension.text.twitter
|
||||
|
||||
import com.twitter.Extractor
|
||||
import org.mariotaku.twidere.extension.model.replyMentions
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.ParcelableUserMention
|
||||
|
||||
fun Extractor.extractMentionsAndNonMentionStartIndex(text: String): MentionsAndNonMentionStartIndex {
|
||||
fun Extractor.extractMentionsAndNonMentionStartIndex(text: String, mentions: Array<ParcelableUserMention>?): MentionsAndNonMentionStartIndex {
|
||||
var nextExpectedPos = 0
|
||||
val mentions = extractMentionedScreennamesWithIndices(text)
|
||||
val entities = extractMentionedScreennamesWithIndices(text)
|
||||
@Suppress("LoopToCallChain")
|
||||
for (entity in mentions) {
|
||||
for (entity in entities) {
|
||||
if (entity.start != nextExpectedPos) break
|
||||
// Break at first mention not found in `inReplyTo.mentions`
|
||||
if (mentions?.none { entity.value.equals(it.screen_name, ignoreCase = true) } ?: false) break
|
||||
nextExpectedPos = (entity.end..text.indices.endInclusive).firstOrNull {
|
||||
!text[it].isWhitespace()
|
||||
} ?: text.indices.endInclusive + 1
|
||||
}
|
||||
return MentionsAndNonMentionStartIndex(mentions, nextExpectedPos)
|
||||
return MentionsAndNonMentionStartIndex(entities, nextExpectedPos)
|
||||
}
|
||||
|
||||
fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableStatus): ReplyTextAndMentions {
|
||||
// First extract mentions and 'real text' start index
|
||||
val (textMentions, index) = extractMentionsAndNonMentionStartIndex(text)
|
||||
val (textMentions, index) = extractMentionsAndNonMentionStartIndex(text, inReplyTo.replyMentions)
|
||||
|
||||
val replyMentions = run {
|
||||
val mentions = inReplyTo.mentions?.toMutableList() ?: mutableListOf()
|
||||
|
@ -436,16 +436,17 @@ abstract class AbsActivitiesFragment protected constructor() :
|
||||
if (position == RecyclerView.NO_POSITION || adapter.getActivityCount(false) <= 0) return
|
||||
val item = adapter.getActivity(position)
|
||||
var positionUpdated = false
|
||||
readPositionTag?.let {
|
||||
for (accountKey in accountKeys) {
|
||||
val tag = Utils.getReadPositionTagWithAccount(it, accountKey)
|
||||
readPositionTag?.let { positionTag ->
|
||||
accountKeys.forEach { accountKey ->
|
||||
val tag = Utils.getReadPositionTagWithAccount(positionTag, accountKey)
|
||||
if (readStateManager.setPosition(tag, item.timestamp)) {
|
||||
positionUpdated = true
|
||||
}
|
||||
timelineSyncManager?.setPosition(positionTag, tag, item.position_key)
|
||||
}
|
||||
currentReadPositionTag?.let { currentTag ->
|
||||
readStateManager.setPosition(currentTag, item.timestamp, true)
|
||||
}
|
||||
}
|
||||
currentReadPositionTag?.let {
|
||||
readStateManager.setPosition(it, item.timestamp, true)
|
||||
}
|
||||
|
||||
if (positionUpdated) {
|
||||
|
@ -471,9 +471,14 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
||||
} else {
|
||||
status.position_key
|
||||
}
|
||||
val positionTag = readPositionTag ?: ReadPositionTag.CUSTOM_TIMELINE
|
||||
readPositionTagWithArguments?.let {
|
||||
accountKeys.map { accountKey -> Utils.getReadPositionTagWithAccount(it, accountKey) }
|
||||
.forEach { readStateManager.setPosition(it, readPosition) }
|
||||
accountKeys.forEach { accountKey ->
|
||||
val tag = Utils.getReadPositionTagWithAccount(it, accountKey)
|
||||
readStateManager.setPosition(tag, readPosition)
|
||||
|
||||
timelineSyncManager?.setPosition(positionTag, tag, status.position_key)
|
||||
}
|
||||
}
|
||||
currentReadPositionTag?.let {
|
||||
readStateManager.setPosition(it, readPosition, true)
|
||||
|
@ -33,6 +33,7 @@ import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
import org.mariotaku.twidere.util.schedule.StatusScheduleProvider
|
||||
import org.mariotaku.twidere.util.sync.TimelineSyncManager
|
||||
import javax.inject.Inject
|
||||
|
||||
open class BaseFragment : Fragment(), IBaseFragment<BaseFragment> {
|
||||
@ -69,11 +70,16 @@ open class BaseFragment : Fragment(), IBaseFragment<BaseFragment> {
|
||||
@Inject
|
||||
lateinit var statusScheduleProviderFactory: StatusScheduleProvider.Factory
|
||||
@Inject
|
||||
lateinit var timelineSyncManagerFactory: TimelineSyncManager.Factory
|
||||
@Inject
|
||||
lateinit var restHttpClient: RestHttpClient
|
||||
|
||||
protected val statusScheduleProvider: StatusScheduleProvider?
|
||||
get() = statusScheduleProviderFactory.newInstance(context)
|
||||
|
||||
protected val timelineSyncManager: TimelineSyncManager?
|
||||
get() = timelineSyncManagerFactory.get()
|
||||
|
||||
private val actionHelper = IBaseFragment.ActionHelper(this)
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
|
@ -98,7 +98,8 @@ abstract class AbsStatusDialogFragment : BaseDialogFragment() {
|
||||
currentDialog.itemContent.itemMenu.visibility = View.GONE
|
||||
currentDialog.itemContent.actionButtons.visibility = View.GONE
|
||||
val adapter = DummyItemAdapter(fragment.context, requestManager = Glide.with(fragment))
|
||||
adapter.setShouldShowAccountsColor(true)
|
||||
adapter.showCardActions = false
|
||||
adapter.showAccountsColor = true
|
||||
val holder = StatusViewHolder(adapter, currentDialog.itemContent)
|
||||
holder.displayStatus(status = status, displayInReplyTo = false)
|
||||
currentDialog.onStatusLoaded(details, status, savedInstanceState)
|
||||
|
@ -36,7 +36,6 @@ import android.widget.EditText
|
||||
import android.widget.ImageButton
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import com.bumptech.glide.Glide
|
||||
import com.twitter.Validator
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.task
|
||||
@ -45,7 +44,6 @@ import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.activity.content.RetweetQuoteDialogActivity
|
||||
import org.mariotaku.twidere.adapter.DummyItemAdapter
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.constant.IntentConstants.*
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_QUICK_SEND
|
||||
@ -65,7 +63,6 @@ import org.mariotaku.twidere.util.Utils.isMyRetweet
|
||||
import org.mariotaku.twidere.util.view.SimpleTextWatcher
|
||||
import org.mariotaku.twidere.view.ComposeEditText
|
||||
import org.mariotaku.twidere.view.StatusTextCountView
|
||||
import org.mariotaku.twidere.view.holder.StatusViewHolder
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -99,12 +96,6 @@ class RetweetQuoteDialogFragment : AbsStatusDialogFragment() {
|
||||
|
||||
override fun AlertDialog.onStatusLoaded(details: AccountDetails, status: ParcelableStatus,
|
||||
savedInstanceState: Bundle?) {
|
||||
|
||||
val adapter = DummyItemAdapter(context, requestManager = Glide.with(this@RetweetQuoteDialogFragment))
|
||||
adapter.setShouldShowAccountsColor(true)
|
||||
val holder = StatusViewHolder(adapter, itemContent)
|
||||
holder.displayStatus(status = status, displayInReplyTo = false)
|
||||
|
||||
textCountView.maxLength = details.textLimit
|
||||
|
||||
val useQuote = useQuote(!status.user_is_protected, details)
|
||||
|
@ -27,7 +27,7 @@ import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS
|
||||
import org.mariotaku.twidere.constant.nameFirstKey
|
||||
import org.mariotaku.twidere.extension.applyTheme
|
||||
import org.mariotaku.twidere.extension.model.referenced_users
|
||||
import org.mariotaku.twidere.extension.model.referencedUsers
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.fragment.CreateUserBlockDialogFragment
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
@ -42,7 +42,7 @@ class BlockStatusUsersDialogFragment : BaseDialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(context)
|
||||
val referencedUsers = status.referenced_users
|
||||
val referencedUsers = status.referencedUsers
|
||||
val nameFirst = preferences[nameFirstKey]
|
||||
val displayNames = referencedUsers.map {
|
||||
userColorNameManager.getDisplayName(it, nameFirst)
|
||||
|
@ -27,7 +27,7 @@ import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS
|
||||
import org.mariotaku.twidere.constant.nameFirstKey
|
||||
import org.mariotaku.twidere.extension.applyTheme
|
||||
import org.mariotaku.twidere.extension.model.referenced_users
|
||||
import org.mariotaku.twidere.extension.model.referencedUsers
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.fragment.CreateUserMuteDialogFragment
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
@ -42,7 +42,7 @@ class MuteStatusUsersDialogFragment : BaseDialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(context)
|
||||
val referencedUsers = status.referenced_users
|
||||
val referencedUsers = status.referencedUsers
|
||||
val nameFirst = preferences[nameFirstKey]
|
||||
val displayNames = referencedUsers.map {
|
||||
userColorNameManager.getDisplayName(it, nameFirst)
|
||||
|
@ -13,9 +13,8 @@ import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
|
||||
import org.mariotaku.twidere.extension.applyTheme
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.fragment.BasePreferenceFragment
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderInfo
|
||||
import org.mariotaku.twidere.util.TaskServiceRunner
|
||||
import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
|
||||
import org.mariotaku.twidere.util.sync.DataSyncProvider
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/3.
|
||||
@ -23,12 +22,12 @@ import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
|
||||
|
||||
class SyncSettingsFragment : BasePreferenceFragment() {
|
||||
|
||||
private var providerInfo: SyncProviderInfo? = null
|
||||
private var syncProvider: DataSyncProvider? = null
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
providerInfo = kPreferences[dataSyncProviderInfoKey]
|
||||
syncProvider = kPreferences[dataSyncProviderInfoKey]
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
@ -74,9 +73,10 @@ class SyncSettingsFragment : BasePreferenceFragment() {
|
||||
}
|
||||
|
||||
private fun cleanupAndDisconnect() {
|
||||
val providerInfo = kPreferences[dataSyncProviderInfoKey]!!
|
||||
val providerInfo = kPreferences[dataSyncProviderInfoKey] ?: return
|
||||
syncController.cleanupSyncCache(providerInfo)
|
||||
kPreferences[dataSyncProviderInfoKey] = null
|
||||
DataSyncProvider.Factory.notifyUpdate(context)
|
||||
activity?.finish()
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ class SyncSettingsFragment : BasePreferenceFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(context)
|
||||
val providerInfo = kPreferences[dataSyncProviderInfoKey]!!
|
||||
val entry = SyncProviderInfoFactory.getProviderEntry(context, providerInfo.type)!!
|
||||
val entry = DataSyncProvider.Factory.getProviderEntry(context, providerInfo.type)!!
|
||||
builder.setMessage(getString(R.string.message_sync_disconnect_from_name_confirm, entry.name))
|
||||
builder.setPositiveButton(R.string.action_sync_disconnect) { _, _ ->
|
||||
(parentFragment as SyncSettingsFragment).cleanupAndDisconnect()
|
||||
|
@ -1,14 +0,0 @@
|
||||
package org.mariotaku.twidere.model.sync
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.mariotaku.twidere.util.sync.SyncTaskRunner
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/2.
|
||||
*/
|
||||
|
||||
abstract class SyncProviderInfo(val type: String) {
|
||||
abstract fun writeToPreferences(editor: SharedPreferences.Editor)
|
||||
abstract fun newSyncTaskRunner(context: Context): SyncTaskRunner
|
||||
}
|
@ -11,6 +11,7 @@ import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import org.mariotaku.twidere.util.media.MediaPreloader
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
import org.mariotaku.twidere.util.schedule.StatusScheduleProvider
|
||||
import org.mariotaku.twidere.util.sync.TimelineSyncManager
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@ -45,6 +46,8 @@ abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context)
|
||||
lateinit var scheduleProviderFactory: StatusScheduleProvider.Factory
|
||||
@Inject
|
||||
lateinit var extractor: Extractor
|
||||
@Inject
|
||||
lateinit var timelineSyncManagerFactory: TimelineSyncManager.Factory
|
||||
|
||||
val scheduleProvider: StatusScheduleProvider?
|
||||
get() = scheduleProviderFactory.newInstance(context)
|
||||
|
@ -41,7 +41,10 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
|
||||
override val errorInfoKey: String
|
||||
get() = ErrorInfoStore.KEY_INTERACTIONS
|
||||
|
||||
override fun saveReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog) {
|
||||
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)
|
||||
@ -72,7 +75,4 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
|
||||
statuses.mapTo(activities) { InternalActivityCreator.status(it, details.key.id) }
|
||||
return activities
|
||||
}
|
||||
|
||||
override val contentUri: Uri
|
||||
get() = Activities.AboutMe.CONTENT_URI
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ abstract class GetActivitiesTask(
|
||||
val storeResult = storeActivities(cr, loadItemLimit, credentials, noItemsBefore,
|
||||
activities, sinceId, maxId, false)
|
||||
if (saveReadPosition) {
|
||||
saveReadPosition(accountKey, credentials, microBlog)
|
||||
setLocalReadPosition(accountKey, credentials, microBlog)
|
||||
}
|
||||
errorInfoStore.remove(errorInfoKey, accountKey)
|
||||
if (storeResult != 0) {
|
||||
@ -116,6 +116,16 @@ abstract class GetActivitiesTask(
|
||||
handler?.invoke(true)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
override fun beforeExecute() {
|
||||
bus.post(GetActivitiesTaskEvent(contentUri, true, null))
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
private fun storeActivities(cr: ContentResolver, loadItemLimit: Int, details: AccountDetails,
|
||||
noItemsBefore: Boolean, activities: ResponseList<Activity>,
|
||||
sinceId: String?, maxId: String?, notify: Boolean): Int {
|
||||
@ -196,14 +206,4 @@ abstract class GetActivitiesTask(
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@UiThread
|
||||
override fun beforeExecute() {
|
||||
bus.post(GetActivitiesTaskEvent(contentUri, true, null))
|
||||
}
|
||||
|
||||
protected abstract fun saveReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog)
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
protected abstract fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging): ResponseList<Activity>
|
||||
}
|
||||
|
@ -26,23 +26,38 @@ import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
||||
import org.mariotaku.microblog.library.twitter.model.Status
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/11.
|
||||
*/
|
||||
class GetHomeTimelineTask(context: Context) : GetStatusesTask(context) {
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getStatuses(twitter: MicroBlog, paging: Paging): ResponseList<Status> {
|
||||
return twitter.getHomeTimeline(paging)
|
||||
}
|
||||
|
||||
override val contentUri: Uri
|
||||
get() = Statuses.CONTENT_URI
|
||||
|
||||
override val errorInfoKey: String
|
||||
get() = ErrorInfoStore.KEY_HOME_TIMELINE
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getStatuses(twitter: MicroBlog, paging: Paging): ResponseList<Status> {
|
||||
return twitter.getHomeTimeline(paging)
|
||||
}
|
||||
|
||||
override fun setLocalReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog) {
|
||||
val syncManager = timelineSyncManagerFactory.get() ?: return
|
||||
try {
|
||||
val tag = Utils.getReadPositionTagWithAccount(ReadPositionTag.HOME_TIMELINE, accountKey)
|
||||
val positionKey = syncManager.blockingGetPosition(ReadPositionTag.HOME_TIMELINE, tag)
|
||||
readStateManager
|
||||
}catch (e: IOException) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ abstract class GetStatusesTask(
|
||||
val sinceSortIds = param.sinceSortIds
|
||||
val result = ArrayList<TwitterWrapper.StatusListResponse>()
|
||||
val loadItemLimit = preferences[loadItemLimitKey]
|
||||
var saveReadPosition = false
|
||||
for (i in 0 until accountKeys.size) {
|
||||
val accountKey = accountKeys[i]
|
||||
val details = AccountUtils.getAccountDetails(AccountManager.get(context),
|
||||
@ -97,12 +98,16 @@ abstract class GetStatusesTask(
|
||||
if (maxIds == null) {
|
||||
paging.setLatestResults(true)
|
||||
}
|
||||
saveReadPosition = true
|
||||
} else {
|
||||
sinceId = null
|
||||
}
|
||||
val statuses = getStatuses(microBlog, paging)
|
||||
val storeResult = storeStatus(accountKey, details, statuses, sinceId, maxId,
|
||||
sinceSortId, maxSortId, loadItemLimit, false)
|
||||
if (saveReadPosition) {
|
||||
setLocalReadPosition(accountKey, details, microBlog)
|
||||
}
|
||||
// TODO cache related data and preload
|
||||
val cacheTask = CacheUsersStatusesTask(context, accountKey, details.type, statuses)
|
||||
TaskStarter.execute(cacheTask)
|
||||
@ -137,6 +142,9 @@ abstract class GetStatusesTask(
|
||||
bus.post(GetStatusesTaskEvent(contentUri, true, null))
|
||||
}
|
||||
|
||||
protected abstract fun setLocalReadPosition(accountKey: UserKey, details: AccountDetails,
|
||||
twitter: MicroBlog)
|
||||
|
||||
private fun storeStatus(accountKey: UserKey, details: AccountDetails,
|
||||
statuses: List<Status>,
|
||||
sinceId: String?, maxId: String?,
|
||||
|
@ -159,9 +159,11 @@ class UpdateStatusTask(
|
||||
}) return@mapNotNull null
|
||||
return@mapNotNull mention.key.id
|
||||
}.toTypedArray()
|
||||
val replyToSelf = details.key == inReplyTo.user_key
|
||||
pending.replyToSelf[i] = replyToSelf
|
||||
pending.replyToOriginalUser[i] = replyToOriginalUser
|
||||
// Fix status to at least make mentioned user know what status it is
|
||||
if (!replyToOriginalUser && update.attachment_url == null) {
|
||||
if (!replyToOriginalUser && !replyToSelf && update.attachment_url == null) {
|
||||
update.attachment_url = LinkCreator.getTwitterStatusLink(inReplyTo.user_screen_name,
|
||||
inReplyTo.id).toString()
|
||||
}
|
||||
@ -425,9 +427,10 @@ class UpdateStatusTask(
|
||||
status.inReplyToStatusId(inReplyToStatus.id)
|
||||
if (statusUpdate.accounts[index].type == AccountType.TWITTER) {
|
||||
val replyToOriginalUser = pendingUpdate.replyToOriginalUser[index]
|
||||
status.autoPopulateReplyMetadata(replyToOriginalUser)
|
||||
val replyToSelf = pendingUpdate.replyToSelf[index]
|
||||
status.autoPopulateReplyMetadata(replyToOriginalUser || replyToSelf)
|
||||
val excludeReplyIds = pendingUpdate.excludeReplyUserIds[index]
|
||||
if (replyToOriginalUser && excludeReplyIds.isNotNullOrEmpty()) {
|
||||
if ((replyToOriginalUser || replyToSelf) && excludeReplyIds.isNotNullOrEmpty()) {
|
||||
status.excludeReplyUserIds(excludeReplyIds)
|
||||
}
|
||||
}
|
||||
@ -548,6 +551,7 @@ class UpdateStatusTask(
|
||||
val overrideTexts: Array<String> = Array(length) { defaultText }
|
||||
val excludeReplyUserIds: Array<Array<String>?> = arrayOfNulls(length)
|
||||
val replyToOriginalUser: BooleanArray = BooleanArray(length)
|
||||
val replyToSelf: BooleanArray = BooleanArray(length)
|
||||
val mediaIds: Array<Array<String>?> = arrayOfNulls(length)
|
||||
|
||||
val mediaUploadResults: Array<MediaUploadResult?> = arrayOfNulls(length)
|
||||
|
@ -63,10 +63,7 @@ import org.mariotaku.twidere.util.refresh.AutoRefreshController
|
||||
import org.mariotaku.twidere.util.refresh.JobSchedulerAutoRefreshController
|
||||
import org.mariotaku.twidere.util.refresh.LegacyAutoRefreshController
|
||||
import org.mariotaku.twidere.util.schedule.StatusScheduleProvider
|
||||
import org.mariotaku.twidere.util.sync.JobSchedulerSyncController
|
||||
import org.mariotaku.twidere.util.sync.LegacySyncController
|
||||
import org.mariotaku.twidere.util.sync.SyncController
|
||||
import org.mariotaku.twidere.util.sync.SyncPreferences
|
||||
import org.mariotaku.twidere.util.sync.*
|
||||
import java.io.File
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -255,18 +252,6 @@ class ApplicationModule(private val application: Application) {
|
||||
return LegacySyncController(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun statusScheduleProviderFactory(): StatusScheduleProvider.Factory {
|
||||
return StatusScheduleProvider.Factory.instance
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun gifShareProviderFactory(): GifShareProvider.Factory {
|
||||
return GifShareProvider.Factory.instance
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun syncPreferences(): SyncPreferences {
|
||||
@ -355,6 +340,24 @@ class ApplicationModule(private val application: Application) {
|
||||
return DiskLRUFileCache(getCacheDir("media", 100 * 1048576L))
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun statusScheduleProviderFactory(): StatusScheduleProvider.Factory {
|
||||
return StatusScheduleProvider.newFactory()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun gifShareProviderFactory(): GifShareProvider.Factory {
|
||||
return GifShareProvider.newFactory()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun timelineSyncManagerFactory(): TimelineSyncManager.Factory {
|
||||
return TimelineSyncManager.newFactory()
|
||||
}
|
||||
|
||||
private fun getCacheDir(dirName: String, sizeInBytes: Long): File {
|
||||
return Utils.getExternalCacheDir(application, dirName, sizeInBytes) ?:
|
||||
Utils.getInternalCacheDir(application, dirName)
|
||||
|
@ -30,6 +30,7 @@ import org.mariotaku.twidere.model.DefaultFeatures
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.media.MediaPreloader
|
||||
import org.mariotaku.twidere.util.media.ThumborWrapper
|
||||
import org.mariotaku.twidere.util.sync.TimelineSyncManager
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@ -78,6 +79,9 @@ class DependencyHolder internal constructor(context: Context) {
|
||||
@Inject
|
||||
lateinit var thumbor: ThumborWrapper
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var timelineSyncManagerFactory: TimelineSyncManager.Factory
|
||||
internal set
|
||||
|
||||
init {
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
|
@ -36,13 +36,14 @@ interface GifShareProvider {
|
||||
interface Factory {
|
||||
fun newInstance(context: Context): GifShareProvider?
|
||||
|
||||
companion object {
|
||||
val instance: Factory get() = ServiceLoader.load(Factory::class.java)?.firstOrNull() ?: NullFactory
|
||||
}
|
||||
|
||||
private object NullFactory : Factory {
|
||||
override fun newInstance(context: Context) = null
|
||||
companion object {
|
||||
fun newFactory(): Factory = ServiceLoader.load(Factory::class.java)?.firstOrNull() ?: NullFactory
|
||||
|
||||
private object NullFactory : Factory {
|
||||
override fun newInstance(context: Context) = null
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,11 @@ class TwidereGlideModule : GlideModule {
|
||||
val conf = HttpClientFactory.HttpClientConfiguration(holder.preferences)
|
||||
val thumbor = holder.thumbor
|
||||
HttpClientFactory.initOkHttpClient(conf, builder, holder.dns, holder.connectionPool, holder.cache)
|
||||
val userAgent = UserAgentUtils.getDefaultUserAgentString(context)
|
||||
val userAgent = try {
|
||||
UserAgentUtils.getDefaultUserAgentStringSafe(context)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
builder.addInterceptor(ModifyRequestInterceptor(ThumborModifier(thumbor), UserAgentModifier(userAgent)))
|
||||
val client = builder.build()
|
||||
glide.register(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(client))
|
||||
|
@ -61,15 +61,16 @@ interface StatusScheduleProvider {
|
||||
|
||||
fun parseInfo(json: String): ScheduleInfo?
|
||||
|
||||
companion object {
|
||||
val instance: Factory get() = ServiceLoader.load(Factory::class.java)?.firstOrNull() ?: NullFactory
|
||||
}
|
||||
|
||||
private object NullFactory : Factory {
|
||||
override fun newInstance(context: Context) = null
|
||||
private object NullFactory : Factory {
|
||||
override fun newInstance(context: Context) = null
|
||||
|
||||
override fun parseInfo(json: String): ScheduleInfo? = null
|
||||
override fun parseInfo(json: String): ScheduleInfo? = null
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newFactory(): Factory = ServiceLoader.load(Factory::class.java)?.firstOrNull() ?: NullFactory
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.sync
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderEntry
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/2.
|
||||
*/
|
||||
abstract class DataSyncProvider(val type: String) {
|
||||
|
||||
abstract fun writeToPreferences(editor: SharedPreferences.Editor)
|
||||
|
||||
abstract fun newSyncTaskRunner(context: Context): SyncTaskRunner
|
||||
|
||||
open fun newTimelineSyncManager(context: Context): TimelineSyncManager? = null
|
||||
|
||||
abstract class Factory {
|
||||
abstract fun createForType(type: String, preferences: SharedPreferences): DataSyncProvider?
|
||||
|
||||
abstract fun getSupportedProviders(context: Context): List<SyncProviderEntry>
|
||||
|
||||
abstract fun notifyUpdate(context: Context)
|
||||
|
||||
companion object {
|
||||
fun notifyUpdate(context: Context) {
|
||||
ServiceLoader.load(Factory::class.java).forEach { factory ->
|
||||
factory.notifyUpdate(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun createForType(type: String, preferences: SharedPreferences): DataSyncProvider? {
|
||||
ServiceLoader.load(Factory::class.java).forEach { factory ->
|
||||
val info = factory.createForType(type, preferences)
|
||||
if (info != null) return info
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getSupportedProviders(context: Context): List<SyncProviderEntry> {
|
||||
val result = ArrayList<SyncProviderEntry>()
|
||||
ServiceLoader.load(Factory::class.java).forEach { factory ->
|
||||
result.addAll(factory.getSupportedProviders(context))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun getProviderEntry(context: Context, type: String): SyncProviderEntry? {
|
||||
ServiceLoader.load(Factory::class.java).forEach { factory ->
|
||||
factory.getSupportedProviders(context).forEach { entry ->
|
||||
if (entry.type == type) return entry
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -3,14 +3,13 @@ package org.mariotaku.twidere.util.sync
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderEntry
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderInfo
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/2.
|
||||
*/
|
||||
|
||||
class OpenSourceSyncProviderInfoFactory : SyncProviderInfoFactory() {
|
||||
override fun getInfoForType(type: String, preferences: SharedPreferences): SyncProviderInfo? {
|
||||
class OpenSourceSyncProviderInfoFactory : DataSyncProvider.Factory() {
|
||||
override fun createForType(type: String, preferences: SharedPreferences): DataSyncProvider? {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -18,4 +17,8 @@ class OpenSourceSyncProviderInfoFactory : SyncProviderInfoFactory() {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun notifyUpdate(context: Context) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.mariotaku.twidere.util.sync
|
||||
|
||||
import android.content.Context
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderInfo
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/3.
|
||||
@ -10,11 +9,11 @@ import org.mariotaku.twidere.model.sync.SyncProviderInfo
|
||||
abstract class SyncController(val context: Context) {
|
||||
abstract fun appStarted()
|
||||
|
||||
fun performSync(providerInfo: SyncProviderInfo) {
|
||||
providerInfo.newSyncTaskRunner(context).performSync()
|
||||
fun performSync(syncProvider: DataSyncProvider) {
|
||||
syncProvider.newSyncTaskRunner(context).performSync()
|
||||
}
|
||||
|
||||
fun cleanupSyncCache(providerInfo: SyncProviderInfo) {
|
||||
providerInfo.newSyncTaskRunner(context).cleanupSyncCache()
|
||||
fun cleanupSyncCache(syncProvider: DataSyncProvider) {
|
||||
syncProvider.newSyncTaskRunner(context).cleanupSyncCache()
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
package org.mariotaku.twidere.util.sync
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderEntry
|
||||
import org.mariotaku.twidere.model.sync.SyncProviderInfo
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/2.
|
||||
*/
|
||||
|
||||
abstract class SyncProviderInfoFactory {
|
||||
abstract fun getInfoForType(type: String, preferences: SharedPreferences): SyncProviderInfo?
|
||||
|
||||
abstract fun getSupportedProviders(context: Context): List<SyncProviderEntry>
|
||||
|
||||
companion object {
|
||||
fun getInfoForType(type: String, preferences: SharedPreferences): SyncProviderInfo? {
|
||||
ServiceLoader.load(SyncProviderInfoFactory::class.java).forEach { factory ->
|
||||
val info = factory.getInfoForType(type, preferences)
|
||||
if (info != null) return info
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getSupportedProviders(context: Context): List<SyncProviderEntry> {
|
||||
val result = ArrayList<SyncProviderEntry>()
|
||||
ServiceLoader.load(SyncProviderInfoFactory::class.java).forEach { factory ->
|
||||
result.addAll(factory.getSupportedProviders(context))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun getProviderEntry(context: Context, type: String): SyncProviderEntry? {
|
||||
ServiceLoader.load(SyncProviderInfoFactory::class.java).forEach { factory ->
|
||||
factory.getSupportedProviders(context).forEach { entry ->
|
||||
if (entry.type == type) return entry
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.sync
|
||||
|
||||
import android.content.Context
|
||||
import android.support.annotation.UiThread
|
||||
import android.support.annotation.WorkerThread
|
||||
import android.support.v4.util.ArrayMap
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/4/13.
|
||||
*/
|
||||
|
||||
abstract class TimelineSyncManager(val context: Context) {
|
||||
|
||||
private val internalMap = ArrayMap<TimelineKey, Long>()
|
||||
|
||||
fun setPosition(@ReadPositionTag positionTag: String, currentTag: String?, positionKey: Long) {
|
||||
internalMap[TimelineKey(positionTag, currentTag)] = positionKey
|
||||
}
|
||||
|
||||
fun commit() {
|
||||
val data = internalMap.map { (key, value) ->
|
||||
PositionData(key.positionTag, key.currentTag, value)
|
||||
}.toTypedArray()
|
||||
internalMap.clear()
|
||||
performSync(data)
|
||||
}
|
||||
|
||||
|
||||
@UiThread
|
||||
protected abstract fun performSync(data: Array<PositionData>)
|
||||
|
||||
@WorkerThread
|
||||
@Throws(IOException::class)
|
||||
abstract fun blockingGetPosition(@ReadPositionTag positionTag: String, currentTag: String?): Long
|
||||
|
||||
data class TimelineKey(val positionTag: String, val currentTag: String?)
|
||||
data class PositionData(val positionTag: String, val currentTag: String?, val positionKey: Long)
|
||||
|
||||
abstract class Factory {
|
||||
protected var manager: TimelineSyncManager? = null
|
||||
|
||||
fun get(): TimelineSyncManager? = manager
|
||||
|
||||
fun setup(context: Context) {
|
||||
manager = create(context)
|
||||
}
|
||||
|
||||
protected abstract fun create(context: Context): TimelineSyncManager?
|
||||
|
||||
}
|
||||
|
||||
object DummyFactory : Factory() {
|
||||
override fun create(context: Context) = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newFactory(): Factory = ServiceLoader.load(Factory::class.java).firstOrNull() ?: DummyFactory
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -19,7 +19,7 @@ import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
|
||||
import org.mariotaku.twidere.fragment.sync.SyncSettingsFragment
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
|
||||
import org.mariotaku.twidere.util.sync.DataSyncProvider
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/3.
|
||||
@ -72,7 +72,7 @@ class SyncStatusViewController : PremiumDashboardActivity.ExtraFeatureViewContro
|
||||
button1.setText(R.string.action_purchase)
|
||||
}
|
||||
} else {
|
||||
val providerEntry = SyncProviderInfoFactory.getProviderEntry(context, providerInfo.type)!!
|
||||
val providerEntry = DataSyncProvider.Factory.getProviderEntry(context, providerInfo.type)!!
|
||||
messageView.text = context.getString(R.string.message_sync_data_synced_with_name, providerEntry.name)
|
||||
button1.visibility = View.GONE
|
||||
button2.visibility = View.VISIBLE
|
||||
@ -88,7 +88,7 @@ class SyncStatusViewController : PremiumDashboardActivity.ExtraFeatureViewContro
|
||||
|
||||
class ConnectNetworkStorageSelectionDialogFragment : BaseDialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val providers = SyncProviderInfoFactory.getSupportedProviders(context)
|
||||
val providers = DataSyncProvider.Factory.getSupportedProviders(context)
|
||||
val itemNames = providers.map { it.name }.toTypedArray()
|
||||
|
||||
val builder = AlertDialog.Builder(context)
|
||||
|
Loading…
x
Reference in New Issue
Block a user