started to abandon AbstractTask

This commit is contained in:
Mariotaku Lee 2017-11-06 19:09:14 +08:00
parent 9a21a5f4ee
commit b533e4157d
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
47 changed files with 631 additions and 630 deletions

View File

@ -77,7 +77,7 @@ subprojects {
AndroidImageCropper : '2.4.6',
ExportablePreferences : '0.9.7',
ACRA : '4.9.2',
AbstractTask : '0.9.5',
AbstractTask : '0.9.8',
Dagger : '2.11',
StethoBeanShellREPL : '0.3',
ArchLifecycleExtensions: '1.0.0-beta2',

View File

@ -23,11 +23,11 @@ import android.os.Parcelable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.bluelinelabs.logansquare.annotation.OnJsonParseComplete;
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
/**
* Created by mariotaku on 15/12/31.
*/
import java.io.IOException;
@ParcelablePlease
@JsonObject
public class CardResponse implements Parcelable {
@ -38,6 +38,11 @@ public class CardResponse implements Parcelable {
return card;
}
@OnJsonParseComplete
void onParseComplete() throws IOException {
if (card == null) throw new IOException("Empty card result");
}
@Override
public int describeContents() {
return 0;

View File

@ -3,23 +3,18 @@ package org.mariotaku.abstask.library;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
/**
* Created by mariotaku on 16/5/25.
*/
public class ManualTaskStarter {
@UiThread
public static void invokeBeforeExecute(AbstractTask<?, ?, ?> task) {
task.invokeBeforeExecute();
}
@UiThread
public static <Result> void invokeAfterExecute(AbstractTask<?, Result, ?> task, Result result) {
task.invokeAfterExecute(result);
}
@WorkerThread
public static <Result> Result invokeExecute(AbstractTask<?, Result, ?> task) {
return task.invokeExecute();
return null;
}
}

View File

@ -1,6 +1,7 @@
package org.mariotaku.twidere.model.util;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.mariotaku.microblog.library.twitter.model.GeoLocation;
@ -14,7 +15,7 @@ public class ParcelableLocationUtils {
private ParcelableLocationUtils() {
}
public static String getHumanReadableString(ParcelableLocation obj, int decimalDigits) {
public static String getHumanReadableString(@NonNull ParcelableLocation obj, int decimalDigits) {
return String.format("%s,%s", InternalParseUtils.parsePrettyDecimal(obj.latitude, decimalDigits),
InternalParseUtils.parsePrettyDecimal(obj.longitude, decimalDigits));
}

View File

@ -1,14 +1,26 @@
package org.mariotaku.ktextension
import android.os.Handler
import nl.komponents.kovenant.Deferred
import nl.komponents.kovenant.Kovenant
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReferenceArray
/**
* Created by mariotaku on 2016/12/2.
*/
fun <V> Promise<V, Exception>.deadline(time: Long, unit: TimeUnit): Promise<V, Exception> {
val weakPromise = toWeak()
WatchdogHandler.postDelayed({
val promise = weakPromise.get() ?: return@postDelayed
if (promise.isDone()) return@postDelayed
Kovenant.cancel(promise, DeadlineException())
}, unit.toMillis(time))
return this
}
fun <V, E> combine(promises: List<Promise<V, E>>): Promise<List<V>, E> {
return concreteCombine(promises)
}
@ -50,3 +62,9 @@ fun <V, E> concreteCombine(promises: List<Promise<V, E>>): Promise<List<V>, E> {
return deferred.promise
}
private object WatchdogHandler : Handler()
class DeadlineException : Exception() {
}

View File

@ -59,9 +59,15 @@ import com.bumptech.glide.Glide
import com.twitter.Extractor
import com.twitter.Validator
import kotlinx.android.synthetic.main.activity_compose.*
import nl.komponents.kovenant.combine.and
import nl.komponents.kovenant.task
import org.mariotaku.abstask.library.AbstractTask
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.alwaysUi
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.promiseOnUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.edit
import org.mariotaku.kpreferences.get
import org.mariotaku.kpreferences.set
import org.mariotaku.ktextension.*
@ -91,7 +97,6 @@ import org.mariotaku.twidere.preference.ComponentPickerPreference
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.service.LengthyOperationsService
import org.mariotaku.twidere.task.compose.AbsAddMediaTask
import org.mariotaku.twidere.task.compose.AbsDeleteMediaTask
import org.mariotaku.twidere.task.status.UpdateStatusTask
import org.mariotaku.twidere.text.MarkForDeleteSpan
import org.mariotaku.twidere.text.style.EmojiSpan
@ -107,10 +112,10 @@ import org.mariotaku.twidere.view.ExtendedRecyclerView
import org.mariotaku.twidere.view.ShapedImageView
import org.mariotaku.twidere.view.helper.SimpleItemTouchHelperCallback
import org.mariotaku.twidere.view.holder.compose.MediaPreviewViewHolder
import java.io.IOException
import java.text.Normalizer
import java.util.*
import javax.inject.Inject
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
import android.Manifest.permission as AndroidPermission
@ -300,7 +305,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
showLabelAndHint(intent)
val selectedAccountKeys = accountsAdapter.selectedAccountKeys
if (selectedAccountKeys.isNullOrEmpty()) {
val idsInPrefs = kPreferences[composeAccountsKey]?.asList() ?: emptyList()
val idsInPrefs = preferences[composeAccountsKey]?.asList() ?: emptyList()
val intersection = defaultAccountKeys.intersect(idsInPrefs)
if (intersection.isEmpty()) {
@ -357,9 +362,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
override fun onStart() {
super.onStart()
imageUploaderUsed = !ComponentPickerPreference.isNoneValue(kPreferences[mediaUploaderKey])
statusShortenerUsed = !ComponentPickerPreference.isNoneValue(kPreferences[statusShortenerKey])
if (kPreferences[attachLocationKey]) {
imageUploaderUsed = !ComponentPickerPreference.isNoneValue(preferences[mediaUploaderKey])
statusShortenerUsed = !ComponentPickerPreference.isNoneValue(preferences[statusShortenerKey])
if (preferences[attachLocationKey]) {
if (checkAnySelfPermissionsGranted(AndroidPermission.ACCESS_COARSE_LOCATION,
AndroidPermission.ACCESS_FINE_LOCATION)) {
try {
@ -558,18 +563,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
IntentUtils.openDrafts(this)
}
R.id.delete -> {
val media = this.media
val task = DeleteMediaTask(this, media)
task.callback = { result ->
setProgressVisible(false)
removeMedia(media.filterIndexed { i, _ -> result[i] })
setMenu()
if (result.contains(false)) {
Toast.makeText(this, R.string.message_toast_error_occurred,
Toast.LENGTH_SHORT).show()
}
}
TaskStarter.execute(task)
deleteMedia(media)
}
R.id.toggle_sensitive -> {
if (!hasMedia) return true
@ -720,7 +714,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
} else {
Toast.makeText(this, R.string.message_toast_cannot_get_location, Toast.LENGTH_SHORT).show()
kPreferences.edit {
preferences.edit {
this[attachLocationKey] = false
this[attachPreciseLocationKey] = false
}
@ -774,7 +768,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
attachPreciseLocationChecked = false
}
}
kPreferences.edit {
preferences.edit {
this[attachLocationKey] = attachLocationChecked
this[attachPreciseLocationKey] = attachPreciseLocationChecked
}
@ -1328,8 +1322,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
menu.setGroupAvailability(MENU_GROUP_IMAGE_EXTENSION, hasMedia)
menu.setItemChecked(R.id.toggle_sensitive, possiblySensitive)
val attachLocation = kPreferences[attachLocationKey]
val attachPreciseLocation = kPreferences[attachPreciseLocationKey]
val attachLocation = preferences[attachLocationKey]
val attachPreciseLocation = preferences[attachPreciseLocationKey]
if (!attachLocation) {
menu.setItemChecked(R.id.location_off, true)
@ -1383,20 +1377,12 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
private fun setRecentLocation(location: ParcelableLocation?) {
if (location != null) {
val attachPreciseLocation = kPreferences[attachPreciseLocationKey]
if (attachPreciseLocation) {
locationLabel.spannable = ParcelableLocationUtils.getHumanReadableString(location, 3)
} else {
if (locationLabel.tag == null || location != recentLocation) {
val task = DisplayPlaceNameTask()
task.params = location
task.callback = this
TaskStarter.execute(task)
}
}
} else {
if (location == null) {
locationLabel.setText(R.string.unknown_location)
} else if (preferences[attachPreciseLocationKey]) {
locationLabel.spannable = ParcelableLocationUtils.getHumanReadableString(location, 3)
} else if (locationLabel.tag == null || location != recentLocation) {
loadPlaceName(location)
}
recentLocation = location
}
@ -1409,11 +1395,11 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
@Throws(SecurityException::class)
private fun startLocationUpdateIfEnabled(): Boolean {
if (locationListener != null) return true
val attachLocation = kPreferences[attachLocationKey]
val attachLocation = preferences[attachLocationKey]
if (!attachLocation) {
return false
}
val attachPreciseLocation = kPreferences[attachPreciseLocationKey]
val attachPreciseLocation = preferences[attachPreciseLocationKey]
val criteria = Criteria()
if (attachPreciseLocation) {
criteria.accuracy = Criteria.ACCURACY_FINE
@ -1583,8 +1569,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
update.summary = summary
val attachLocation = kPreferences[attachLocationKey]
val attachPreciseLocation = kPreferences[attachPreciseLocationKey]
val attachLocation = preferences[attachLocationKey]
val attachPreciseLocation = preferences[attachPreciseLocationKey]
update.draft_action = draftAction
update.accounts = accounts
if (attachLocation) {
@ -1636,7 +1622,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
private fun updateLocationState() {
if (kPreferences[attachLocationKey]) {
if (preferences[attachLocationKey]) {
locationLabel.visibility = View.VISIBLE
if (recentLocation != null) {
setRecentLocation(recentLocation)
@ -1814,6 +1800,72 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
editTextContainer.touchDelegate = ComposeEditTextTouchDelegate(editTextContainer, editText)
}
private fun loadPlaceName(location: ParcelableLocation) {
val weakThis by weak(this)
promiseOnUi {
val activity = weakThis ?: return@promiseOnUi
activity.updateLocationLabel(location, false)
} and task {
val activity = weakThis ?: throw InterruptedException()
val gcd = Geocoder(activity, Locale.getDefault())
return@task gcd.getFromLocation(location.latitude, location.longitude, 1)
?.firstOrNull() ?: throw NoSuchElementException("Address not found")
}.successUi { address ->
val activity = weakThis ?: return@successUi
activity.locationLabel.tag = address
activity.updateLocationLabel(location, true)
}.failUi { ex ->
val activity = weakThis ?: return@failUi
activity.locationLabel.tag = if (ex is NoSuchElementException) NoAddress else null
activity.updateLocationLabel(location, true)
}
}
private fun updateLocationLabel(location: ParcelableLocation, finished: Boolean) {
when {
!preferences[attachLocationKey] -> {
locationLabel.setText(R.string.no_location)
}
preferences[attachPreciseLocationKey] -> {
locationLabel.string = ParcelableLocationUtils.getHumanReadableString(location, 3)
}
else -> {
val tag = locationLabel.tag
when (tag) {
is Address -> locationLabel.text = tag.locality
is NoAddress -> locationLabel.setText(R.string.label_location_your_coarse_location)
null -> if (finished) {
locationLabel.setText(R.string.no_location)
} else {
locationLabel.setText(R.string.getting_location)
}
}
}
}
}
private fun deleteMedia(media:Array<ParcelableMediaUpdate>) {
val weakThis by weak(this)
promiseOnUi {
weakThis?.setProgressVisible(true)
}.then {
val context = weakThis ?: throw InterruptedException()
media.forEach {
Utils.deleteMedia(context, Uri.parse(it.uri))
}
}.alwaysUi {
weakThis?.setProgressVisible(false)
weakThis?.removeMedia(media.toList())
setMenu()
}.failUi {
val context = weakThis ?: throw InterruptedException()
Toast.makeText(context, R.string.message_toast_error_occurred,
Toast.LENGTH_SHORT).show()
}
}
internal object NoAddress
class RetweetProtectedStatusWarnFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
@ -2066,93 +2118,6 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
private class DeleteMediaTask(
activity: ComposeActivity,
media: Array<ParcelableMediaUpdate>
) : AbsDeleteMediaTask<((BooleanArray) -> Unit)?>(activity, media.mapToArray { Uri.parse(it.uri) }) {
override fun beforeExecute() {
val activity = context as? ComposeActivity ?: return
activity.setProgressVisible(true)
}
override fun afterExecute(callback: ((BooleanArray) -> Unit)?, result: BooleanArray) {
callback?.invoke(result)
}
}
private class DisplayPlaceNameTask : AbstractTask<ParcelableLocation, List<Address>, ComposeActivity>() {
override fun doLongOperation(location: ParcelableLocation): List<Address>? {
try {
val activity = callback ?: throw IOException("Interrupted")
val gcd = Geocoder(activity, Locale.getDefault())
return gcd.getFromLocation(location.latitude, location.longitude, 1)
} catch (e: IOException) {
return null
}
}
override fun beforeExecute() {
val location = params
val activity = callback ?: return
val textView = activity.locationLabel ?: return
val preferences = activity.preferences
val attachLocation = preferences[attachLocationKey]
val attachPreciseLocation = preferences[attachPreciseLocationKey]
if (attachLocation) {
if (attachPreciseLocation) {
textView.spannable = ParcelableLocationUtils.getHumanReadableString(location, 3)
textView.tag = location
} else {
val tag = textView.tag
if (tag is Address) {
textView.spannable = tag.locality
} else if (tag is NoAddress) {
textView.setText(R.string.label_location_your_coarse_location)
} else {
textView.setText(R.string.getting_location)
}
}
} else {
textView.setText(R.string.no_location)
}
}
override fun afterExecute(activity: ComposeActivity?, addresses: List<Address>?) {
if (activity == null) return
val textView = activity.locationLabel ?: return
val preferences = activity.preferences
val attachLocation = preferences[attachLocationKey]
val attachPreciseLocation = preferences[attachPreciseLocationKey]
if (attachLocation) {
if (attachPreciseLocation) {
val location = params
textView.spannable = ParcelableLocationUtils.getHumanReadableString(location, 3)
textView.tag = location
} else if (addresses == null || addresses.isEmpty()) {
val tag = textView.tag
if (tag is Address) {
textView.spannable = tag.locality
} else {
textView.setText(R.string.label_location_your_coarse_location)
textView.tag = NoAddress()
}
} else {
val address = addresses[0]
textView.spannable = address.locality
textView.tag = address
}
} else {
textView.setText(R.string.no_location)
}
}
internal class NoAddress
}
private class ComposeEnterListener(private val activity: ComposeActivity?) : EnterListener {
override fun shouldCallListener(): Boolean {

View File

@ -23,10 +23,6 @@ import android.os.Bundle
import org.mariotaku.twidere.activity.BaseActivity
import org.mariotaku.twidere.util.TaskServiceRunner
/**
* Created by mariotaku on 2017/8/22.
*/
class ToggleRefreshActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@ -19,14 +19,12 @@
package org.mariotaku.twidere.alias
import org.mariotaku.microblog.library.mastodon.model.Notification
import org.mariotaku.microblog.library.mastodon.model.Status
import org.mariotaku.microblog.library.mastodon.model.StatusUpdate
import org.mariotaku.microblog.library.mastodon.model.TimelineOption
/**
* Created by mariotaku on 2017/4/21.
*/
typealias MastodonStatus = Status
typealias MastodonTimelineOption = TimelineOption
typealias MastodonStatusUpdate = StatusUpdate
typealias MastodonStatusUpdate = StatusUpdate
typealias MastodonNotification = Notification

View File

@ -0,0 +1,48 @@
/*
* 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.data.fetcher
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.mastodon.Mastodon
import org.mariotaku.microblog.library.mastodon.model.LinkHeaderList
import org.mariotaku.microblog.library.twitter.model.Activity
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.Status
import org.mariotaku.twidere.alias.MastodonNotification
import org.mariotaku.twidere.exception.APINotSupportedException
import org.mariotaku.twidere.model.AccountDetails
interface ActivitiesFetcher {
fun forTwitterOfficial(account: AccountDetails, twitter: MicroBlog, paging: Paging): List<Activity>
= throw APINotSupportedException(account.type)
fun forTwitter(account: AccountDetails, twitter: MicroBlog, paging: Paging): List<Status>
= throw APINotSupportedException(account.type)
fun forStatusNet(account: AccountDetails, statusNet: MicroBlog, paging: Paging): List<Status>
= throw APINotSupportedException(account.type)
fun forFanfou(account: AccountDetails, fanfou: MicroBlog, paging: Paging): List<Status>
= throw APINotSupportedException(account.type)
fun forMastodon(account: AccountDetails, mastodon: Mastodon, paging: Paging): LinkHeaderList<MastodonNotification>
= throw APINotSupportedException(account.type)
}

View File

@ -0,0 +1,52 @@
/*
* 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.data.fetcher.activities
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.mastodon.Mastodon
import org.mariotaku.microblog.library.mastodon.model.LinkHeaderList
import org.mariotaku.microblog.library.twitter.model.Activity
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.Status
import org.mariotaku.twidere.alias.MastodonNotification
import org.mariotaku.twidere.data.fetcher.ActivitiesFetcher
import org.mariotaku.twidere.model.AccountDetails
class ActivitiesAboutMeFetcher : ActivitiesFetcher {
override fun forTwitterOfficial(account: AccountDetails, twitter: MicroBlog, paging: Paging): List<Activity> {
return twitter.getActivitiesAboutMe(paging)
}
override fun forTwitter(account: AccountDetails, twitter: MicroBlog, paging: Paging): List<Status> {
return twitter.getMentionsTimeline(paging)
}
override fun forStatusNet(account: AccountDetails, statusNet: MicroBlog, paging: Paging): List<Status> {
return statusNet.getMentionsTimeline(paging)
}
override fun forFanfou(account: AccountDetails, fanfou: MicroBlog, paging: Paging): List<Status> {
return fanfou.getMentions(paging)
}
override fun forMastodon(account: AccountDetails, mastodon: Mastodon, paging: Paging): LinkHeaderList<MastodonNotification> {
return mastodon.getNotifications(paging)
}
}

View File

@ -1,39 +1,26 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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.task.cache
package org.mariotaku.twidere.data.syncher
import android.content.Context
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
class CacheUsersStatusesTask(
private val context: Context,
private val accountKey: UserKey,
private val accountType: String,
private val statuses: List<ParcelableStatus>
) : AbstractTask<Any?, Unit?, Unit?>() {
override fun doLongOperation(params: Any?) {
}
interface TimelinePositionSyncher {
fun get(accountKeys: Array<UserKey>)
}

View File

@ -9,9 +9,6 @@ import org.mariotaku.twidere.util.JsonSerializer
import org.mariotaku.twidere.util.filter.FiltersSubscriptionProvider
import org.mariotaku.twidere.util.filter.LocalFiltersSubscriptionProvider
/**
* Created by mariotaku on 2017/1/9.
*/
fun FiltersSubscription.instantiateComponent(context: Context): FiltersSubscriptionProvider? {
val component = this.component ?: return null

View File

@ -30,7 +30,7 @@ import org.mariotaku.twidere.model.util.ParcelableCardEntityUtils
import java.text.ParseException
import java.util.*
fun CardEntity.toParcelable(accountKey: UserKey, accountType: String): ParcelableCardEntity? {
fun CardEntity.toParcelable(accountKey: UserKey, accountType: String): ParcelableCardEntity {
val obj = ParcelableCardEntity()
obj.name = name
obj.url = url

View File

@ -283,7 +283,7 @@ abstract class AbsContentRecyclerViewFragment<A : LoadMoreSupportAdapter<Recycle
val progressCircleDiameter = swipeLayout.progressCircleDiameter
if (progressCircleDiameter == 0) return
val progressViewStart = 0 - progressCircleDiameter
val progressViewEnd = insets.top + resources.getDimensionPixelSize(R.dimen.element_spacing_normal)
val progressViewEnd = insets.top + resources.getDimensionPixelSize(R.dimen.element_spacing_large)
swipeLayout.setProgressViewOffset(false, progressViewStart, progressViewEnd)
}

View File

@ -399,8 +399,7 @@ class UserProfileEditorFragment : BaseFragment(), OnSizeChangedListener,
val context = this.callback?.context ?: return@promiseOnUi
val (user, account) = result
val task = UpdateAccountInfoTask(context)
task.params = Pair(account, user)
TaskStarter.execute(task)
task.toPromise(Pair(account, user))
} and callback.executeAfterFragmentResumed { fragment ->
fragment.childFragmentManager.dismissDialogFragment(DIALOG_FRAGMENT_TAG)
fragment.activity.finish()

View File

@ -18,8 +18,9 @@ import android.widget.ListView
import android.widget.TextView
import com.rengwuxian.materialedittext.MaterialEditText
import kotlinx.android.synthetic.main.layout_list_with_empty_view.*
import nl.komponents.kovenant.combine.and
import nl.komponents.kovenant.ui.alwaysUi
import okhttp3.HttpUrl
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.ktextension.*
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.sqliteqb.library.Expression
@ -35,7 +36,6 @@ import org.mariotaku.twidere.extension.util.isAdvancedFiltersEnabled
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.BaseFragment
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
import org.mariotaku.twidere.fragment.ProgressDialogFragment
import org.mariotaku.twidere.model.FiltersSubscription
import org.mariotaku.twidere.model.analyzer.PurchaseFinished
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
@ -44,7 +44,6 @@ import org.mariotaku.twidere.util.Analyzer
import org.mariotaku.twidere.util.content.ContentResolverUtils
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
import org.mariotaku.twidere.util.view.SimpleTextWatcher
import java.lang.ref.WeakReference
/**
@ -134,16 +133,9 @@ class FiltersSubscriptionsFragment : BaseFragment(), LoaderManager.LoaderCallbac
return true
}
R.id.refresh -> {
executeAfterFragmentResumed { fragment ->
ProgressDialogFragment.show(fragment.childFragmentManager, FRAGMENT_TAG_RREFRESH_FILTERS)
val task = RefreshFiltersSubscriptionsTask(fragment.context)
val fragmentRef = WeakReference(fragment)
task.callback = {
fragmentRef.get()?.executeAfterFragmentResumed { fragment ->
fragment.fragmentManager.dismissDialogFragment(FRAGMENT_TAG_RREFRESH_FILTERS)
}
}
TaskStarter.execute(task)
val weakThis by weak(this)
showProgressDialog(FRAGMENT_TAG_RREFRESH_FILTERS) and RefreshFiltersSubscriptionsTask(context).toPromise(Unit).alwaysUi {
weakThis?.fragmentManager?.dismissDialogFragment(FRAGMENT_TAG_RREFRESH_FILTERS)
}
return true
}

View File

@ -44,6 +44,9 @@ import kotlinx.android.synthetic.main.activity_premium_dashboard.*
import kotlinx.android.synthetic.main.fragment_messages_conversation.*
import kotlinx.android.synthetic.main.fragment_messages_conversation.view.*
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.*
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.alwaysUi
import nl.komponents.kovenant.ui.promiseOnUi
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
@ -79,14 +82,10 @@ import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.service.LengthyOperationsService
import org.mariotaku.twidere.task.compose.AbsAddMediaTask
import org.mariotaku.twidere.task.compose.AbsDeleteMediaTask
import org.mariotaku.twidere.task.twitter.message.DestroyMessageTask
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.task.twitter.message.MarkMessageReadTask
import org.mariotaku.twidere.util.ClipboardUtils
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.PreviewGridItemDecoration
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.view.ExtendedRecyclerView
import org.mariotaku.twidere.view.holder.compose.MediaPreviewViewHolder
import java.lang.ref.WeakReference
@ -148,9 +147,7 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
mediaPreviewAdapter.listener = object : MediaPreviewAdapter.Listener {
override fun onRemoveClick(position: Int, holder: MediaPreviewViewHolder) {
val task = DeleteMediaTask(this@MessagesConversationFragment,
arrayOf(mediaPreviewAdapter.getItem(position)))
TaskStarter.execute(task)
deleteMedia(mediaPreviewAdapter.getItem(position))
}
override fun onEditClick(position: Int, holder: MediaPreviewViewHolder) {
@ -565,27 +562,19 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
}
internal class DeleteMediaTask(
fragment: MessagesConversationFragment,
val media: Array<ParcelableMediaUpdate>
) : AbsDeleteMediaTask<MessagesConversationFragment>(fragment.context,
media.mapToArray { Uri.parse(it.uri) }) {
init {
callback = fragment
private fun deleteMedia(vararg media: ParcelableMediaUpdate) {
val weakThis by weak(this)
promiseOnUi {
weakThis?.setProgressVisible(true)
}.then {
val context = weakThis?.context ?: throw InterruptedException()
media.forEach {
Utils.deleteMedia(context, Uri.parse(it.uri))
}
}.alwaysUi {
weakThis?.setProgressVisible(false)
weakThis?.removeMedia(media.toList())
}
override fun afterExecute(callback: MessagesConversationFragment?, results: BooleanArray) {
if (callback == null) return
callback.setProgressVisible(false)
callback.removeMedia(media.toList())
}
override fun beforeExecute() {
val fragment = callback ?: return
fragment.setProgressVisible(true)
}
}
internal class ConversationLoader(

View File

@ -25,7 +25,6 @@ import android.os.Bundle
import android.support.v4.content.FixedAsyncTaskLoader
import android.text.TextUtils
import android.util.Log
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.ktextension.set
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.MicroBlog
@ -202,8 +201,7 @@ class ParcelableUserLoader(
val account = data.extras.getParcelable<AccountDetails>(EXTRA_ACCOUNT)
if (account != null) {
val task = UpdateAccountInfoTask(context)
task.params = Pair(account, user)
TaskStarter.execute(task)
task.toPromise(Pair(account, user))
}
}
}

View File

@ -19,26 +19,23 @@
package org.mariotaku.twidere.service
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.job.JobParameters
import android.app.job.JobService
import android.os.Build
import android.util.Log
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.ktextension.deadline
import org.mariotaku.twidere.annotation.AutoRefreshType
import org.mariotaku.twidere.constant.autoRefreshCompatibilityModeKey
import org.mariotaku.twidere.util.Analyzer
import org.mariotaku.twidere.util.TaskServiceRunner
import org.mariotaku.twidere.util.dagger.GeneralComponent
import org.mariotaku.twidere.util.support.JobServiceSupport
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by mariotaku on 14/12/12.
*/
@SuppressLint("Registered")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class JobTaskService : JobService() {
@ -49,22 +46,19 @@ class JobTaskService : JobService() {
override fun onCreate() {
super.onCreate()
Log.d(LOGTAG, "JobTaskService started")
GeneralComponent.get(this).inject(this)
}
override fun onDestroy() {
Log.d(LOGTAG, "JobTaskService destroyed")
super.onDestroy()
}
override fun onStartJob(params: JobParameters): Boolean {
Log.d(LOGTAG, "JobTaskService received job $params")
if (kPreferences[autoRefreshCompatibilityModeKey]) return false
val action = getTaskAction(params.jobId) ?: return false
return taskServiceRunner.runTask(action) {
this.jobFinished(params, false)
val promise = taskServiceRunner.createPromise(action) ?: return false
promise.deadline(3, TimeUnit.MINUTES).successUi {
jobFinished(params, false)
}.failUi {
jobFinished(params, false)
}
return true
}
override fun onStopJob(params: JobParameters): Boolean {
@ -116,5 +110,7 @@ class JobTaskService : JobService() {
JOB_ID_SYNC_USER_COLORS -> TaskServiceRunner.ACTION_SYNC_USER_COLORS
else -> null
}
}
}

View File

@ -23,7 +23,10 @@ import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.deadline
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.annotation.AutoRefreshType
import org.mariotaku.twidere.constant.autoRefreshCompatibilityModeKey
@ -31,6 +34,7 @@ import org.mariotaku.twidere.util.TaskServiceRunner.Companion.ACTION_REFRESH_DIR
import org.mariotaku.twidere.util.TaskServiceRunner.Companion.ACTION_REFRESH_HOME_TIMELINE
import org.mariotaku.twidere.util.TaskServiceRunner.Companion.ACTION_REFRESH_NOTIFICATIONS
import org.mariotaku.twidere.util.dagger.GeneralComponent
import java.util.concurrent.TimeUnit
class LegacyTaskService : BaseService() {
@ -50,14 +54,22 @@ class LegacyTaskService : BaseService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(LOGTAG, "LegacyTaskService received $intent")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
!preferences[autoRefreshCompatibilityModeKey]) return START_NOT_STICKY
val action = intent?.action ?: return START_NOT_STICKY
taskServiceRunner.runTask(action) {
!preferences[autoRefreshCompatibilityModeKey]) return serviceNotHandled(startId)
val action = intent?.action ?: return serviceNotHandled(startId)
val promise = taskServiceRunner.createPromise(action) ?: return serviceNotHandled(startId)
promise.deadline(3, TimeUnit.MINUTES).successUi {
stopSelfResult(startId)
}.failUi {
stopSelfResult(startId)
}
return START_NOT_STICKY
}
private fun serviceNotHandled(startId: Int): Int {
stopSelfResult(startId)
return START_NOT_STICKY
}
companion object {

View File

@ -21,10 +21,6 @@ import org.mariotaku.twidere.util.sync.SyncPreferences
import org.mariotaku.twidere.util.sync.TimelineSyncManager
import javax.inject.Inject
/**
* Created by mariotaku on 2017/2/7.
*/
abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context) : AbstractTask<Params, Result, Callback>() {
@Inject
@ -73,4 +69,5 @@ abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context)
@Suppress("UNCHECKED_CAST")
GeneralComponent.get(context).inject(this as BaseAbstractTask<Any, Any, Any>)
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.task
import nl.komponents.kovenant.Promise
interface PromiseTask<in P, out T : Any> {
fun toPromise(param: P): Promise<T, Exception>
}

View File

@ -5,44 +5,46 @@ import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.support.v4.util.LongSparseArray
import android.text.TextUtils
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.library.objectcursor.ObjectCursor
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.TwidereConstants.ACCOUNT_TYPE
import org.mariotaku.twidere.extension.model.setAccountKey
import org.mariotaku.twidere.extension.model.setAccountUser
import org.mariotaku.twidere.extension.queryReference
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableUser
import org.mariotaku.twidere.model.Tab
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.*
import java.io.IOException
import org.mariotaku.twidere.util.updateItems
/**
* Created by mariotaku on 16/3/8.
*/
class UpdateAccountInfoTask(
private val context: Context
) : AbstractTask<Pair<AccountDetails, ParcelableUser>, Unit, Unit?>() {
) : PromiseTask<Pair<AccountDetails, ParcelableUser>, Unit> {
override fun doLongOperation(params: Pair<AccountDetails, ParcelableUser>) {
override fun toPromise(param: Pair<AccountDetails, ParcelableUser>): Promise<Unit, Exception> {
val resolver = context.contentResolver
val (details, user) = params
val (details, user) = param
if (user.is_cache) {
return
return Promise.ofSuccess(Unit)
}
if (!user.key.maybeEquals(user.account_key)) {
return
return Promise.ofSuccess(Unit)
}
return task {
val am = AccountManager.get(context)
val account = Account(details.account.name, ACCOUNT_TYPE)
account.setAccountUser(am, user)
account.setAccountKey(am, user.key)
}.then {
updateTimeline(user, details, resolver)
}.then {
updateTabs(resolver, user.key)
}
}
val am = AccountManager.get(context)
val account = Account(details.account.name, ACCOUNT_TYPE)
account.setAccountUser(am, user)
account.setAccountKey(am, user.key)
private fun updateTimeline(user: ParcelableUser, details: AccountDetails, resolver: ContentResolver) {
val accountKeyValues = ContentValues().apply {
put(AccountSupportColumns.ACCOUNT_KEY, user.key.toString())
}
@ -54,38 +56,19 @@ class UpdateAccountInfoTask(
resolver.update(Messages.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
resolver.update(Messages.Conversations.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
resolver.update(CachedRelationships.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
updateTabs(resolver, user.key)
}
private fun updateTabs(resolver: ContentResolver, accountKey: UserKey) {
resolver.queryReference(Tabs.CONTENT_URI, Tabs.COLUMNS, null, null,
null)?.use { (tabsCursor) ->
val indices = ObjectCursor.indicesFrom(tabsCursor, Tab::class.java)
val creator = ObjectCursor.valuesCreatorFrom(Tab::class.java)
tabsCursor.moveToFirst()
val values = LongSparseArray<ContentValues>()
try {
while (!tabsCursor.isAfterLast) {
val tab = indices.newObject(tabsCursor)
val arguments = tab.arguments
if (arguments != null) {
val accountId = arguments.accountId
val keys = arguments.accountKeys
if (TextUtils.equals(accountKey.id, accountId) && keys == null) {
arguments.accountKeys = arrayOf(accountKey)
values.put(tab.id, creator.create(tab))
}
}
tabsCursor.moveToNext()
resolver.updateItems(Tabs.CONTENT_URI, Tabs.COLUMNS, null, null, Tab::class.java) { tab ->
val arguments = tab.arguments
if (arguments != null) {
val accountId = arguments.accountId
val keys = arguments.accountKeys
if (accountKey.id == accountId && keys == null) {
arguments.accountKeys = arrayOf(accountKey)
}
} catch (e: IOException) {
// Ignore
}
for (i in 0 until values.size()) {
val where = Expression.equals(Tabs._ID, values.keyAt(i)).sql
resolver.update(Tabs.CONTENT_URI, values.valueAt(i), where, null)
}
return@updateItems tab
}
}
}

View File

@ -3,46 +3,39 @@ package org.mariotaku.twidere.task.filter
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import org.mariotaku.abstask.library.AbstractTask
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.task
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.extension.model.instantiateComponent
import org.mariotaku.twidere.extension.queryReference
import org.mariotaku.twidere.extension.queryAll
import org.mariotaku.twidere.model.FiltersData
import org.mariotaku.twidere.model.FiltersSubscription
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
import org.mariotaku.twidere.task.PromiseTask
import org.mariotaku.twidere.util.DebugLog
import org.mariotaku.twidere.util.content.ContentResolverUtils
import org.mariotaku.twidere.util.sync.LOGTAG_SYNC
import java.io.IOException
import java.util.*
class RefreshFiltersSubscriptionsTask(val context: Context) : AbstractTask<Unit?, Boolean, (Boolean) -> Unit>() {
override fun doLongOperation(param: Unit?): Boolean {
class RefreshFiltersSubscriptionsTask(val context: Context) : PromiseTask<Unit, Unit> {
override fun toPromise(param: Unit): Promise<Unit, Exception> = task {
val resolver = context.contentResolver
val sourceIds = ArrayList<Long>()
resolver.queryReference(Filters.Subscriptions.CONTENT_URI, Filters.Subscriptions.COLUMNS,
null, null, null)?.use { (cursor) ->
val indices = ObjectCursor.indicesFrom(cursor, FiltersSubscription::class.java)
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val subscription = indices.newObject(cursor)
sourceIds.add(subscription.id)
val component = subscription.instantiateComponent(context)
if (component != null) {
try {
if (component.fetchFilters()) {
updateUserItems(resolver, component.users, subscription.id)
updateBaseItems(resolver, component.keywords, Filters.Keywords.CONTENT_URI, subscription.id)
updateBaseItems(resolver, component.links, Filters.Links.CONTENT_URI, subscription.id)
updateBaseItems(resolver, component.sources, Filters.Sources.CONTENT_URI, subscription.id)
}
} catch (e: IOException) {
DebugLog.w(LOGTAG_SYNC, "Unable to refresh filters", e)
}
resolver.queryAll(Filters.Subscriptions.CONTENT_URI, Filters.Subscriptions.COLUMNS,
null, null, cls = FiltersSubscription::class.java).forEach { subscription ->
sourceIds.add(subscription.id)
val component = subscription.instantiateComponent(context) ?: return@forEach
try {
if (component.fetchFilters()) {
updateUserItems(resolver, component.users, subscription.id)
updateBaseItems(resolver, component.keywords, Filters.Keywords.CONTENT_URI, subscription.id)
updateBaseItems(resolver, component.links, Filters.Links.CONTENT_URI, subscription.id)
updateBaseItems(resolver, component.sources, Filters.Sources.CONTENT_URI, subscription.id)
}
cursor.moveToNext()
} catch (e: IOException) {
DebugLog.w(LOGTAG_SYNC, "Unable to refresh filters", e)
}
}
// Delete 'orphaned' filter items with `sourceId` > 0
@ -55,16 +48,7 @@ class RefreshFiltersSubscriptionsTask(val context: Context) : AbstractTask<Unit?
true, sourceIds, extraWhere, null)
ContentResolverUtils.bulkDelete(resolver, Filters.Links.CONTENT_URI, Filters.Links.SOURCE,
true, sourceIds, extraWhere, null)
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
return false
}
return true
}
override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Boolean) {
callback?.invoke(result)
Thread.sleep(1000)
}
private fun updateUserItems(resolver: ContentResolver, items: List<FiltersData.UserItem>?, sourceId: Long) {

View File

@ -29,9 +29,6 @@ import org.mariotaku.twidere.util.JsonSerializer
import java.io.IOException
/**
* Created by mariotaku on 2017/8/22.
*/
class RefreshLaunchPresentationsTask(context: Context) : BaseAbstractTask<Unit?, Boolean, (Boolean) -> Unit>(context) {
override fun doLongOperation(params: Unit?): Boolean {
val builder = HttpRequest.Builder()
@ -54,6 +51,10 @@ class RefreshLaunchPresentationsTask(context: Context) : BaseAbstractTask<Unit?,
}
}
override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Boolean) {
callback?.invoke(result)
}
companion object {
const val JSON_CACHE_KEY = "launch_presentations"
}

View File

@ -25,11 +25,9 @@ import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.GroupTimelineFetcher
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.GroupTimelineContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetGroupTimelineTask(context: Context) : GetStatusesTask<GroupTimelineContentRefreshParam>(context) {
@ -44,7 +42,4 @@ class GetGroupTimelineTask(context: Context) : GetStatusesTask<GroupTimelineCont
return GroupTimelineFetcher(params?.id, params?.name)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -22,14 +22,10 @@ package org.mariotaku.twidere.task.statuses
import android.content.Context
import android.net.Uri
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.data.fetcher.HomeTimelineFetcher
import org.mariotaku.twidere.fragment.timeline.HomeTimelineFragment
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.ContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetHomeTimelineTask(context: Context) : GetStatusesTask<ContentRefreshParam>(context) {
@ -41,8 +37,4 @@ class GetHomeTimelineTask(context: Context) : GetStatusesTask<ContentRefreshPara
override fun getStatusesFetcher(params: ContentRefreshParam?) = HomeTimelineFetcher()
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
val tag = HomeTimelineFragment.getTimelineSyncTag(accountKeys)
manager.fetchSingle(ReadPositionTag.HOME_TIMELINE, tag)
}
}

View File

@ -25,11 +25,9 @@ import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.ListTimelineFetcher
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.ListTimelineContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetListTimelineTask(context: Context) : GetStatusesTask<ListTimelineContentRefreshParam>(context) {
@ -44,7 +42,4 @@ class GetListTimelineTask(context: Context) : GetStatusesTask<ListTimelineConten
return ListTimelineFetcher(params?.id, params?.slug, params?.ownerId, params?.ownerScreenName)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -25,11 +25,9 @@ import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.MediaSearchTimelineFetcher
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.SearchTimelineContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetMediaSearchTimelineTask(context: Context) : GetStatusesTask<SearchTimelineContentRefreshParam>(context) {
@ -44,7 +42,4 @@ class GetMediaSearchTimelineTask(context: Context) : GetStatusesTask<SearchTimel
return MediaSearchTimelineFetcher(params?.query)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -22,14 +22,10 @@ package org.mariotaku.twidere.task.statuses
import android.content.Context
import android.net.Uri
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.data.fetcher.NetworkPublicTimelineFetcher
import org.mariotaku.twidere.fragment.timeline.NetworkPublicTimelineFragment
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.ContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetNetworkPublicTimelineTask(context: Context) : GetStatusesTask<ContentRefreshParam>(context) {
@ -41,8 +37,4 @@ class GetNetworkPublicTimelineTask(context: Context) : GetStatusesTask<ContentRe
override fun getStatusesFetcher(params: ContentRefreshParam?) = NetworkPublicTimelineFetcher()
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
val tag = NetworkPublicTimelineFragment.getTimelineSyncTag(accountKeys)
manager.fetchSingle(ReadPositionTag.NETWORK_PUBLIC_TIMELINE, tag)
}
}

View File

@ -22,14 +22,10 @@ package org.mariotaku.twidere.task.statuses
import android.content.Context
import android.net.Uri
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.data.fetcher.PublicTimelineFetcher
import org.mariotaku.twidere.fragment.timeline.PublicTimelineFragment
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.ContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetPublicTimelineTask(context: Context) : GetStatusesTask<ContentRefreshParam>(context) {
@ -41,8 +37,4 @@ class GetPublicTimelineTask(context: Context) : GetStatusesTask<ContentRefreshPa
override fun getStatusesFetcher(params: ContentRefreshParam?) = PublicTimelineFetcher()
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
val tag = PublicTimelineFragment.getTimelineSyncTag(accountKeys)
manager.fetchSingle(ReadPositionTag.PUBLIC_TIMELINE, tag)
}
}

View File

@ -25,11 +25,9 @@ import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.SearchTimelineFetcher
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.SearchTimelineContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetSearchTimelineTask(context: Context) : GetStatusesTask<SearchTimelineContentRefreshParam>(context) {
@ -44,7 +42,4 @@ class GetSearchTimelineTask(context: Context) : GetStatusesTask<SearchTimelineCo
return SearchTimelineFetcher(params?.query, params?.local ?: false)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -42,6 +42,7 @@ import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.constant.loadItemLimitKey
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.data.syncher.TimelinePositionSyncher
import org.mariotaku.twidere.exception.AccountNotFoundException
import org.mariotaku.twidere.extension.model.*
import org.mariotaku.twidere.extension.model.api.applyLoadLimit
@ -72,8 +73,6 @@ abstract class GetStatusesTask<P : ContentRefreshParam>(
) : BaseAbstractTask<P, List<Pair<GetTimelineResult<ParcelableStatus>?, Exception?>>,
(Boolean) -> Unit>(context) {
private val profileImageSize = context.getString(R.string.profile_image_size)
protected abstract val contentUri: Uri
@FilterScope
@ -81,7 +80,9 @@ abstract class GetStatusesTask<P : ContentRefreshParam>(
protected abstract val errorInfoKey: String
override fun doLongOperation(param: P): List<Pair<GetTimelineResult<ParcelableStatus>?, Exception?>> {
private val profileImageSize = context.getString(R.string.profile_image_size)
override final fun doLongOperation(param: P): List<Pair<GetTimelineResult<ParcelableStatus>?, Exception?>> {
if (param.shouldAbort) return emptyList()
val accountKeys = param.accountKeys.takeIf { it.isNotEmpty() } ?: return emptyList()
val loadItemLimit = preferences[loadItemLimitKey]
@ -210,7 +211,12 @@ abstract class GetStatusesTask<P : ContentRefreshParam>(
protected abstract fun getStatusesFetcher(params: P?): StatusesFetcher
protected abstract fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>)
protected open fun getPositionSyncher(manager: TimelineSyncManager): TimelinePositionSyncher? = null
private fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
val fetcher = getPositionSyncher(manager) ?: return
fetcher.get(accountKeys)
}
private fun extractMicroBlogUsers(timeline: List<Status>, account: AccountDetails): List<ParcelableUser> {
return timeline.flatMap { status ->
@ -319,13 +325,12 @@ abstract class GetStatusesTask<P : ContentRefreshParam>(
fun getPositionKey(timestamp: Long, sortId: Long, lastSortId: Long, sortDiff: Long,
position: Int, count: Int): Long {
if (sortDiff == 0L) return timestamp
val extraValue: Int
if (sortDiff > 0) {
val extraValue = if (sortDiff > 0) {
// descent sorted by time
extraValue = count - 1 - position
count - 1 - position
} else {
// ascent sorted by time
extraValue = position
position
}
return timestamp + (sortId - lastSortId) * (499 - count) / sortDiff + extraValue.toLong()
}

View File

@ -24,11 +24,9 @@ import android.net.Uri
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.UserFavoritesFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.UserRelatedContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetUserFavoritesTask(context: Context) : GetStatusesTask<UserRelatedContentRefreshParam>(context) {
@ -43,7 +41,4 @@ class GetUserFavoritesTask(context: Context) : GetStatusesTask<UserRelatedConten
return UserFavoritesFetcher(params?.userKey, params?.userScreenName)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -25,11 +25,9 @@ import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.data.fetcher.UserMediaTimelineFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.UserRelatedContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetUserMediaTimelineTask(context: Context) : GetStatusesTask<UserRelatedContentRefreshParam>(context) {
@ -44,7 +42,4 @@ class GetUserMediaTimelineTask(context: Context) : GetStatusesTask<UserRelatedCo
return UserMediaTimelineFetcher(params?.userKey, params?.userScreenName)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -24,11 +24,9 @@ import android.net.Uri
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.UserTimelineFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.UserRelatedContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetUserMentionsTimelineTask(context: Context) : GetStatusesTask<UserRelatedContentRefreshParam>(context) {
@ -43,7 +41,4 @@ class GetUserMentionsTimelineTask(context: Context) : GetStatusesTask<UserRelate
return UserTimelineFetcher(params?.userKey, params?.userScreenName, null)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -24,11 +24,9 @@ import android.net.Uri
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.data.fetcher.UserTimelineFetcher
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.refresh.UserRelatedContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
class GetUserTimelineTask(context: Context) : GetStatusesTask<UserRelatedContentRefreshParam>(context) {
@ -43,7 +41,4 @@ class GetUserTimelineTask(context: Context) : GetStatusesTask<UserRelatedContent
return UserTimelineFetcher(params?.userKey, params?.userScreenName, null)
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {
}
}

View File

@ -21,111 +21,25 @@ package org.mariotaku.twidere.task.twitter
import android.content.Context
import android.net.Uri
import org.mariotaku.ktextension.addTo
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.mastodon.Mastodon
import org.mariotaku.microblog.library.twitter.model.Activity
import org.mariotaku.microblog.library.twitter.model.InternalActivityCreator
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.twidere.R
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.extension.api.batchGetRelationships
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
import org.mariotaku.twidere.extension.model.api.microblog.toParcelable
import org.mariotaku.twidere.extension.model.extractFanfouHashtags
import org.mariotaku.twidere.extension.model.isOfficial
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.data.fetcher.ActivitiesFetcher
import org.mariotaku.twidere.data.fetcher.activities.ActivitiesAboutMeFetcher
import org.mariotaku.twidere.fragment.activities.InteractionsActivitiesFragment
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableActivity
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.task.GetTimelineResult
import org.mariotaku.twidere.model.refresh.ContentRefreshParam
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.sync.TimelineSyncManager
/**
* Created by mariotaku on 16/2/11.
*/
class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
override val errorInfoKey: String = ErrorInfoStore.KEY_INTERACTIONS
override val filterScopes: Int = FilterScope.INTERACTIONS
override val contentUri: Uri = Activities.AboutMe.CONTENT_URI
private val profileImageSize = context.getString(R.string.profile_image_size)
@Throws(MicroBlogException::class)
override fun getActivities(account: AccountDetails, paging: Paging): GetTimelineResult<ParcelableActivity> {
when (account.type) {
AccountType.MASTODON -> {
val mastodon = account.newMicroBlogInstance(context, Mastodon::class.java)
val notifications = mastodon.getNotifications(paging)
val userIds = notifications.flatMapTo(HashSet()) {
val mapResult = mutableSetOf<String>()
it?.account?.id?.addTo(mapResult)
it.status?.account?.id?.addTo(mapResult)
return@flatMapTo mapResult
}
val relationships = mastodon.batchGetRelationships(userIds)
val activities = notifications.mapNotNull {
val activity = it.toParcelable(account, relationships)
if (activity.action == Activity.Action.INVALID) return@mapNotNull null
return@mapNotNull activity
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, notifications.flatMapTo(HashSet()) { notification ->
notification.status?.tags?.map { it.name }.orEmpty()
})
}
AccountType.TWITTER -> {
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
if (account.isOfficial(context)) {
val timeline = microBlog.getActivitiesAboutMe(paging)
val activities = timeline.map {
it.toParcelable(account, profileImageSize = profileImageSize)
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, timeline.flatMapTo(HashSet()) { activity ->
val mapResult = mutableSetOf<String>()
activity.targetStatuses?.flatMapTo(mapResult) { status ->
status.entities?.hashtags?.map { it.text }.orEmpty()
}
activity.targetObjectStatuses?.flatMapTo(mapResult) { status ->
status.entities?.hashtags?.map { it.text }.orEmpty()
}
return@flatMapTo mapResult
})
}
}
AccountType.FANFOU -> {
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
val activities = microBlog.getMentions(paging).map {
InternalActivityCreator.status(it, account.key.id).toParcelable(account,
profileImageSize = profileImageSize)
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, activities.flatMap { it.extractFanfouHashtags() })
}
}
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
val timeline = microBlog.getMentionsTimeline(paging)
val activities = timeline.map {
InternalActivityCreator.status(it, account.key.id).toParcelable(account,
profileImageSize = profileImageSize)
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, timeline.flatMap {
it.entities?.hashtags?.map { it.text }.orEmpty()
})
override fun getActivitiesFetcher(params: ContentRefreshParam?): ActivitiesFetcher {
return ActivitiesAboutMeFetcher()
}
override fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>) {

View File

@ -6,23 +6,32 @@ import android.content.Context
import android.net.Uri
import android.support.annotation.UiThread
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.addTo
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.mastodon.Mastodon
import org.mariotaku.microblog.library.twitter.model.Activity
import org.mariotaku.microblog.library.twitter.model.InternalActivityCreator
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFY_CHANGE
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.constant.loadItemLimitKey
import org.mariotaku.twidere.data.fetcher.ActivitiesFetcher
import org.mariotaku.twidere.exception.AccountNotFoundException
import org.mariotaku.twidere.extension.model.getMaxId
import org.mariotaku.twidere.extension.model.getMaxSortId
import org.mariotaku.twidere.extension.model.getSinceId
import org.mariotaku.twidere.extension.api.batchGetRelationships
import org.mariotaku.twidere.extension.model.*
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
import org.mariotaku.twidere.extension.model.api.microblog.toParcelable
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableActivity
import org.mariotaku.twidere.model.refresh.ContentRefreshParam
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.GetActivitiesTaskEvent
import org.mariotaku.twidere.model.refresh.ContentRefreshParam
import org.mariotaku.twidere.model.task.GetTimelineResult
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
@ -37,9 +46,6 @@ import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.TimelineSyncManager
import java.util.*
/**
* Created by mariotaku on 16/1/4.
*/
abstract class GetActivitiesTask(
context: Context
) : BaseAbstractTask<ContentRefreshParam, List<Pair<GetTimelineResult<ParcelableActivity>?, Exception?>>,
@ -52,6 +58,8 @@ abstract class GetActivitiesTask(
protected abstract val contentUri: Uri
private val profileImageSize = context.getString(R.string.profile_image_size)
override fun doLongOperation(param: ContentRefreshParam): List<Pair<GetTimelineResult<ParcelableActivity>?, Exception?>> {
if (param.shouldAbort) return emptyList()
val accountKeys = param.accountKeys.takeIf { it.isNotEmpty() } ?: return emptyList()
@ -119,7 +127,91 @@ abstract class GetActivitiesTask(
}
@Throws(MicroBlogException::class)
protected abstract fun getActivities(account: AccountDetails, paging: Paging): GetTimelineResult<ParcelableActivity>
protected fun getActivities(account: AccountDetails, paging: Paging): GetTimelineResult<ParcelableActivity> {
val fetcher = getActivitiesFetcher(params)
when (account.type) {
AccountType.MASTODON -> {
val mastodon = account.newMicroBlogInstance(context, Mastodon::class.java)
val notifications = fetcher.forMastodon(account, mastodon, paging)
val userIds = notifications.flatMapTo(HashSet()) {
val mapResult = mutableSetOf<String>()
it?.account?.id?.addTo(mapResult)
it.status?.account?.id?.addTo(mapResult)
return@flatMapTo mapResult
}
val relationships = mastodon.batchGetRelationships(userIds)
val activities = notifications.mapNotNull {
val activity = it.toParcelable(account, relationships)
if (activity.action == Activity.Action.INVALID) return@mapNotNull null
return@mapNotNull activity
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, notifications.flatMapTo(HashSet()) { notification ->
notification.status?.tags?.map { it.name }.orEmpty()
})
}
AccountType.TWITTER -> {
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
if (account.isOfficial(context)) {
val timeline = fetcher.forTwitterOfficial(account, microBlog, paging)
val activities = timeline.map {
it.toParcelable(account, profileImageSize = profileImageSize)
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, timeline.flatMapTo(HashSet()) { activity ->
val mapResult = mutableSetOf<String>()
activity.targetStatuses?.flatMapTo(mapResult) { status ->
status.entities?.hashtags?.map { it.text }.orEmpty()
}
activity.targetObjectStatuses?.flatMapTo(mapResult) { status ->
status.entities?.hashtags?.map { it.text }.orEmpty()
}
return@flatMapTo mapResult
})
} else {
val timeline = fetcher.forTwitter(account, microBlog, paging)
val activities = timeline.map {
InternalActivityCreator.status(it, account.key.id).toParcelable(account,
profileImageSize = profileImageSize)
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, timeline.flatMap {
it.entities?.hashtags?.map { it.text }.orEmpty()
})
}
}
AccountType.FANFOU -> {
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
val activities = fetcher.forFanfou(account, microBlog, paging).map {
InternalActivityCreator.status(it, account.key.id).toParcelable(account,
profileImageSize = profileImageSize)
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, activities.flatMap { it.extractFanfouHashtags() })
}
AccountType.STATUSNET -> {
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
val timeline = fetcher.forStatusNet(account, microBlog, paging)
val activities = timeline.map {
InternalActivityCreator.status(it, account.key.id).toParcelable(account,
profileImageSize = profileImageSize)
}
return GetTimelineResult(account, activities, activities.flatMap {
it.sources?.toList().orEmpty()
}, timeline.flatMap {
it.entities?.hashtags?.map { it.text }.orEmpty()
})
}
else -> throw UnsupportedOperationException()
}
}
protected abstract fun getActivitiesFetcher(params: ContentRefreshParam?): ActivitiesFetcher
protected abstract fun syncFetchReadPosition(manager: TimelineSyncManager, accountKeys: Array<UserKey>)

View File

@ -20,41 +20,36 @@
package org.mariotaku.twidere.task.twitter
import android.content.Context
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.microblog.library.MicroBlogException
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all
import nl.komponents.kovenant.task
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.model.SingleResponse
import org.mariotaku.twidere.exception.NoAccountException
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches
import org.mariotaku.twidere.task.PromiseTask
import org.mariotaku.twidere.util.ContentValuesCreator
import org.mariotaku.twidere.util.DebugLog
import org.mariotaku.twidere.util.MicroBlogAPIFactory
import org.mariotaku.twidere.util.content.ContentResolverUtils
/**
* Created by mariotaku on 16/2/13.
*/
class GetSavedSearchesTask(
private val context: Context
) : AbstractTask<Array<UserKey>, SingleResponse<Unit>, Any?>() {
) : PromiseTask<Array<UserKey>, List<Unit>> {
override fun doLongOperation(params: Array<UserKey>): SingleResponse<Unit> {
val cr = context.contentResolver
for (accountKey in params) {
val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?: continue
try {
val searches = twitter.savedSearches
val values = ContentValuesCreator.createSavedSearches(searches,
accountKey)
val where = Expression.equalsArgs(SavedSearches.ACCOUNT_KEY)
val whereArgs = arrayOf(accountKey.toString())
cr.delete(SavedSearches.CONTENT_URI, where.sql, whereArgs)
ContentResolverUtils.bulkInsert(cr, SavedSearches.CONTENT_URI, values)
} catch (e: MicroBlogException) {
DebugLog.w(LOGTAG, tr = e)
}
override fun toPromise(param: Array<UserKey>): Promise<List<Unit>, Exception> = all(param.map { accountKey ->
return@map task {
val cr = context.contentResolver
val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?:
throw NoAccountException()
val searches = twitter.savedSearches
val values = ContentValuesCreator.createSavedSearches(searches,
accountKey)
val where = Expression.equalsArgs(SavedSearches.ACCOUNT_KEY)
val whereArgs = arrayOf(accountKey.toString())
cr.delete(SavedSearches.CONTENT_URI, where.sql, whereArgs)
ContentResolverUtils.bulkInsert(cr, SavedSearches.CONTENT_URI, values)
return@task
}
return SingleResponse(Unit)
}
}, cancelOthersOnError = false)
}

View File

@ -25,6 +25,7 @@ import android.net.Uri
import android.widget.Toast
import com.squareup.otto.Bus
import com.squareup.otto.Subscribe
import nl.komponents.kovenant.Promise
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.mapToArray
@ -245,10 +246,9 @@ class AsyncTwitterWrapper(
TaskStarter.execute(task)
}
fun getSavedSearchesAsync(accountKeys: Array<UserKey>) {
fun getSavedSearchesAsync(accountKeys: Array<UserKey>): Promise<List<Unit>, Exception> {
val task = GetSavedSearchesTask(context)
task.params = accountKeys
TaskStarter.execute(task)
return task.toPromise(accountKeys)
}
fun getSendingDraftIds(): LongArray {

View File

@ -0,0 +1,24 @@
/*
* 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
/**
* Created by mariotaku on 2017/11/6.
*/

View File

@ -17,25 +17,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.task.compose
package org.mariotaku.twidere.util
import android.content.Context
import android.net.Uri
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.twidere.util.Utils
import java.lang.ref.WeakReference
import org.mariotaku.abstask.library.TaskEngine
open class AbsDeleteMediaTask<Callback>(
context: Context,
val sources: Array<Uri>
) : AbstractTask<Unit, BooleanArray, Callback>() {
private val contextRef = WeakReference(context)
val context: Context? get() = contextRef.get()
override fun doLongOperation(params: Unit?): BooleanArray {
val context = contextRef.get() ?: return kotlin.BooleanArray(sources.size) { false }
return BooleanArray(sources.size) { Utils.deleteMedia(context, sources[it]) }
object PromiseTaskEngine : TaskEngine() {
override fun <Params : Any?, Result : Any?, Callback : Any?> execute(t: AbstractTask<Params, Result, Callback>?) {
val dispatcher = getDispatcher<Params, Result, Callback>(t)
task {
dispatcher.invokeExecute()
}.successUi {
dispatcher.invokeAfterExecute(it)
}
}
}
}

View File

@ -3,14 +3,13 @@ package org.mariotaku.twidere.util
import android.content.Context
import android.content.SharedPreferences
import android.support.annotation.StringDef
import android.util.Log
import com.squareup.otto.Bus
import nl.komponents.kovenant.Promise
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.mapToArray
import org.mariotaku.ktextension.toNulls
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.constant.IntentConstants.INTENT_PACKAGE_PREFIX
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.constant.stopAutoRefreshWhenBatteryLowKey
@ -26,10 +25,7 @@ import org.mariotaku.twidere.task.filter.RefreshLaunchPresentationsTask
import org.mariotaku.twidere.task.statuses.GetHomeTimelineTask
import org.mariotaku.twidere.task.twitter.GetActivitiesAboutMeTask
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
/**
* Created by mariotaku on 2017/1/6.
*/
import java.util.concurrent.TimeUnit
class TaskServiceRunner(
val context: Context,
@ -38,8 +34,17 @@ class TaskServiceRunner(
val bus: Bus
) {
fun runTask(@Action action: String, callback: (Boolean) -> Unit): Boolean {
Log.d(LOGTAG, "TaskServiceRunner run task $action")
fun createPromise(action: String): Promise<*, Exception>? {
return when (action) {
ACTION_REFRESH_LAUNCH_PRESENTATIONS -> {
RefreshFiltersSubscriptionsTask(context).toPromise(Unit)
}
else -> null
}
}
fun runTask(@Action action: String, timeout: Long = 0, unit: TimeUnit? = null, callback: (Boolean) -> Unit): Boolean {
DebugLog.d(msg = "TaskServiceRunner run task $action")
when (action) {
ACTION_REFRESH_HOME_TIMELINE, ACTION_REFRESH_NOTIFICATIONS,
ACTION_REFRESH_DIRECT_MESSAGES, ACTION_REFRESH_FILTERS_SUBSCRIPTIONS,
@ -51,7 +56,7 @@ class TaskServiceRunner(
}
ACTION_SYNC_DRAFTS, ACTION_SYNC_FILTERS, ACTION_SYNC_USER_NICKNAMES, ACTION_SYNC_USER_COLORS -> {
val runner = preferences[dataSyncProviderInfoKey]?.newSyncTaskRunner(context) ?: return false
return runner.runTask(action, callback)
return runner.runTask(action, timeout, unit, callback)
}
}
return false
@ -96,9 +101,6 @@ class TaskServiceRunner(
}
return task
}
ACTION_REFRESH_FILTERS_SUBSCRIPTIONS -> {
return RefreshFiltersSubscriptionsTask(context)
}
ACTION_REFRESH_LAUNCH_PRESENTATIONS -> {
return RefreshLaunchPresentationsTask(context)
}
@ -151,11 +153,12 @@ class TaskServiceRunner(
const val ACTION_SYNC_FILTERS = INTENT_PACKAGE_PREFIX + "SYNC_FILTERS"
@Action
const val ACTION_SYNC_USER_NICKNAMES = INTENT_PACKAGE_PREFIX + "SYNC_USER_NICKNAMES"
@Action
const val ACTION_SYNC_USER_COLORS = INTENT_PACKAGE_PREFIX + "SYNC_USER_COLORS"
val ACTIONS_SYNC = arrayOf(ACTION_SYNC_DRAFTS, ACTION_SYNC_FILTERS, ACTION_SYNC_USER_COLORS,
ACTION_SYNC_USER_NICKNAMES)
}
data class SyncFinishedEvent(val syncType: String, val success: Boolean)

View File

@ -0,0 +1,21 @@
/*
* 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

View File

@ -9,12 +9,9 @@ import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.util.dagger.GeneralComponent
import java.lang.Exception
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by mariotaku on 2017/1/3.
*/
abstract class SyncTaskRunner(val context: Context) {
@Inject
protected lateinit var userColorNameManager: UserColorNameManager
@ -35,7 +32,7 @@ abstract class SyncTaskRunner(val context: Context) {
*/
protected abstract fun onRunningTask(action: String, callback: ((Boolean) -> Unit)): Boolean
fun runTask(action: String, callback: ((Boolean) -> Unit)? = null): Boolean {
fun runTask(action: String, timeout: Long = 0, unit: TimeUnit? = null, callback: ((Boolean) -> Unit)? = null): Boolean {
val syncType = SyncTaskRunner.getSyncType(action) ?: return false
if (!syncPreferences.isSyncEnabled(syncType)) return false
return onRunningTask(action) { success ->

View File

@ -24,23 +24,21 @@ import android.graphics.*
import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat
import android.text.format.DateUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.RadioButton
import android.widget.TextView
import kotlinx.android.synthetic.main.layout_twitter_card_poll.view.*
import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.ktextension.spannable
import org.mariotaku.ktextension.toLongOr
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.ktextension.weak
import org.mariotaku.microblog.library.twitter.TwitterCaps
import org.mariotaku.microblog.library.twitter.model.CardDataMap
import org.mariotaku.twidere.Constants.LOGTAG
import org.mariotaku.twidere.R
import org.mariotaku.twidere.exception.NoAccountException
import org.mariotaku.twidere.extension.model.*
import org.mariotaku.twidere.model.ParcelableCardEntity
import org.mariotaku.twidere.model.ParcelableStatus
@ -49,18 +47,15 @@ import org.mariotaku.twidere.util.MicroBlogAPIFactory
import org.mariotaku.twidere.util.TwitterCardUtils
import org.mariotaku.twidere.util.support.ViewSupport
import org.mariotaku.twidere.view.ContainerView
import java.lang.ref.WeakReference
import java.util.*
/**
* Created by mariotaku on 15/12/20.
*/
class CardPollViewController : ContainerView.ViewController() {
private lateinit var status: ParcelableStatus
private var fetchedCard: ParcelableCardEntity? = null
private val card: ParcelableCardEntity
get() = fetchedCard ?: status.card!!
private var clickedChoice: Boolean = false
private val card: ParcelableCardEntity?
get() = fetchedCard ?: status.card
override fun onCreate() {
super.onCreate()
@ -73,6 +68,7 @@ class CardPollViewController : ContainerView.ViewController() {
}
private fun initChoiceView() {
val card = this.card ?: return
val choicesCount = TwitterCardUtils.getChoicesCount(card)
val inflater = LayoutInflater.from(context)
@ -90,11 +86,13 @@ class CardPollViewController : ContainerView.ViewController() {
}
private fun loadCardPoll() {
val weakThis = WeakReference(this)
val status = this.status
val card = this.card ?: return
val weakThis by weak(this)
task {
val vc = weakThis.get() ?: throw IllegalStateException()
val card = vc.card
val details = AccountUtils.getAccountDetails(AccountManager.get(vc.context), card.account_key, true)!!
val vc = weakThis ?: throw IllegalStateException()
val details = AccountUtils.getAccountDetails(AccountManager.get(vc.context),
card.account_key, true) ?: throw NoAccountException()
val caps = details.newMicroBlogInstance(vc.context, cls = TwitterCaps::class.java)
val params = CardDataMap()
params.putString("card_uri", card.url)
@ -106,7 +104,7 @@ class CardPollViewController : ContainerView.ViewController() {
}
return@task cardResponse.toParcelable(details.key, details.type)
}.successUi { data ->
weakThis.get()?.displayPoll(data, status)
weakThis?.displayPoll(data, status)
}
}
@ -126,54 +124,13 @@ class CardPollViewController : ContainerView.ViewController() {
votesSum += card.getAsInteger("choice${choiceIndex}_count", 0)
}
val clickListener = object : View.OnClickListener {
private var clickedChoice: Boolean = false
override fun onClick(v: View) {
if (hasChoice || clickedChoice) return
for (i in 0 until view.pollContainer.childCount) {
val pollItem = view.pollContainer.getChildAt(i)
pollItem.isClickable = false
clickedChoice = true
val choiceRadioButton: RadioButton = pollItem.findViewById(R.id.choice_button)
val checked = v === pollItem
choiceRadioButton.isChecked = checked
if (checked) {
val cardData = CardDataMap()
cardData.putLong("original_tweet_id", status.id.toLongOr(-1L))
cardData.putString("card_uri", card.url)
cardData.putString("cards_platform", MicroBlogAPIFactory.CARDS_PLATFORM_ANDROID_12)
cardData.putString("response_card_name", card.name)
cardData.putString("selected_choice", (i + 1).toString())
val task = object : AbstractTask<CardDataMap, ParcelableCardEntity, CardPollViewController>() {
override fun afterExecute(callback: CardPollViewController?, results: ParcelableCardEntity?) {
results ?: return
callback?.displayAndReloadPoll(results, status)
}
override fun doLongOperation(cardDataMap: CardDataMap): ParcelableCardEntity? {
val details = AccountUtils.getAccountDetails(AccountManager.get(context),
card.account_key, true) ?: return null
val caps = details.newMicroBlogInstance(context, cls = TwitterCaps::class.java)
try {
val cardEntity = caps.sendPassThrough(cardDataMap).card ?: run {
return null
}
return cardEntity.toParcelable(card.account_key, details.type)
} catch (e: MicroBlogException) {
Log.w(LOGTAG, e)
}
return null
}
}
task.callback = this@CardPollViewController
task.params = cardData
TaskStarter.execute(task)
}
}
}
val clickListener = View.OnClickListener click@ { v ->
if (hasChoice || clickedChoice) return@click
clickedChoice = true
val i = view.pollContainer.indexOfChild(v)
if (i < 0) return@click
v.isClickable = false
submitChoice(i + 1)
}
val color = ContextCompat.getColor(context, R.color.material_light_blue_a200)
@ -218,6 +175,28 @@ class CardPollViewController : ContainerView.ViewController() {
view.pollSummary.spannable = context.getString(R.string.poll_summary_format, nVotes, timeLeft)
}
private fun submitChoice(which: Int) {
val status = this.status
val card = this.card ?: return
val cardData = CardDataMap()
cardData.putLong("original_tweet_id", status.id.toLongOr(-1L))
cardData.putString("card_uri", card.url)
cardData.putString("cards_platform", MicroBlogAPIFactory.CARDS_PLATFORM_ANDROID_12)
cardData.putString("response_card_name", card.name)
cardData.putString("selected_choice", which.toString())
val weakThis by weak(this)
task {
val vc = weakThis ?: throw InterruptedException()
val details = AccountUtils.getAccountDetails(AccountManager.get(vc.context),
card.account_key, true) ?: throw NoAccountException()
val caps = details.newMicroBlogInstance(vc.context, cls = TwitterCaps::class.java)
val cardEntity = caps.sendPassThrough(cardData).card
return@task cardEntity.toParcelable(card.account_key, details.type)
}.then { result ->
weakThis?.displayAndReloadPoll(result, status)
}
}
private class PercentDrawable internal constructor(
private val percent: Float,
private val radius: Float,