1
0
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:
Mariotaku Lee 2017-04-14 00:57:14 +08:00
parent 56c65af196
commit 5f58d0edce
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
39 changed files with 412 additions and 265 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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