improved activities text

improved activities in streaming mode
This commit is contained in:
Mariotaku Lee 2017-03-26 18:05:34 +08:00
parent 17418bebf8
commit ed469b2c82
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
16 changed files with 705 additions and 383 deletions

View File

@ -21,11 +21,43 @@
package org.mariotaku.microblog.library.twitter.model;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.mariotaku.twidere.model.util.UnixEpochMillisDateConverter;
import java.util.Date;
/**
* Created by mariotaku on 2017/3/26.
*/
@JsonObject
public class MutedKeyword {
@JsonField(name = "id")
String id;
@JsonField(name = "keyword")
String keyword;
@JsonField(name = "created_at", typeConverter = UnixEpochMillisDateConverter.class)
Date createdAt;
public String getId() {
return id;
}
public String getKeyword() {
return keyword;
}
public Date getCreatedAt() {
return createdAt;
}
@Override
public String toString() {
return "MutedKeyword{" +
"id='" + id + '\'' +
", keyword='" + keyword + '\'' +
", createdAt=" + createdAt +
'}';
}
}

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.model.util;
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import java.io.IOException;
import java.util.Date;
/**
* Created by mariotaku on 2017/3/25.
*/
public class UnixEpochMillisDateConverter implements TypeConverter<Date> {
@Override
public Date parse(final JsonParser jsonParser) throws IOException {
long value = jsonParser.nextLongValue(-1);
return new Date(value);
}
@Override
public void serialize(final Date object, final String fieldName,
final boolean writeFieldNameForObject, final JsonGenerator jsonGenerator) throws IOException {
if (writeFieldNameForObject) {
jsonGenerator.writeFieldName(fieldName);
}
if (object == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeNumber(object.getTime());
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.model.util;
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Created by mariotaku on 2017/3/25.
*/
public class UnixEpochSecondDateConverter implements TypeConverter<Date> {
@Override
public Date parse(final JsonParser jsonParser) throws IOException {
long value = jsonParser.nextLongValue(-1);
return new Date(TimeUnit.MILLISECONDS.toSeconds(value));
}
@Override
public void serialize(final Date object, final String fieldName,
final boolean writeFieldNameForObject, final JsonGenerator jsonGenerator) throws IOException {
if (writeFieldNameForObject) {
jsonGenerator.writeFieldName(fieldName);
}
if (object == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeNumber(TimeUnit.SECONDS.toMillis(object.getTime()));
}
}
}

View File

@ -1,333 +0,0 @@
package org.mariotaku.twidere.model;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.StyleSpan;
import org.mariotaku.microblog.library.twitter.model.Activity;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.model.util.ParcelableActivityExtensionsKt;
import org.mariotaku.twidere.util.UserColorNameManager;
import org.oshkimaadziig.george.androidutils.SpanFormatter;
/**
* Created by mariotaku on 16/1/1.
*/
public class ActivityTitleSummaryMessage {
private final int icon;
private final int color;
@NonNull
private final CharSequence title;
private final CharSequence summary;
private ActivityTitleSummaryMessage(int icon, int color, @NonNull CharSequence title, @Nullable CharSequence summary) {
this.icon = icon;
this.color = color;
this.title = title;
this.summary = summary;
}
@Nullable
public static ActivityTitleSummaryMessage get(Context context, UserColorNameManager manager, ParcelableActivity activity,
ParcelableUser[] sources, int defaultColor,
boolean shouldUseStarsForLikes,
boolean nameFirst) {
final Resources resources = context.getResources();
switch (activity.action) {
case Activity.Action.FOLLOW: {
int typeIcon = R.drawable.ic_activity_action_follow;
int color = ContextCompat.getColor(context, R.color.highlight_follow);
CharSequence title;
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_follow,
R.string.activity_about_me_follow_multi, sources, nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, null);
}
case Activity.Action.FAVORITE: {
int typeIcon;
int color;
CharSequence title;
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite;
color = ContextCompat.getColor(context, R.color.highlight_favorite);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorite,
R.string.activity_about_me_favorite_multi, sources, nameFirst);
} else {
typeIcon = R.drawable.ic_activity_action_like;
color = ContextCompat.getColor(context, R.color.highlight_like);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_like,
R.string.activity_about_me_like_multi, sources, nameFirst);
}
final CharSequence summary = generateTextOnlySummary(context, activity.target_statuses);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.RETWEET: {
int typeIcon = R.drawable.ic_activity_action_retweet;
int color = ContextCompat.getColor(context, R.color.highlight_retweet);
CharSequence title;
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweet,
R.string.activity_about_me_retweet_multi, sources, nameFirst);
final CharSequence summary = generateTextOnlySummary(context,
activity.target_object_statuses);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.FAVORITED_RETWEET: {
int typeIcon;
int color;
CharSequence title;
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite;
color = ContextCompat.getColor(context, R.color.highlight_favorite);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorited_retweet,
R.string.activity_about_me_favorited_retweet_multi, sources, nameFirst);
} else {
typeIcon = R.drawable.ic_activity_action_like;
color = ContextCompat.getColor(context, R.color.highlight_like);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_retweet,
R.string.activity_about_me_liked_retweet_multi, sources, nameFirst);
}
final Spanned summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.RETWEETED_RETWEET: {
int typeIcon = R.drawable.ic_activity_action_retweet;
int color = ContextCompat.getColor(context, R.color.highlight_retweet);
CharSequence title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_retweet,
R.string.activity_about_me_retweeted_retweet_multi, sources, nameFirst);
final Spanned summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.RETWEETED_MENTION: {
int typeIcon = R.drawable.ic_activity_action_retweet;
int color = ContextCompat.getColor(context, R.color.highlight_retweet);
CharSequence title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_mention,
R.string.activity_about_me_retweeted_mention_multi, sources, nameFirst);
final Spanned summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.FAVORITED_MENTION: {
int typeIcon;
int color;
CharSequence title;
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite;
color = ContextCompat.getColor(context, R.color.highlight_favorite);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorited_mention,
R.string.activity_about_me_favorited_mention_multi, sources, nameFirst);
} else {
typeIcon = R.drawable.ic_activity_action_like;
color = ContextCompat.getColor(context, R.color.highlight_like);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_mention,
R.string.activity_about_me_liked_mention_multi, sources, nameFirst);
}
final Spanned summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.LIST_MEMBER_ADDED: {
CharSequence title;
int icon = R.drawable.ic_activity_action_list_added;
if ((sources.length == 1) && (activity.target_object_user_lists != null)
&& (activity.target_object_user_lists.length == 1)) {
final SpannableString firstDisplayName = new SpannableString(manager.getDisplayName(
sources[0], nameFirst));
final SpannableString listName = new SpannableString(activity.target_object_user_lists[0].name);
firstDisplayName.setSpan(new StyleSpan(Typeface.BOLD), 0, firstDisplayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
listName.setSpan(new StyleSpan(Typeface.BOLD), 0, listName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final String format = context.getString(R.string.activity_about_me_list_member_added_with_name);
final Configuration configuration = resources.getConfiguration();
title = SpanFormatter.format(configuration.locale, format, firstDisplayName,
listName);
} else {
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_list_member_added,
R.string.activity_about_me_list_member_added_multi, sources, nameFirst);
}
return new ActivityTitleSummaryMessage(icon, defaultColor, title, null);
}
case Activity.Action.MENTION:
case Activity.Action.REPLY:
case Activity.Action.QUOTE: {
final ParcelableStatus status = ParcelableActivityExtensionsKt.getActivityStatus(activity);
if (status == null) return null;
final SpannableString title = new SpannableString(manager.getDisplayName(status,
nameFirst));
title.setSpan(new StyleSpan(Typeface.BOLD), 0, title.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return new ActivityTitleSummaryMessage(0, 0, title, status.text_unescaped);
}
case Activity.Action.JOINED_TWITTER: {
int typeIcon = R.drawable.ic_activity_action_follow;
int color = ContextCompat.getColor(context, R.color.highlight_follow);
CharSequence title = getTitleStringAboutMe(resources, manager,
R.string.activity_joined_twitter, R.string.activity_joined_twitter_multi,
sources, nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, null);
}
case Activity.Action.MEDIA_TAGGED: {
int typeIcon = R.drawable.ic_activity_action_media_tagged;
int color = ContextCompat.getColor(context, R.color.highlight_tagged);
CharSequence title;
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_media_tagged,
R.string.activity_about_me_media_tagged_multi, sources, nameFirst);
final Spanned summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.FAVORITED_MEDIA_TAGGED: {
int typeIcon;
int color;
CharSequence title;
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite;
color = ContextCompat.getColor(context, R.color.highlight_favorite);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorited_media_tagged,
R.string.activity_about_me_favorited_media_tagged_multi, sources, nameFirst);
} else {
typeIcon = R.drawable.ic_activity_action_like;
color = ContextCompat.getColor(context, R.color.highlight_like);
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_media_tagged,
R.string.activity_about_me_liked_media_tagged_multi, sources, nameFirst);
}
final Spanned summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
case Activity.Action.RETWEETED_MEDIA_TAGGED: {
int typeIcon = R.drawable.ic_activity_action_retweet;
int color = ContextCompat.getColor(context, R.color.highlight_retweet);
CharSequence title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_media_tagged,
R.string.activity_about_me_retweeted_media_tagged_multi, sources, nameFirst);
final Spanned summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst);
return new ActivityTitleSummaryMessage(typeIcon, color, title, summary);
}
}
return null;
}
public static Spanned generateStatusTextSummary(Context context, UserColorNameManager manager,
ParcelableStatus[] statuses, boolean nameFirst) {
if (statuses == null) return null;
final SpannableStringBuilder summaryBuilder = new SpannableStringBuilder();
boolean first = true;
for (ParcelableStatus status : statuses) {
if (!first) {
summaryBuilder.append('\n');
}
final SpannableString displayName = new SpannableString(manager.getDisplayName(status.user_key,
status.user_name, status.user_screen_name, nameFirst));
displayName.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
summaryBuilder.append(SpanFormatter.format(context.getString(R.string.title_summary_line_format),
displayName, status.text_unescaped.replace('\n', ' ')));
first = false;
}
return summaryBuilder;
}
public static CharSequence generateTextOnlySummary(Context context, ParcelableStatus[] statuses) {
if (statuses == null) return null;
final StringBuilder summaryBuilder = new StringBuilder();
boolean first = true;
for (ParcelableStatus status : statuses) {
if (!first) {
summaryBuilder.append('\n');
}
summaryBuilder.append(status.text_unescaped.replace('\n', ' '));
first = false;
}
return summaryBuilder;
}
private static Spanned getTitleStringAboutMe(Resources resources, UserColorNameManager manager,
int stringRes, int stringResMulti,
ParcelableUser[] sources, boolean nameFirst) {
if (sources == null || sources.length == 0) return null;
final Configuration configuration = resources.getConfiguration();
final SpannableString firstDisplayName = new SpannableString(manager.getDisplayName(sources[0],
nameFirst));
firstDisplayName.setSpan(new StyleSpan(Typeface.BOLD), 0, firstDisplayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (sources.length == 1) {
final String format = resources.getString(stringRes);
return SpanFormatter.format(configuration.locale, format, firstDisplayName);
} else if (sources.length == 2) {
final String format = resources.getString(stringResMulti);
final SpannableString secondDisplayName = new SpannableString(manager.getDisplayName(sources[1],
nameFirst));
secondDisplayName.setSpan(new StyleSpan(Typeface.BOLD), 0, secondDisplayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return SpanFormatter.format(configuration.locale, format, firstDisplayName,
secondDisplayName);
} else {
final int othersCount = sources.length - 1;
final String nOthers = resources.getQuantityString(R.plurals.N_others, othersCount, othersCount);
final String format = resources.getString(stringResMulti);
return SpanFormatter.format(configuration.locale, format, firstDisplayName, nOthers);
}
}
private static Spanned getTitleStringByFriends(Resources resources, UserColorNameManager manager,
int stringRes, int stringResMulti,
ParcelableUser[] sources, Object[] targets, boolean nameFirst) {
if (sources == null || sources.length == 0) return null;
final Configuration configuration = resources.getConfiguration();
final SpannableString firstSourceName = new SpannableString(manager.getDisplayName(
sources[0], nameFirst));
firstSourceName.setSpan(new StyleSpan(Typeface.BOLD), 0, firstSourceName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final String displayName;
final Object target = targets[0];
if (target instanceof ParcelableUser) {
displayName = manager.getDisplayName((ParcelableUser) target, nameFirst);
} else if (target instanceof ParcelableStatus) {
displayName = manager.getDisplayName((ParcelableStatus) target, nameFirst);
} else {
throw new IllegalArgumentException();
}
final SpannableString firstTargetName = new SpannableString(displayName);
firstTargetName.setSpan(new StyleSpan(Typeface.BOLD), 0, firstTargetName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (sources.length == 1) {
final String format = resources.getString(stringRes);
return SpanFormatter.format(configuration.locale, format, firstSourceName, firstTargetName);
} else if (sources.length == 2) {
final String format = resources.getString(stringResMulti);
final SpannableString secondSourceName = new SpannableString(manager.getDisplayName(sources[1],
nameFirst));
secondSourceName.setSpan(new StyleSpan(Typeface.BOLD), 0, secondSourceName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return SpanFormatter.format(configuration.locale, format, firstSourceName,
secondSourceName, firstTargetName);
} else {
final int othersCount = sources.length - 1;
final String nOthers = resources.getQuantityString(R.plurals.N_others, othersCount, othersCount);
final String format = resources.getString(stringResMulti);
return SpanFormatter.format(configuration.locale, format, firstSourceName, nOthers, firstTargetName);
}
}
public int getIcon() {
return icon;
}
public int getColor() {
return color;
}
@NonNull
public CharSequence getTitle() {
return title;
}
@Nullable
public CharSequence getSummary() {
return summary;
}
}

View File

@ -24,6 +24,10 @@ fun <T> Collection<T>.addAllTo(collection: MutableCollection<T>): Boolean {
return collection.addAll(this)
}
fun <T> Array<T>.addAllTo(collection: MutableCollection<T>): Boolean {
return collection.addAll(this)
}
fun <E> Collection<E>?.nullableContentEquals(other: Collection<E>?): Boolean {
if (this == null) return other.isNullOrEmpty()
return contentEquals(other!!)

View File

@ -79,12 +79,10 @@ import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.extension.model.getAccountUser
import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.extension.model.unique_id_non_null
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.EditAltTextDialogFragment
import org.mariotaku.twidere.fragment.PermissionRequestDialog
import org.mariotaku.twidere.fragment.*
import org.mariotaku.twidere.fragment.PermissionRequestDialog.PermissionRequestCancelCallback
import org.mariotaku.twidere.fragment.ProgressDialogFragment
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.analyzer.PurchaseFinished
import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
import org.mariotaku.twidere.model.schedule.ScheduleInfo
import org.mariotaku.twidere.model.util.AccountUtils
@ -328,27 +326,27 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_TAKE_PHOTO, REQUEST_PICK_MEDIA -> {
if (resultCode == Activity.RESULT_OK && intent != null) {
val src = MediaPickerActivity.getMediaUris(intent)
if (resultCode == Activity.RESULT_OK && data != null) {
val src = MediaPickerActivity.getMediaUris(data)
TaskStarter.execute(AddMediaTask(this, src, false, false))
}
}
REQUEST_EDIT_IMAGE -> {
if (resultCode == Activity.RESULT_OK && intent != null) {
if (intent.data != null) {
if (resultCode == Activity.RESULT_OK && data != null) {
if (data.data != null) {
setMenu()
updateTextCount()
}
}
}
REQUEST_EXTENSION_COMPOSE -> {
if (resultCode == Activity.RESULT_OK && intent != null) {
val text = intent.getStringExtra(EXTRA_TEXT)
val append = intent.getStringExtra(EXTRA_APPEND_TEXT)
val imageUri = intent.getParcelableExtra<Uri>(EXTRA_IMAGE_URI)
if (resultCode == Activity.RESULT_OK && data != null) {
val text = data.getStringExtra(EXTRA_TEXT)
val append = data.getStringExtra(EXTRA_APPEND_TEXT)
val imageUri = data.getParcelableExtra<Uri>(EXTRA_IMAGE_URI)
if (text != null) {
editText.setText(text)
} else if (append != null) {
@ -360,9 +358,14 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
updateTextCount()
}
}
REQUEST_PURCHASE_EXTRA_FEATURES -> {
if (resultCode == Activity.RESULT_OK) {
Analyzer.log(PurchaseFinished.create(data!!))
}
}
REQUEST_SET_SCHEDULE -> {
if (resultCode == Activity.RESULT_OK && intent != null) {
scheduleInfo = intent.getParcelableExtra(EXTRA_SCHEDULE_INFO)
if (resultCode == Activity.RESULT_OK) {
scheduleInfo = data?.getParcelableExtra(EXTRA_SCHEDULE_INFO)
}
}
}
@ -545,7 +548,13 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
R.id.schedule -> {
val controller = statusScheduleController ?: return true
startActivityForResult(controller.createSetScheduleIntent(), REQUEST_SET_SCHEDULE)
if (extraFeaturesService.isEnabled(ExtraFeaturesService.FEATURE_SCHEDULE_STATUS)) {
startActivityForResult(controller.createSetScheduleIntent(), REQUEST_SET_SCHEDULE)
} else {
ExtraFeaturesIntroductionDialogFragment.show(supportFragmentManager,
feature = ExtraFeaturesService.FEATURE_SCHEDULE_STATUS,
requestCode = REQUEST_PURCHASE_EXTRA_FEATURES)
}
}
else -> {
val intent = item.intent

View File

@ -1,9 +1,66 @@
package org.mariotaku.twidere.extension.model
import org.mariotaku.ktextension.addAllTo
import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.twidere.model.ParcelableActivity
import java.util.*
/**
* Created by mariotaku on 2016/12/6.
*/
val ParcelableActivity.id: String
get() = "$min_position-$max_position"
get() = "$min_position-$max_position"
val ParcelableActivity.reachedCountLimit: Boolean get() {
fun Array<*>?.reachedCountLimit() = if (this == null) false else size > 10
return sources.reachedCountLimit() || target_statuses.reachedCountLimit() ||
target_users.reachedCountLimit() || target_user_lists.reachedCountLimit() ||
target_object_statuses.reachedCountLimit() || target_object_users.reachedCountLimit() ||
target_object_user_lists.reachedCountLimit()
}
fun ParcelableActivity.isSameSources(another: ParcelableActivity): Boolean {
return Arrays.equals(sources, another.sources)
}
fun ParcelableActivity.isSameTarget(another: ParcelableActivity): Boolean {
if (target_statuses.isNullOrEmpty() && target_users.isNullOrEmpty() && target_user_lists
.isNullOrEmpty()) {
return false
}
return Arrays.equals(target_users, another.target_users) && Arrays.equals(target_statuses,
another.target_statuses) && Arrays.equals(target_user_lists, another.target_user_lists)
}
fun ParcelableActivity.isSameTargetObject(another: ParcelableActivity): Boolean {
if (target_object_statuses.isNullOrEmpty() && target_object_users.isNullOrEmpty()
&& target_object_user_lists.isNullOrEmpty()) {
return false
}
return Arrays.equals(target_object_users, another.target_object_users)
&& Arrays.equals(target_object_statuses, another.target_object_statuses)
&& Arrays.equals(target_object_user_lists, another.target_object_user_lists)
}
fun ParcelableActivity.prependSources(another: ParcelableActivity) {
sources = uniqCombine(another.sources, sources)
}
fun ParcelableActivity.prependTargets(another: ParcelableActivity) {
target_statuses = uniqCombine(another.target_statuses, target_statuses)
target_users = uniqCombine(another.target_users, target_users)
target_user_lists = uniqCombine(another.target_user_lists, target_user_lists)
}
fun ParcelableActivity.prependTargetObjects(another: ParcelableActivity) {
target_object_statuses = uniqCombine(another.target_object_statuses, target_object_statuses)
target_object_users = uniqCombine(another.target_object_users, target_object_users)
target_object_user_lists = uniqCombine(another.target_object_user_lists, target_object_user_lists)
}
private inline fun <reified T> uniqCombine(vararg arrays: Array<T>?): Array<T> {
val set = mutableSetOf<T>()
arrays.forEach { array -> array?.addAllTo(set) }
return set.toTypedArray()
}

View File

@ -41,6 +41,7 @@ import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.coerceInOr
import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.microblog.library.twitter.model.Activity
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter
@ -58,6 +59,7 @@ import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.constant.readFromBottomKey
import org.mariotaku.twidere.constant.rememberPositionKey
import org.mariotaku.twidere.extension.model.getAccountType
import org.mariotaku.twidere.extension.model.id
import org.mariotaku.twidere.fragment.AbsStatusesFragment.DefaultOnLikedListener
import org.mariotaku.twidere.loader.iface.IExtendedLoader
import org.mariotaku.twidere.model.*
@ -310,6 +312,14 @@ abstract class AbsActivitiesFragment protected constructor() :
override fun onGapClick(holder: GapViewHolder, position: Int) {
val activity = adapter.getActivity(position)
DebugLog.v(msg = "Load activity gap $activity")
if (!Utils.isOfficialCredentials(context, activity.account_key)) {
// Skip if item is not a status
if (activity.action !in Activity.Action.MENTION_ACTIONS) {
adapter.removeGapLoadingId(ObjectId(activity.account_key, activity.id))
adapter.notifyItemChanged(position)
return
}
}
val accountIds = arrayOf(activity.account_key)
val maxIds = arrayOf(activity.min_position)
val maxSortIds = longArrayOf(activity.min_sort_position)

View File

@ -144,18 +144,23 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
if (ILoadMoreSupportAdapter.START in position || refreshing) return
super.onLoadMoreContents(position)
if (position == 0L) return
val contentUri = this.contentUri
getActivities(object : SimpleRefreshTaskParam() {
override val accountKeys: Array<UserKey> by lazy {
this@CursorActivitiesFragment.accountKeys
}
override val maxIds: Array<String?>?
get() = getOldestActivityIds(accountKeys)
get() {
val context = context ?: return null
return DataStoreUtils.getRefreshOldestActivityMaxPositions(context, contentUri,
accountKeys.toNulls())
}
override val maxSortIds: LongArray?
get() {
val context = context ?: return null
return DataStoreUtils.getOldestActivityMaxSortPositions(context,
return DataStoreUtils.getRefreshOldestActivityMaxSortPositions(context,
contentUri, accountKeys.toNulls())
}
@ -167,20 +172,28 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
})
}
override fun triggerRefresh(): Boolean {
super.triggerRefresh()
val contentUri = this.contentUri
getActivities(object : SimpleRefreshTaskParam() {
override val accountKeys: Array<UserKey> by lazy {
this@CursorActivitiesFragment.accountKeys
}
override val sinceIds: Array<String?>?
get() = DataStoreUtils.getNewestActivityMaxPositions(context, contentUri,
accountKeys.toNulls())
get() {
val context = context ?: return null
return DataStoreUtils.getRefreshNewestActivityMaxPositions(context, contentUri,
accountKeys.toNulls())
}
override val sinceSortIds: LongArray?
get() = DataStoreUtils.getNewestActivityMaxSortPositions(context, contentUri,
accountKeys.toNulls())
get() {
val context = context ?: return null
return DataStoreUtils.getRefreshNewestActivityMaxSortPositions(context,
contentUri, accountKeys.toNulls())
}
override val hasSinceIds: Boolean
get() = true
@ -209,11 +222,6 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
}
}
protected fun getOldestActivityIds(accountKeys: Array<UserKey>): Array<String?>? {
val context = context ?: return null
return DataStoreUtils.getOldestActivityMaxPositions(context, contentUri, accountKeys.toNulls())
}
protected abstract val isFilterEnabled: Boolean
protected open fun processWhere(where: Expression, whereArgs: Array<String>): ParameterizedExpression {
@ -345,4 +353,8 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
class ActivityCursor(cursor: Cursor, indies: ObjectCursor.CursorIndices<ParcelableActivity>,
val filteredUserIds: Array<UserKey>) : ObjectCursor<ParcelableActivity>(cursor, indies)
}
companion object {
}
}

View File

@ -0,0 +1,252 @@
package org.mariotaku.twidere.model
import android.content.Context
import android.content.res.Resources
import android.graphics.Typeface
import android.support.v4.content.ContextCompat
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.StyleSpan
import org.mariotaku.microblog.library.twitter.model.Activity
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.util.getActivityStatus
import org.mariotaku.twidere.text.style.NonBreakEllipseSpan
import org.mariotaku.twidere.util.UserColorNameManager
import org.oshkimaadziig.george.androidutils.SpanFormatter
/**
* Created by mariotaku on 16/1/1.
*/
class ActivityTitleSummaryMessage private constructor(val icon: Int, val color: Int, val title: CharSequence, val summary: CharSequence?) {
companion object {
fun get(context: Context, manager: UserColorNameManager, activity: ParcelableActivity,
sources: Array<ParcelableUser>, defaultColor: Int, shouldUseStarsForLikes: Boolean,
nameFirst: Boolean): ActivityTitleSummaryMessage? {
val resources = context.resources
when (activity.action) {
Activity.Action.FOLLOW -> {
val typeIcon = R.drawable.ic_activity_action_follow
val color = ContextCompat.getColor(context, R.color.highlight_follow)
val title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_follow,
R.string.activity_about_me_follow_multi, sources, nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, null)
}
Activity.Action.FAVORITE -> {
val typeIcon: Int
val color: Int
val title: CharSequence
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite
color = ContextCompat.getColor(context, R.color.highlight_favorite)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorite,
R.string.activity_about_me_favorite_multi, sources, nameFirst)
} else {
typeIcon = R.drawable.ic_activity_action_like
color = ContextCompat.getColor(context, R.color.highlight_like)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_like,
R.string.activity_about_me_like_multi, sources, nameFirst)
}
val summary = generateTextOnlySummary(activity.target_statuses)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.RETWEET -> {
val typeIcon = R.drawable.ic_activity_action_retweet
val color = ContextCompat.getColor(context, R.color.highlight_retweet)
val title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweet,
R.string.activity_about_me_retweet_multi, sources, nameFirst)
val summary = generateTextOnlySummary(activity.target_object_statuses)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.FAVORITED_RETWEET -> {
val typeIcon: Int
val color: Int
val title: CharSequence
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite
color = ContextCompat.getColor(context, R.color.highlight_favorite)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorited_retweet,
R.string.activity_about_me_favorited_retweet_multi, sources, nameFirst)
} else {
typeIcon = R.drawable.ic_activity_action_like
color = ContextCompat.getColor(context, R.color.highlight_like)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_retweet,
R.string.activity_about_me_liked_retweet_multi, sources, nameFirst)
}
val summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.RETWEETED_RETWEET -> {
val typeIcon = R.drawable.ic_activity_action_retweet
val color = ContextCompat.getColor(context, R.color.highlight_retweet)
val title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_retweet,
R.string.activity_about_me_retweeted_retweet_multi, sources, nameFirst)
val summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.RETWEETED_MENTION -> {
val typeIcon = R.drawable.ic_activity_action_retweet
val color = ContextCompat.getColor(context, R.color.highlight_retweet)
val title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_mention,
R.string.activity_about_me_retweeted_mention_multi, sources, nameFirst)
val summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.FAVORITED_MENTION -> {
val typeIcon: Int
val color: Int
val title: CharSequence
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite
color = ContextCompat.getColor(context, R.color.highlight_favorite)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorited_mention,
R.string.activity_about_me_favorited_mention_multi, sources, nameFirst)
} else {
typeIcon = R.drawable.ic_activity_action_like
color = ContextCompat.getColor(context, R.color.highlight_like)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_mention,
R.string.activity_about_me_liked_mention_multi, sources, nameFirst)
}
val summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.LIST_MEMBER_ADDED -> {
val title: CharSequence
val icon = R.drawable.ic_activity_action_list_added
if (sources.size == 1 && activity.target_object_user_lists != null
&& activity.target_object_user_lists.size == 1) {
val firstDisplayName = SpannableString(manager.getDisplayName(
sources[0], nameFirst))
val listName = SpannableString(activity.target_object_user_lists[0].name)
firstDisplayName.setSpan(StyleSpan(Typeface.BOLD), 0, firstDisplayName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
listName.setSpan(StyleSpan(Typeface.BOLD), 0, listName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
val format = context.getString(R.string.activity_about_me_list_member_added_with_name)
title = SpanFormatter.format(format, firstDisplayName, listName)
} else {
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_list_member_added,
R.string.activity_about_me_list_member_added_multi, sources, nameFirst)
}
return ActivityTitleSummaryMessage(icon, defaultColor, title, null)
}
Activity.Action.MENTION, Activity.Action.REPLY, Activity.Action.QUOTE -> {
val status = activity.getActivityStatus() ?: return null
val title = SpannableString(manager.getDisplayName(status,
nameFirst))
title.setSpan(StyleSpan(Typeface.BOLD), 0, title.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
return ActivityTitleSummaryMessage(0, 0, title, status.text_unescaped)
}
Activity.Action.JOINED_TWITTER -> {
val typeIcon = R.drawable.ic_activity_action_follow
val color = ContextCompat.getColor(context, R.color.highlight_follow)
val title = getTitleStringAboutMe(resources, manager,
R.string.activity_joined_twitter, R.string.activity_joined_twitter_multi,
sources, nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, null)
}
Activity.Action.MEDIA_TAGGED -> {
val typeIcon = R.drawable.ic_activity_action_media_tagged
val color = ContextCompat.getColor(context, R.color.highlight_tagged)
val title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_media_tagged,
R.string.activity_about_me_media_tagged_multi, sources, nameFirst)
val summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.FAVORITED_MEDIA_TAGGED -> {
val typeIcon: Int
val color: Int
val title: CharSequence
if (shouldUseStarsForLikes) {
typeIcon = R.drawable.ic_activity_action_favorite
color = ContextCompat.getColor(context, R.color.highlight_favorite)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_favorited_media_tagged,
R.string.activity_about_me_favorited_media_tagged_multi, sources, nameFirst)
} else {
typeIcon = R.drawable.ic_activity_action_like
color = ContextCompat.getColor(context, R.color.highlight_like)
title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_media_tagged,
R.string.activity_about_me_liked_media_tagged_multi, sources, nameFirst)
}
val summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
Activity.Action.RETWEETED_MEDIA_TAGGED -> {
val typeIcon = R.drawable.ic_activity_action_retweet
val color = ContextCompat.getColor(context, R.color.highlight_retweet)
val title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_media_tagged,
R.string.activity_about_me_retweeted_media_tagged_multi, sources, nameFirst)
val summary = generateStatusTextSummary(context, manager, activity.target_statuses,
nameFirst)
return ActivityTitleSummaryMessage(typeIcon, color, title, summary)
}
}
return null
}
private fun generateStatusTextSummary(context: Context, manager: UserColorNameManager,
statuses: Array<ParcelableStatus>?, nameFirst: Boolean): Spanned? {
return statuses?.joinTo(SpannableStringBuilder(), separator = "\n") { status ->
val displayName = SpannableString(manager.getDisplayName(status.user_key,
status.user_name, status.user_screen_name, nameFirst)).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, it.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
val statusText = if (statuses.size > 1) {
SpannableString(status.text_unescaped.replace('\n', ' ')).also {
it.setSpan(NonBreakEllipseSpan(), 0, it.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
} else {
status.text_unescaped
}
return@joinTo SpanFormatter.format(context.getString(R.string.title_summary_line_format),
displayName, statusText)
}
}
private fun generateTextOnlySummary(statuses: Array<ParcelableStatus>?): CharSequence? {
return statuses?.joinTo(SpannableStringBuilder(), separator = "\n") { status ->
if (statuses.size > 1) {
return@joinTo SpannableString(status.text_unescaped.replace('\n', ' ')).also {
it.setSpan(NonBreakEllipseSpan(), 0, it.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
} else {
return@joinTo status.text_unescaped
}
}
}
private fun getTitleStringAboutMe(resources: Resources, manager: UserColorNameManager,
stringRes: Int, stringResMulti: Int, sources: Array<ParcelableUser>,
nameFirst: Boolean): CharSequence {
val firstDisplayName = SpannableString(manager.getDisplayName(sources[0],
nameFirst))
firstDisplayName.setSpan(StyleSpan(Typeface.BOLD), 0, firstDisplayName.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
if (sources.size == 1) {
val format = resources.getString(stringRes)
return SpanFormatter.format(format, firstDisplayName)
} else if (sources.size == 2) {
val format = resources.getString(stringResMulti)
val secondDisplayName = SpannableString(manager.getDisplayName(sources[1],
nameFirst))
secondDisplayName.setSpan(StyleSpan(Typeface.BOLD), 0, secondDisplayName.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
return SpanFormatter.format(format, firstDisplayName,
secondDisplayName)
} else {
val othersCount = sources.size - 1
val nOthers = resources.getQuantityString(R.plurals.N_others, othersCount, othersCount)
val format = resources.getString(stringResMulti)
return SpanFormatter.format(format, firstDisplayName, nOthers)
}
}
}
}

View File

@ -16,6 +16,7 @@ import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.toLong
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.TwitterUserStream
@ -29,9 +30,7 @@ import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.constant.streamingEnabledKey
import org.mariotaku.twidere.constant.streamingNonMeteredNetworkKey
import org.mariotaku.twidere.constant.streamingPowerSavingKey
import org.mariotaku.twidere.extension.model.isOfficial
import org.mariotaku.twidere.extension.model.isStreamingSupported
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.extension.model.*
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.model.util.ParcelableActivityUtils
@ -257,6 +256,8 @@ class StreamingService : BaseService() {
private var homeInsertGap = false
private var interactionsInsertGap = false
private var lastActivityAboutMe: ParcelableActivity? = null
override fun onConnected(): Boolean {
homeInsertGap = true
interactionsInsertGap = true
@ -303,13 +304,61 @@ class StreamingService : BaseService() {
handler.postDelayed(interactionsTimeoutRunnable, TimeUnit.SECONDS.toMillis(30))
}
} else {
val parcelableActivity = ParcelableActivityUtils.fromActivity(activity,
account.key, interactionsInsertGap, profileImageSize)
parcelableActivity.position_key = parcelableActivity.timestamp
val insertGap: Boolean
if (activity.action in Activity.Action.MENTION_ACTIONS) {
insertGap = interactionsInsertGap
interactionsInsertGap = false
} else {
insertGap = false
}
val curActivity = ParcelableActivityUtils.fromActivity(activity, account.key,
insertGap, profileImageSize)
curActivity.position_key = curActivity.timestamp
var updateId = -1L
if (curActivity.action !in Activity.Action.MENTION_ACTIONS) {
/* Merge two activities if:
* * Not mention/reply/quote
* * Same action
* * Same source or target or target object
*/
val lastActivity = this.lastActivityAboutMe
if (lastActivity != null && curActivity.action == lastActivity.action) {
if (curActivity.reachedCountLimit) {
// Skip if more than 10 sources/targets/target_objects
} else if (curActivity.isSameSources(lastActivity)) {
curActivity.prependTargets(lastActivity)
curActivity.prependTargetObjects(lastActivity)
updateId = lastActivity._id
} else if (curActivity.isSameTarget(lastActivity)) {
curActivity.prependSources(lastActivity)
curActivity.prependTargets(lastActivity)
updateId = lastActivity._id
} else if (curActivity.isSameTargetObject(lastActivity)) {
curActivity.prependSources(lastActivity)
curActivity.prependTargets(lastActivity)
updateId = lastActivity._id
}
if (updateId > 0) {
curActivity.min_position = lastActivity.min_position
curActivity.min_sort_position = lastActivity.min_sort_position
}
}
}
val values = ObjectCursor.valuesCreatorFrom(ParcelableActivity::class.java)
.create(parcelableActivity)
context.contentResolver.insert(Activities.AboutMe.CONTENT_URI, values)
interactionsInsertGap = false
.create(curActivity)
val resolver = context.contentResolver
if (updateId > 0) {
val where = Expression.equalsArgs(Activities._ID).sql
val whereArgs = arrayOf(updateId.toString())
resolver.update(Activities.AboutMe.CONTENT_URI, values, where, whereArgs)
curActivity._id = updateId
} else {
val uri = resolver.insert(Activities.AboutMe.CONTENT_URI, values)
if (uri != null) {
curActivity._id = uri.lastPathSegment.toLong(-1)
}
}
lastActivityAboutMe = curActivity
}
return true
}
@ -374,11 +423,11 @@ class StreamingService : BaseService() {
override val sinceIds: Array<String?>?
get() = DataStoreUtils.getNewestActivityMaxPositions(context,
Activities.AboutMe.CONTENT_URI, arrayOf(account.key))
Activities.AboutMe.CONTENT_URI, arrayOf(account.key), null, null)
override val sinceSortIds: LongArray?
get() = DataStoreUtils.getNewestActivityMaxSortPositions(context,
Activities.AboutMe.CONTENT_URI, arrayOf(account.key))
Activities.AboutMe.CONTENT_URI, arrayOf(account.key), null, null)
override val hasSinceIds: Boolean = true

View File

@ -36,6 +36,7 @@ import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.UnreadCountUpdatedEvent
import org.mariotaku.twidere.model.message.conversation.TwitterOfficialConversationExtras
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Messages
@ -77,6 +78,10 @@ class MarkMessageReadTask(
return true
}
override fun onSucceed(callback: Unit?, result: Boolean) {
bus.post(UnreadCountUpdatedEvent(-1))
}
private fun performMarkRead(microBlog: MicroBlog, account: AccountDetails,
conversation: ParcelableMessageConversation): Pair<String, Long>? {
when (account.type) {

View File

@ -0,0 +1,45 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.text.style
import android.graphics.Canvas
import android.graphics.Paint
import android.text.TextPaint
import android.text.TextUtils
import android.text.style.ReplacementSpan
/**
* Created by mariotaku on 2017/3/26.
*/
class NonBreakEllipseSpan : ReplacementSpan() {
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
return 1
}
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int,
y: Int, bottom: Int, paint: Paint) {
if (paint !is TextPaint) return
val ellipsized = TextUtils.ellipsize(text.substring(start, end), paint, canvas.width - x,
TextUtils.TruncateAt.END)
canvas.drawText(ellipsized, 0, ellipsized.length, x, y.toFloat(), paint)
}
}

View File

@ -283,7 +283,7 @@ class AsyncTwitterWrapper(
override val accountKeys: Array<UserKey> by lazy { action() }
override val sinceIds: Array<String?>? by lazy {
DataStoreUtils.getNewestActivityMaxPositions(context,
DataStoreUtils.getRefreshNewestActivityMaxPositions(context,
Activities.AboutMe.CONTENT_URI, accountKeys.toNulls())
}
})

View File

@ -189,28 +189,76 @@ object DataStoreUtils {
OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)), null, null)
}
fun getNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
fun getNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): Array<String?> {
return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
Activities.MAX_REQUEST_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)),
null, null)
extraWhere, extraWhereArgs)
}
fun getOldestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
fun getRefreshNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
Array<String?> {
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
DataStoreUtils.getNewestActivityMaxPositions(context, uri, accountKeys,
where, whereArgs)
}, { arr1, arr2 ->
Array(accountKeys.size) { arr1[it] ?: arr2[it] }
}, accountKeys)
}
fun getOldestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): Array<String?> {
return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
Activities.MAX_REQUEST_POSITION, OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP)),
null, null)
extraWhere, extraWhereArgs)
}
fun getNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
fun getRefreshOldestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
Array<String?> {
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
DataStoreUtils.getOldestActivityMaxPositions(context, uri, accountKeys,
where, whereArgs)
}, { arr1, arr2 ->
Array(accountKeys.size) { arr1[it] ?: arr2[it] }
}, accountKeys)
}
fun getNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): LongArray {
return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
Activities.MAX_SORT_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)),
null, null)
extraWhere, extraWhereArgs)
}
fun getOldestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
fun getRefreshNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
LongArray {
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
DataStoreUtils.getNewestActivityMaxSortPositions(context, uri, accountKeys,
where, whereArgs)
}, { arr1, arr2 ->
LongArray(accountKeys.size) { arr1[it].takeIf { it > 0 } ?: arr2[it] }
}, accountKeys)
}
fun getOldestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): LongArray {
return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
Activities.MAX_SORT_POSITION, OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP)),
null, null)
extraWhere, extraWhereArgs)
}
fun getRefreshOldestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
LongArray {
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
DataStoreUtils.getOldestActivityMaxSortPositions(context, uri, accountKeys,
where, whereArgs)
}, { arr1, arr2 ->
LongArray(accountKeys.size) { arr1[it].takeIf { it > 0 } ?: arr2[it] }
}, accountKeys)
}
fun getStatusCount(context: Context, uri: Uri, accountId: UserKey): Int {
@ -911,4 +959,31 @@ object DataStoreUtils {
}
private fun getIdsWhere(official: Boolean): Pair<Expression?, Array<String>?> {
if (official) return Pair(null, null)
return Pair(Expression.inArgs(Activities.ACTION, Activity.Action.MENTION_ACTIONS.size)
, Activity.Action.MENTION_ACTIONS)
}
private fun <T> getOfficialSeparatedIds(context: Context, getFromDatabase: (Array<UserKey?>, Boolean) -> T,
mergeResult: (T, T) -> T, accountKeys: Array<UserKey?>): T {
val officialKeys = Array(accountKeys.size) {
val key = accountKeys[it]
if (Utils.isOfficialCredentials(context, key)) {
return@Array key
}
return@Array null
}
val notOfficialKeys = Array(accountKeys.size) {
val key = accountKeys[it]
if (Utils.isOfficialCredentials(context, key)) {
return@Array null
}
return@Array key
}
val officialMaxPositions = getFromDatabase(officialKeys, true)
val notOfficialMaxPositions = getFromDatabase(notOfficialKeys, false)
return mergeResult(officialMaxPositions, notOfficialMaxPositions)
}
}

View File

@ -63,8 +63,8 @@ class TaskServiceRunner(
ACTION_REFRESH_NOTIFICATIONS -> {
val task = GetActivitiesAboutMeTask(context)
task.params = AutoRefreshTaskParam(context, AccountPreferences::isAutoRefreshMentionsEnabled) { accountKeys ->
DataStoreUtils.getNewestActivityMaxPositions(context, Activities.AboutMe.CONTENT_URI,
accountKeys.toNulls())
DataStoreUtils.getRefreshNewestActivityMaxPositions(context,
Activities.AboutMe.CONTENT_URI, accountKeys.toNulls())
}
return task
}