1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-17 04:00:48 +01:00

added timeline filter

supporting new reply style
This commit is contained in:
Mariotaku Lee 2017-03-31 16:05:50 +08:00
parent 31543f1d25
commit 4ca81cac7d
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
24 changed files with 470 additions and 83 deletions

View File

@ -57,7 +57,8 @@ subprojects {
MediaViewerLibrary: '0.9.23', MediaViewerLibrary: '0.9.23',
MultiValueSwitch : '0.9.8', MultiValueSwitch : '0.9.8',
PickNCrop : '0.9.22', PickNCrop : '0.9.22',
AndroidGIFDrawable: '1.2.6' AndroidGIFDrawable: '1.2.6',
KPreferences : '0.9.6'
] ]
} }

View File

@ -30,11 +30,6 @@ public class StatusUpdate extends SimpleValueMap {
put("status", status); put("status", status);
} }
public StatusUpdate displayCoordinates(final boolean displayCoordinates) {
setDisplayCoordinates(displayCoordinates);
return this;
}
public void setInReplyToStatusId(final String inReplyToStatusId) { public void setInReplyToStatusId(final String inReplyToStatusId) {
put("in_reply_to_status_id", inReplyToStatusId); put("in_reply_to_status_id", inReplyToStatusId);
} }
@ -43,14 +38,6 @@ public class StatusUpdate extends SimpleValueMap {
put("repost_status_id", repostStatusId); put("repost_status_id", repostStatusId);
} }
public void setLocation(final GeoLocation location) {
remove("lat");
remove("long");
if (location == null) return;
put("lat", location.getLatitude());
put("long", location.getLongitude());
}
public void setMediaIds(final String... mediaIds) { public void setMediaIds(final String... mediaIds) {
remove("media_ids"); remove("media_ids");
if (mediaIds == null) return; if (mediaIds == null) return;
@ -71,17 +58,22 @@ public class StatusUpdate extends SimpleValueMap {
} }
public void setDisplayCoordinates(final boolean displayCoordinates) { public StatusUpdate displayCoordinates(final boolean displayCoordinates) {
put("display_coordinates", displayCoordinates); put("display_coordinates", displayCoordinates);
return this;
} }
public StatusUpdate autoPopulateReplyMetadata(final boolean autoPopulateReplyMetadata) {
public void setPossiblySensitive(final boolean possiblySensitive) { put("auto_populate_reply_metadata", autoPopulateReplyMetadata);
put("possibly_sensitive", possiblySensitive); return this;
} }
public StatusUpdate location(final GeoLocation location) { public StatusUpdate location(final GeoLocation location) {
setLocation(location); remove("lat");
remove("long");
if (location == null) return this;
put("lat", location.getLatitude());
put("long", location.getLongitude());
return this; return this;
} }
@ -101,7 +93,7 @@ public class StatusUpdate extends SimpleValueMap {
} }
public StatusUpdate possiblySensitive(final boolean possiblySensitive) { public StatusUpdate possiblySensitive(final boolean possiblySensitive) {
setPossiblySensitive(possiblySensitive); put("possibly_sensitive", possiblySensitive);
return this; return this;
} }

View File

@ -185,7 +185,7 @@ dependencies {
compile "com.github.mariotaku.CommonsLibrary:io:${libVersions['MariotakuCommons']}" compile "com.github.mariotaku.CommonsLibrary:io:${libVersions['MariotakuCommons']}"
compile "com.github.mariotaku.CommonsLibrary:text:${libVersions['MariotakuCommons']}" compile "com.github.mariotaku.CommonsLibrary:text:${libVersions['MariotakuCommons']}"
compile "com.github.mariotaku.CommonsLibrary:text-kotlin:${libVersions['MariotakuCommons']}" compile "com.github.mariotaku.CommonsLibrary:text-kotlin:${libVersions['MariotakuCommons']}"
compile 'com.github.mariotaku:KPreferences:0.9.5' compile "com.github.mariotaku:KPreferences:${libVersions['KPreferences']}"
compile 'com.github.mariotaku:Chameleon:0.9.16' compile 'com.github.mariotaku:Chameleon:0.9.16'
compile "org.jetbrains.kotlin:kotlin-stdlib:${libVersions['Kotlin']}" compile "org.jetbrains.kotlin:kotlin-stdlib:${libVersions['Kotlin']}"

View File

@ -33,6 +33,9 @@ public class DefaultFeatures {
@JsonField(name = "media_link_counts_in_status") @JsonField(name = "media_link_counts_in_status")
boolean mediaLinkCountsInStatus = false; boolean mediaLinkCountsInStatus = false;
@JsonField(name = "mentions_counts_in_status")
boolean mentionsCountsInStatus = false;
@JsonField(name = "default_twitter_consumer_key") @JsonField(name = "default_twitter_consumer_key")
String defaultTwitterConsumerKey; String defaultTwitterConsumerKey;
@ -49,6 +52,10 @@ public class DefaultFeatures {
return mediaLinkCountsInStatus; return mediaLinkCountsInStatus;
} }
public boolean isMentionsCountsInStatus() {
return mentionsCountsInStatus;
}
public String getDefaultTwitterConsumerKey() { public String getDefaultTwitterConsumerKey() {
return defaultTwitterConsumerKey; return defaultTwitterConsumerKey;
} }

View File

@ -0,0 +1,31 @@
/*
* 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.timeline;
import android.content.Context;
import android.os.Parcelable;
/**
* Created by mariotaku on 2017/3/31.
*/
public interface TimelineFilter extends Parcelable {
CharSequence getSummary(Context context);
}

View File

@ -0,0 +1,88 @@
/*
* 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.timeline;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease;
import org.mariotaku.twidere.R;
/**
* Created by mariotaku on 2017/3/31.
*/
@ParcelablePlease
public class UserTimelineFilter implements TimelineFilter, Parcelable {
@ParcelableThisPlease
boolean includeRetweets = true;
@ParcelableThisPlease
boolean includeReplies = true;
public boolean isIncludeRetweets() {
return includeRetweets;
}
public void setIncludeRetweets(final boolean includeRetweets) {
this.includeRetweets = includeRetweets;
}
public boolean isIncludeReplies() {
return includeReplies;
}
public void setIncludeReplies(final boolean includeReplies) {
this.includeReplies = includeReplies;
}
@Override
public CharSequence getSummary(final Context context) {
if (includeRetweets && includeReplies) {
return context.getString(R.string.label_statuses_retweets_replies);
} else if (includeReplies) {
return context.getString(R.string.label_statuses_replies);
}
return context.getString(R.string.label_statuses);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
UserTimelineFilterParcelablePlease.writeToParcel(this, dest, flags);
}
public static final Creator<UserTimelineFilter> CREATOR = new Creator<UserTimelineFilter>() {
public UserTimelineFilter createFromParcel(Parcel source) {
UserTimelineFilter target = new UserTimelineFilter();
UserTimelineFilterParcelablePlease.readFromParcel(target, source);
return target;
}
public UserTimelineFilter[] newArray(int size) {
return new UserTimelineFilter[size];
}
};
}

View File

@ -1434,7 +1434,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val accountKeys = accountsAdapter.selectedAccountKeys val accountKeys = accountsAdapter.selectedAccountKeys
val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true) val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true)
val ignoreMentions = accounts.all { it.type == AccountType.TWITTER } val ignoreMentions = accounts.all { it.type == AccountType.TWITTER }
val tweetLength = validator.getTweetLength(text, ignoreMentions) val tweetLength = validator.getTweetLength(text, ignoreMentions &&
defaultFeatures.isMentionsCountsInStatus)
val maxLength = statusTextCount.maxLength val maxLength = statusTextCount.maxLength
if (accountsAdapter.isSelectionEmpty) { if (accountsAdapter.isSelectionEmpty) {
editText.error = getString(R.string.message_toast_no_account_selected) editText.error = getString(R.string.message_toast_no_account_selected)
@ -1491,10 +1492,10 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private fun updateTextCount() { private fun updateTextCount() {
val am = AccountManager.get(this) val am = AccountManager.get(this)
val text = editText.text?.toString() ?: return val text = editText.text?.toString() ?: return
val ignoreMentions = accountsAdapter.selectedAccountKeys.all { val ignoreMentions = inReplyToStatus != null && accountsAdapter.selectedAccountKeys.all {
val account = AccountUtils.findByAccountKey(am, it) ?: return@all false val account = AccountUtils.findByAccountKey(am, it) ?: return@all false
return@all account.getAccountType(am) == AccountType.TWITTER return@all account.getAccountType(am) == AccountType.TWITTER
} } && defaultFeatures.isMentionsCountsInStatus
statusTextCount.textCount = validator.getTweetLength(text, ignoreMentions) statusTextCount.textCount = validator.getTweetLength(text, ignoreMentions)
} }

View File

@ -40,13 +40,19 @@ import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
import org.mariotaku.twidere.annotation.PreviewStyle import org.mariotaku.twidere.annotation.PreviewStyle
import org.mariotaku.twidere.constant.* import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_DISPLAY_SENSITIVE_CONTENTS import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_DISPLAY_SENSITIVE_CONTENTS
import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.ItemCounts
import org.mariotaku.twidere.model.ObjectId
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.timeline.TimelineFilter
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler
import org.mariotaku.twidere.util.TwidereLinkify import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.view.holder.EmptyViewHolder import org.mariotaku.twidere.view.holder.EmptyViewHolder
import org.mariotaku.twidere.view.holder.GapViewHolder import org.mariotaku.twidere.view.holder.GapViewHolder
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
import org.mariotaku.twidere.view.holder.TimelineFilterHeaderViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
import java.util.* import java.util.*
@ -56,8 +62,8 @@ import java.util.*
abstract class ParcelableStatusesAdapter( abstract class ParcelableStatusesAdapter(
context: Context, context: Context,
requestManager: RequestManager requestManager: RequestManager
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context, requestManager), IStatusesAdapter<List<ParcelableStatus>>, ) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context, requestManager),
IItemCountsAdapter { IStatusesAdapter<List<ParcelableStatus>>, IItemCountsAdapter {
protected val inflater: LayoutInflater = LayoutInflater.from(context) protected val inflater: LayoutInflater = LayoutInflater.from(context)
@ -105,6 +111,12 @@ abstract class ParcelableStatusesAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
var timelineFilter: TimelineFilter? = null
set(value) {
field = value
notifyDataSetChanged()
}
private var data: List<ParcelableStatus>? = null private var data: List<ParcelableStatus>? = null
private var displayPositions: IntArray? = null private var displayPositions: IntArray? = null
private var displayDataCount: Int = 0 private var displayDataCount: Int = 0
@ -112,7 +124,7 @@ abstract class ParcelableStatusesAdapter(
private var showingActionCardId = RecyclerView.NO_ID private var showingActionCardId = RecyclerView.NO_ID
override val itemCounts = ItemCounts(4) override val itemCounts = ItemCounts(5)
val statusStartIndex: Int val statusStartIndex: Int
get() = getItemStartPosition(ITEM_INDEX_STATUS) get() = getItemStartPosition(ITEM_INDEX_STATUS)
@ -148,8 +160,8 @@ abstract class ParcelableStatusesAdapter(
if (data is ObjectCursor) { if (data is ObjectCursor) {
val cursor = (data as ObjectCursor).cursor val cursor = (data as ObjectCursor).cursor
if (!cursor.moveToPosition(dataPosition)) return false if (!cursor.moveToPosition(dataPosition)) return false
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices val indices = (data as ObjectCursor).indices
return cursor.getShort(indices.is_gap).toInt() == 1 return cursor.getShort(indices[Statuses.STATUS_ID]).toInt() == 1
} }
return getStatus(position).is_gap return getStatus(position).is_gap
} }
@ -205,8 +217,8 @@ abstract class ParcelableStatusesAdapter(
return mask + status.hashCode() return mask + status.hashCode()
} }
ITEM_INDEX_STATUS -> return getFieldValue(position, { cursor, indices -> ITEM_INDEX_STATUS -> return getFieldValue(position, { cursor, indices ->
val accountKey = UserKey.valueOf(cursor.getString(indices.account_key)) val accountKey = UserKey.valueOf(cursor.getString(indices[Statuses.ACCOUNT_KEY]))
val id = cursor.getString(indices.id) val id = cursor.getString(indices[Statuses.STATUS_ID])
return@getFieldValue ParcelableStatus.calculateHashCode(accountKey, id).toLong() return@getFieldValue ParcelableStatus.calculateHashCode(accountKey, id).toLong()
}, { status -> }, { status ->
return@getFieldValue status.hashCode().toLong() return@getFieldValue status.hashCode().toLong()
@ -217,7 +229,7 @@ abstract class ParcelableStatusesAdapter(
override fun getStatusId(position: Int, raw: Boolean): String { override fun getStatusId(position: Int, raw: Boolean): String {
return getFieldValue(position, { cursor, indices -> return getFieldValue(position, { cursor, indices ->
return@getFieldValue cursor.getString(indices.id) return@getFieldValue cursor.getString(indices[Statuses.STATUS_ID])
}, { status -> }, { status ->
return@getFieldValue status.id return@getFieldValue status.id
}, "") }, "")
@ -225,7 +237,7 @@ abstract class ParcelableStatusesAdapter(
fun getStatusSortId(position: Int, raw: Boolean): Long { fun getStatusSortId(position: Int, raw: Boolean): Long {
return getFieldValue(position, { cursor, indices -> return getFieldValue(position, { cursor, indices ->
return@getFieldValue cursor.safeGetLong(indices.sort_id) return@getFieldValue cursor.safeGetLong(indices[Statuses.SORT_ID])
}, { status -> }, { status ->
return@getFieldValue status.sort_id return@getFieldValue status.sort_id
}, -1L, raw) }, -1L, raw)
@ -233,7 +245,7 @@ abstract class ParcelableStatusesAdapter(
override fun getStatusTimestamp(position: Int, raw: Boolean): Long { override fun getStatusTimestamp(position: Int, raw: Boolean): Long {
return getFieldValue(position, { cursor, indices -> return getFieldValue(position, { cursor, indices ->
return@getFieldValue cursor.safeGetLong(indices.timestamp) return@getFieldValue cursor.safeGetLong(indices[Statuses.STATUS_TIMESTAMP])
}, { status -> }, { status ->
return@getFieldValue status.timestamp return@getFieldValue status.timestamp
}, -1L) }, -1L)
@ -241,9 +253,9 @@ abstract class ParcelableStatusesAdapter(
override fun getStatusPositionKey(position: Int, raw: Boolean): Long { override fun getStatusPositionKey(position: Int, raw: Boolean): Long {
return getFieldValue(position, { cursor, indices -> return getFieldValue(position, { cursor, indices ->
val positionKey = cursor.safeGetLong(indices.position_key) val positionKey = cursor.safeGetLong(indices[Statuses.POSITION_KEY])
if (positionKey > 0) return@getFieldValue positionKey if (positionKey > 0) return@getFieldValue positionKey
return@getFieldValue cursor.safeGetLong(indices.timestamp) return@getFieldValue cursor.safeGetLong(indices[Statuses.STATUS_TIMESTAMP])
}, { status -> }, { status ->
val positionKey = status.position_key val positionKey = status.position_key
if (positionKey > 0) return@getFieldValue positionKey if (positionKey > 0) return@getFieldValue positionKey
@ -254,7 +266,7 @@ abstract class ParcelableStatusesAdapter(
override fun getAccountKey(position: Int, raw: Boolean): UserKey { override fun getAccountKey(position: Int, raw: Boolean): UserKey {
val def: UserKey? = null val def: UserKey? = null
return getFieldValue(position, { cursor, indices -> return getFieldValue(position, { cursor, indices ->
return@getFieldValue UserKey.valueOf(cursor.getString(indices.account_key)) return@getFieldValue UserKey.valueOf(cursor.getString(indices[Statuses.ACCOUNT_KEY]))
}, { status -> }, { status ->
return@getFieldValue status.account_key return@getFieldValue status.account_key
}, def, raw)!! }, def, raw)!!
@ -281,9 +293,6 @@ abstract class ParcelableStatusesAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when (viewType) { when (viewType) {
VIEW_TYPE_STATUS -> {
return onCreateStatusViewHolder(parent) as RecyclerView.ViewHolder
}
ITEM_VIEW_TYPE_GAP -> { ITEM_VIEW_TYPE_GAP -> {
val view = inflater.inflate(GapViewHolder.layoutResource, parent, false) val view = inflater.inflate(GapViewHolder.layoutResource, parent, false)
return GapViewHolder(this, view) return GapViewHolder(this, view)
@ -292,9 +301,17 @@ abstract class ParcelableStatusesAdapter(
val view = inflater.inflate(R.layout.list_item_load_indicator, parent, false) val view = inflater.inflate(R.layout.list_item_load_indicator, parent, false)
return LoadIndicatorViewHolder(view) return LoadIndicatorViewHolder(view)
} }
VIEW_TYPE_STATUS -> {
return onCreateStatusViewHolder(parent) as RecyclerView.ViewHolder
}
VIEW_TYPE_EMPTY -> { VIEW_TYPE_EMPTY -> {
return EmptyViewHolder(Space(context)) return EmptyViewHolder(Space(context))
} }
VIEW_TYPE_FILTER_HEADER -> {
val view = inflater.inflate(TimelineFilterHeaderViewHolder.layoutResource,
parent, false)
return TimelineFilterHeaderViewHolder(this, view)
}
} }
throw IllegalStateException("Unknown view type " + viewType) throw IllegalStateException("Unknown view type " + viewType)
} }
@ -307,6 +324,9 @@ abstract class ParcelableStatusesAdapter(
(holder as IStatusViewHolder).displayStatus(status, displayInReplyTo = isShowInReplyTo, (holder as IStatusViewHolder).displayStatus(status, displayInReplyTo = isShowInReplyTo,
displayPinned = countIdx == ITEM_INDEX_PINNED_STATUS) displayPinned = countIdx == ITEM_INDEX_PINNED_STATUS)
} }
VIEW_TYPE_FILTER_HEADER -> {
(holder as TimelineFilterHeaderViewHolder).display(timelineFilter!!)
}
ITEM_VIEW_TYPE_GAP -> { ITEM_VIEW_TYPE_GAP -> {
val status = getStatus(position) val status = getStatus(position)
val loading = gapLoadingIds.any { it.accountKey == status.account_key && it.id == status.id } val loading = gapLoadingIds.any { it.accountKey == status.account_key && it.id == status.id }
@ -333,6 +353,9 @@ abstract class ParcelableStatusesAdapter(
return VIEW_TYPE_STATUS return VIEW_TYPE_STATUS
} }
} }
ITEM_INDEX_FILTER_HEADER -> {
return VIEW_TYPE_FILTER_HEADER
}
} }
throw AssertionError() throw AssertionError()
} }
@ -404,7 +427,7 @@ abstract class ParcelableStatusesAdapter(
} }
private inline fun <T> getFieldValue(position: Int, private inline fun <T> getFieldValue(position: Int,
readCursorValueAction: (cursor: Cursor, indices: ParcelableStatusCursorIndices) -> T, readCursorValueAction: (cursor: Cursor, indices: ObjectCursor.CursorIndices<ParcelableStatus>) -> T,
readStatusValueAction: (status: ParcelableStatus) -> T, readStatusValueAction: (status: ParcelableStatus) -> T,
defValue: T, raw: Boolean = false): T { defValue: T, raw: Boolean = false): T {
if (data is ObjectCursor) { if (data is ObjectCursor) {
@ -414,7 +437,7 @@ abstract class ParcelableStatusesAdapter(
} }
val cursor = (data as ObjectCursor).cursor val cursor = (data as ObjectCursor).cursor
if (!cursor.safeMoveToPosition(dataPosition)) return defValue if (!cursor.safeMoveToPosition(dataPosition)) return defValue
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices val indices = (data as ObjectCursor).indices
return readCursorValueAction(cursor, indices) return readCursorValueAction(cursor, indices)
} }
return readStatusValueAction(getStatus(position, raw)) return readStatusValueAction(getStatus(position, raw))
@ -443,6 +466,7 @@ abstract class ParcelableStatusesAdapter(
private fun updateItemCount() { private fun updateItemCount() {
itemCounts[ITEM_INDEX_LOAD_START_INDICATOR] = if (ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) 1 else 0 itemCounts[ITEM_INDEX_LOAD_START_INDICATOR] = if (ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) 1 else 0
itemCounts[ITEM_INDEX_FILTER_HEADER] = if (timelineFilter != null) 1 else 0
itemCounts[ITEM_INDEX_PINNED_STATUS] = pinnedStatuses?.size ?: 0 itemCounts[ITEM_INDEX_PINNED_STATUS] = pinnedStatuses?.size ?: 0
itemCounts[ITEM_INDEX_STATUS] = getStatusCount(false) itemCounts[ITEM_INDEX_STATUS] = getStatusCount(false)
itemCounts[ITEM_INDEX_LOAD_END_INDICATOR] = if (ILoadMoreSupportAdapter.END in loadMoreIndicatorPosition) 1 else 0 itemCounts[ITEM_INDEX_LOAD_END_INDICATOR] = if (ILoadMoreSupportAdapter.END in loadMoreIndicatorPosition) 1 else 0
@ -451,11 +475,13 @@ abstract class ParcelableStatusesAdapter(
companion object { companion object {
const val VIEW_TYPE_STATUS = 2 const val VIEW_TYPE_STATUS = 2
const val VIEW_TYPE_EMPTY = 3 const val VIEW_TYPE_EMPTY = 3
const val VIEW_TYPE_FILTER_HEADER = 4
const val ITEM_INDEX_LOAD_START_INDICATOR = 0 const val ITEM_INDEX_LOAD_START_INDICATOR = 0
const val ITEM_INDEX_PINNED_STATUS = 1 const val ITEM_INDEX_FILTER_HEADER = 1
const val ITEM_INDEX_STATUS = 2 const val ITEM_INDEX_PINNED_STATUS = 2
const val ITEM_INDEX_LOAD_END_INDICATOR = 3 const val ITEM_INDEX_STATUS = 3
const val ITEM_INDEX_LOAD_END_INDICATOR = 4
} }

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.constant
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.support.v4.util.ArraySet
import android.text.TextUtils import android.text.TextUtils
import org.apache.commons.lang3.LocaleUtils import org.apache.commons.lang3.LocaleUtils
import org.mariotaku.kpreferences.* import org.mariotaku.kpreferences.*
@ -18,6 +19,7 @@ import org.mariotaku.twidere.model.CustomAPIConfig
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.account.cred.Credentials import org.mariotaku.twidere.model.account.cred.Credentials
import org.mariotaku.twidere.model.sync.SyncProviderInfo import org.mariotaku.twidere.model.sync.SyncProviderInfo
import org.mariotaku.twidere.model.timeline.UserTimelineFilter
import org.mariotaku.twidere.preference.ThemeBackgroundPreference import org.mariotaku.twidere.preference.ThemeBackgroundPreference
import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
import java.util.* import java.util.*
@ -241,4 +243,29 @@ object composeAccountsKey : KSimpleKey<Array<UserKey>?>(KEY_COMPOSE_ACCOUNTS, nu
return true return true
} }
}
object userTimelineFilterKey : KSimpleKey<UserTimelineFilter>("user_timeline_filter", UserTimelineFilter()) {
override fun read(preferences: SharedPreferences): UserTimelineFilter {
val rawString = preferences.getString(key, null) ?: return def
val options = rawString.split(",")
return UserTimelineFilter().apply {
isIncludeReplies = "replies" in options
isIncludeRetweets = "retweets" in options
}
}
override fun write(editor: SharedPreferences.Editor, value: UserTimelineFilter): Boolean {
val options = ArraySet<String>().apply {
if (value.isIncludeReplies) {
add("replies")
}
if (value.isIncludeRetweets) {
add("retweets")
}
}.joinToString(",")
editor.putString(key, options)
return true
}
} }

View File

@ -57,6 +57,7 @@ import org.mariotaku.twidere.loader.iface.IExtendedLoader
import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.analyzer.Share import org.mariotaku.twidere.model.analyzer.Share
import org.mariotaku.twidere.model.event.StatusListChangedEvent import org.mariotaku.twidere.model.event.StatusListChangedEvent
import org.mariotaku.twidere.model.timeline.TimelineFilter
import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.*
@ -127,6 +128,10 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
get() = (parentFragment as? StatusesFragmentDelegate)?.shouldInitLoader ?: true get() = (parentFragment as? StatusesFragmentDelegate)?.shouldInitLoader ?: true
protected open val enableTimelineFilter: Boolean = false
protected open val timelineFilter: TimelineFilter? = null
// Fragment life cycles // Fragment life cycles
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
@ -311,6 +316,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
} }
// 2. Change adapter data // 2. Change adapter data
adapterData = data adapterData = data
adapter.timelineFilter = timelineFilter
refreshEnabled = true refreshEnabled = true
@ -490,7 +496,8 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
protected fun saveReadPosition(position: Int) { protected fun saveReadPosition(position: Int) {
if (host == null) return if (host == null) return
if (position == RecyclerView.NO_POSITION || adapter.getStatusCount(false) <= 0) return if (position == RecyclerView.NO_POSITION || adapter.getStatusCount(false) <= 0) return
val status = adapter.getStatus(position) val status = adapter.getStatus(position.coerceIn(rangeOfSize(adapter.statusStartIndex,
adapter.getStatusCount(false))))
val readPosition = if (useSortIdAsReadPosition) { val readPosition = if (useSortIdAsReadPosition) {
status.sort_id status.sort_id
} else { } else {

View File

@ -49,7 +49,6 @@ import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_QUICK_SEND import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_QUICK_SEND
import org.mariotaku.twidere.extension.applyTheme import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.getTweetLength import org.mariotaku.twidere.extension.getTweetLength
import org.mariotaku.twidere.extension.model.getAccountType
import org.mariotaku.twidere.extension.model.textLimit import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.model.util.AccountUtils
@ -200,9 +199,7 @@ class RetweetQuoteDialogFragment : BaseDialogFragment() {
} }
val textCountView = dialog.findViewById(R.id.commentTextCount) as StatusTextCountView val textCountView = dialog.findViewById(R.id.commentTextCount) as StatusTextCountView
val am = AccountManager.get(context) val am = AccountManager.get(context)
val ignoreMentions = AccountUtils.findByAccountKey(am, accountKey)?.getAccountType(am) == textCountView.textCount = validator.getTweetLength(s.toString(), false)
AccountType.TWITTER
textCountView.textCount = validator.getTweetLength(s.toString(), ignoreMentions)
} }
private val status: ParcelableStatus private val status: ParcelableStatus

View File

@ -1425,22 +1425,12 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
tabArgs.putParcelable(EXTRA_USER_KEY, args.getParcelable<Parcelable>(EXTRA_USER_KEY)) tabArgs.putParcelable(EXTRA_USER_KEY, args.getParcelable<Parcelable>(EXTRA_USER_KEY))
tabArgs.putString(EXTRA_SCREEN_NAME, args.getString(EXTRA_SCREEN_NAME)) tabArgs.putString(EXTRA_SCREEN_NAME, args.getString(EXTRA_SCREEN_NAME))
} }
if (userKey?.host == USER_TYPE_TWITTER_COM) { pagerAdapter.add(cls = UserTimelineFragment::class.java, args = Bundle(tabArgs).apply {
pagerAdapter.add(cls = UserTimelineFragment::class.java, args = Bundle(tabArgs).apply { this[UserTimelineFragment.EXTRA_ENABLE_TIMELINE_FILTER] = true
putBoolean(EXTRA_EXCLUDE_REPLIES, true) }, name = getString(R.string.title_statuses), type = TAB_TYPE_STATUSES,
}, name = getString(R.string.title_statuses), type = TAB_TYPE_STATUSES, position = TAB_POSITION_STATUSES)
position = TAB_POSITION_STATUSES)
pagerAdapter.add(cls = UserTimelineFragment::class.java, args = tabArgs,
name = getString(R.string.title_statuses_and_replies), type = TAB_TYPE_STATUSES_WITH_REPLIES,
position = TAB_POSITION_STATUSES)
} else {
pagerAdapter.add(cls = UserTimelineFragment::class.java, args = tabArgs,
name = getString(R.string.title_statuses), type = TAB_TYPE_STATUSES,
position = TAB_POSITION_STATUSES)
}
pagerAdapter.add(cls = UserMediaTimelineFragment::class.java, args = tabArgs, pagerAdapter.add(cls = UserMediaTimelineFragment::class.java, args = tabArgs,
name = getString(R.string.media), type = TAB_TYPE_MEDIA, name = getString(R.string.media), type = TAB_TYPE_MEDIA, position = TAB_POSITION_MEDIA)
position = TAB_POSITION_MEDIA)
if (preferences.getBoolean(KEY_I_WANT_MY_STARS_BACK)) { if (preferences.getBoolean(KEY_I_WANT_MY_STARS_BACK)) {
pagerAdapter.add(cls = UserFavoritesFragment::class.java, args = tabArgs, pagerAdapter.add(cls = UserFavoritesFragment::class.java, args = tabArgs,
name = getString(R.string.title_favorites), type = TAB_TYPE_FAVORITES, name = getString(R.string.title_favorites), type = TAB_TYPE_FAVORITES,

View File

@ -9,9 +9,11 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager import android.support.v7.widget.StaggeredGridLayoutManager
import android.text.TextUtils import android.text.TextUtils
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import org.mariotaku.kpreferences.get
import org.mariotaku.twidere.adapter.StaggeredGridParcelableStatusesAdapter import org.mariotaku.twidere.adapter.StaggeredGridParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.constant.IntentConstants.* import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.userTimelineFilterKey
import org.mariotaku.twidere.extensions.reachingEnd import org.mariotaku.twidere.extensions.reachingEnd
import org.mariotaku.twidere.extensions.reachingStart import org.mariotaku.twidere.extensions.reachingStart
import org.mariotaku.twidere.loader.MediaTimelineLoader import org.mariotaku.twidere.loader.MediaTimelineLoader

View File

@ -19,15 +19,25 @@
package org.mariotaku.twidere.fragment package org.mariotaku.twidere.fragment
import android.app.Dialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v4.content.Loader import android.support.v4.content.Loader
import android.support.v7.app.AlertDialog
import edu.tsinghua.hotmobi.model.TimelineType import edu.tsinghua.hotmobi.model.TimelineType
import org.mariotaku.kpreferences.get
import org.mariotaku.kpreferences.set
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.* import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.constant.userTimelineFilterKey
import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.loader.UserTimelineLoader import org.mariotaku.twidere.loader.UserTimelineLoader
import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.timeline.TimelineFilter
import org.mariotaku.twidere.model.timeline.UserTimelineFilter
import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.view.holder.TimelineFilterHeaderViewHolder
import java.util.* import java.util.*
/** /**
@ -42,11 +52,9 @@ class UserTimelineFragment : ParcelableStatusesFragment() {
override val savedStatusesFileArgs: Array<String>? override val savedStatusesFileArgs: Array<String>?
get() { get() {
val args = arguments!! val accountKey = Utils.getAccountKey(context, arguments)!!
val accountKey = Utils.getAccountKey(context, args)!! val userKey = arguments.getParcelable<UserKey>(EXTRA_USER_KEY)
val userKey = args.getParcelable<UserKey>(EXTRA_USER_KEY) val screenName = arguments.getString(EXTRA_SCREEN_NAME)
val screenName = args.getString(EXTRA_SCREEN_NAME)
val excludeReplies = args.getBoolean(EXTRA_EXCLUDE_REPLIES)
val result = ArrayList<String>() val result = ArrayList<String>()
result.add(AUTHORITY_USER_TIMELINE) result.add(AUTHORITY_USER_TIMELINE)
result.add("account=$accountKey") result.add("account=$accountKey")
@ -57,21 +65,24 @@ class UserTimelineFragment : ParcelableStatusesFragment() {
} else { } else {
return null return null
} }
if (excludeReplies) { (timelineFilter as? UserTimelineFilter)?.let {
result.add("exclude_replies") if (it.isIncludeReplies) {
result.add("include_replies")
}
if (it.isIncludeRetweets) {
result.add("include_retweets")
}
} }
return result.toTypedArray() return result.toTypedArray()
} }
override val readPositionTagWithArguments: String? override val readPositionTagWithArguments: String?
get() { get() {
val args = arguments!! if (arguments.getLong(EXTRA_TAB_ID, -1) < 0) return null
val tabPosition = args.getInt(EXTRA_TAB_POSITION, -1)
val sb = StringBuilder("user_timeline_") val sb = StringBuilder("user_timeline_")
if (tabPosition < 0) return null
val userKey = args.getParcelable<UserKey>(EXTRA_USER_KEY) val userKey = arguments.getParcelable<UserKey>(EXTRA_USER_KEY)
val screenName = args.getString(EXTRA_SCREEN_NAME) val screenName = arguments.getString(EXTRA_SCREEN_NAME)
if (userKey != null) { if (userKey != null) {
sb.append(userKey) sb.append(userKey)
} else if (screenName != null) { } else if (screenName != null) {
@ -82,6 +93,12 @@ class UserTimelineFragment : ParcelableStatusesFragment() {
return sb.toString() return sb.toString()
} }
override val enableTimelineFilter: Boolean
get() = arguments.getBoolean(EXTRA_ENABLE_TIMELINE_FILTER)
override val timelineFilter: TimelineFilter?
get() = if (enableTimelineFilter) preferences[userTimelineFilterKey] else null
override fun onCreateStatusesLoader(context: Context, args: Bundle, fromUser: Boolean): override fun onCreateStatusesLoader(context: Context, args: Bundle, fromUser: Boolean):
Loader<List<ParcelableStatus>?> { Loader<List<ParcelableStatus>?> {
refreshing = true refreshing = true
@ -93,10 +110,10 @@ class UserTimelineFragment : ParcelableStatusesFragment() {
val screenName = args.getString(EXTRA_SCREEN_NAME) val screenName = args.getString(EXTRA_SCREEN_NAME)
val tabPosition = args.getInt(EXTRA_TAB_POSITION, -1) val tabPosition = args.getInt(EXTRA_TAB_POSITION, -1)
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false) val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false)
val excludeReplies = args.getBoolean(EXTRA_EXCLUDE_REPLIES, false)
val pinnedIds = if (adapter.hasPinnedStatuses) null else pinnedStatusIds val pinnedIds = if (adapter.hasPinnedStatuses) null else pinnedStatusIds
return UserTimelineLoader(context, accountKey, userKey, screenName, sinceId, maxId, data, return UserTimelineLoader(context, accountKey, userKey, screenName, sinceId, maxId, data,
savedStatusesFileArgs, tabPosition, fromUser, loadingMore, pinnedIds, excludeReplies) savedStatusesFileArgs, tabPosition, fromUser, loadingMore, pinnedIds,
timelineFilter as? UserTimelineFilter)
} }
override fun onStatusesLoaded(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) { override fun onStatusesLoaded(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) {
@ -107,7 +124,63 @@ class UserTimelineFragment : ParcelableStatusesFragment() {
super.onStatusesLoaded(loader, data) super.onStatusesLoaded(loader, data)
} }
override fun onFilterClick(holder: TimelineFilterHeaderViewHolder) {
val df = UserTimelineFilterDialogFragment()
df.setTargetFragment(this, REQUEST_SET_TIMELINE_FILTER)
df.show(childFragmentManager, "set_timeline_filter")
}
private fun reloadAllStatuses() {
adapterData = null
triggerRefresh()
showProgress()
}
interface UserTimelineFragmentDelegate { interface UserTimelineFragmentDelegate {
val pinnedStatusIds: Array<String>? val pinnedStatusIds: Array<String>?
}
class UserTimelineFilterDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context)
val values = resources.getStringArray(R.array.values_user_timeline_filter)
val checkedItems = BooleanArray(values.size) {
val filter = preferences[userTimelineFilterKey]
when (values[it]) {
"replies" -> filter.isIncludeReplies
"retweets" -> filter.isIncludeRetweets
else -> false
}
}
builder.setTitle(R.string.title_user_timeline_filter)
builder.setMultiChoiceItems(R.array.entries_user_timeline_filter, checkedItems, null)
builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(android.R.string.ok) { dialog, _ ->
dialog as AlertDialog
val listView = dialog.listView
val filter = UserTimelineFilter().apply {
isIncludeRetweets = listView.isItemChecked(values.indexOf("retweets"))
isIncludeReplies = listView.isItemChecked(values.indexOf("replies"))
}
preferences.edit().apply {
this[userTimelineFilterKey] = filter
}.apply()
(targetFragment as UserTimelineFragment).reloadAllStatuses()
}
val dialog = builder.create()
dialog.setOnShowListener {
it as AlertDialog
it.applyTheme()
}
return dialog
}
}
companion object {
const val EXTRA_ENABLE_TIMELINE_FILTER = "enable_timeline_filter"
const val REQUEST_SET_TIMELINE_FILTER = 101
} }
} }

View File

@ -33,6 +33,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.timeline.UserTimelineFilter
import org.mariotaku.twidere.model.util.ParcelableStatusUtils import org.mariotaku.twidere.model.util.ParcelableStatusUtils
import org.mariotaku.twidere.util.InternalTwitterContentUtils import org.mariotaku.twidere.util.InternalTwitterContentUtils
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
@ -50,7 +51,7 @@ class UserTimelineLoader(
fromUser: Boolean, fromUser: Boolean,
loadingMore: Boolean, loadingMore: Boolean,
val pinnedStatusIds: Array<String>?, val pinnedStatusIds: Array<String>?,
val excludeReplies: Boolean = false val timelineFilter: UserTimelineFilter? = null
) : MicroBlogAPIStatusesLoader(context, accountId, sinceId, maxId, -1, data, savedStatusesArgs, ) : MicroBlogAPIStatusesLoader(context, accountId, sinceId, maxId, -1, data, savedStatusesArgs,
tabPosition, fromUser, loadingMore) { tabPosition, fromUser, loadingMore) {
@ -80,7 +81,10 @@ class UserTimelineLoader(
} }
} }
val option = TimelineOption() val option = TimelineOption()
option.setExcludeReplies(excludeReplies) if (timelineFilter != null) {
option.setExcludeReplies(!timelineFilter.isIncludeReplies)
option.setIncludeRetweets(timelineFilter.isIncludeRetweets)
}
if (userId != null) { if (userId != null) {
return microBlog.getUserTimeline(userId.id, paging, option) return microBlog.getUserTimeline(userId.id, paging, option)
} else if (screenName != null) { } else if (screenName != null) {

View File

@ -4,6 +4,7 @@ import android.content.Context
import com.squareup.otto.Bus import com.squareup.otto.Bus
import org.mariotaku.abstask.library.AbstractTask import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.kpreferences.KPreferences import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.twidere.model.DefaultFeatures
import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.media.MediaPreloader import org.mariotaku.twidere.util.media.MediaPreloader
@ -38,6 +39,8 @@ abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context)
@Inject @Inject
lateinit var extraFeaturesService: ExtraFeaturesService lateinit var extraFeaturesService: ExtraFeaturesService
@Inject @Inject
lateinit var defaultFeatures: DefaultFeatures
@Inject
lateinit var scheduleProviderFactory: StatusScheduleProvider.Factory lateinit var scheduleProviderFactory: StatusScheduleProvider.Factory
val scheduleProvider: StatusScheduleProvider? val scheduleProvider: StatusScheduleProvider?

View File

@ -14,6 +14,7 @@ import android.support.annotation.WorkerThread
import android.text.TextUtils import android.text.TextUtils
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.twitter.Validator
import edu.tsinghua.hotmobi.HotMobiLogger import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.MediaUploadEvent import edu.tsinghua.hotmobi.model.MediaUploadEvent
import net.ypresto.androidtranscoder.MediaTranscoder import net.ypresto.androidtranscoder.MediaTranscoder
@ -36,6 +37,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.* import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.app.TwidereApplication import org.mariotaku.twidere.app.TwidereApplication
import org.mariotaku.twidere.extension.getTweetLength
import org.mariotaku.twidere.extension.model.mediaSizeLimit import org.mariotaku.twidere.extension.model.mediaSizeLimit
import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.extension.model.textLimit import org.mariotaku.twidere.extension.model.textLimit
@ -217,13 +219,16 @@ class UpdateStatusTask(
update: ParcelableStatusUpdate, update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) { pending: PendingStatusUpdate) {
if (shortener == null) return if (shortener == null) return
val validator = Validator()
stateCallback.onShorteningStatus() stateCallback.onShorteningStatus()
val sharedShortened = HashMap<UserKey, StatusShortenResult>() val sharedShortened = HashMap<UserKey, StatusShortenResult>()
for (i in 0 until pending.length) { for (i in 0 until pending.length) {
val account = update.accounts[i] val account = update.accounts[i]
val text = pending.overrideTexts[i] val text = pending.overrideTexts[i]
val textLimit = account.textLimit val textLimit = account.textLimit
if (textLimit >= 0 && text.length <= textLimit) { val ignoreMentions = update.in_reply_to_status != null && account.type ==
AccountType.TWITTER && defaultFeatures.isMentionsCountsInStatus
if (textLimit >= 0 && validator.getTweetLength(text, ignoreMentions) <= textLimit) {
continue continue
} }
shortener.waitForService() shortener.waitForService()
@ -403,6 +408,9 @@ class UpdateStatusTask(
status.location(ParcelableLocationUtils.toGeoLocation(statusUpdate.location)) status.location(ParcelableLocationUtils.toGeoLocation(statusUpdate.location))
status.displayCoordinates(statusUpdate.display_coordinates) status.displayCoordinates(statusUpdate.display_coordinates)
} }
if (statusUpdate.accounts[index].type == AccountType.TWITTER) {
status.autoPopulateReplyMetadata(true)
}
val mediaIds = pendingUpdate.mediaIds[index] val mediaIds = pendingUpdate.mediaIds[index]
if (mediaIds != null) { if (mediaIds != null) {
status.mediaIds(*mediaIds) status.mediaIds(*mediaIds)

View File

@ -0,0 +1,51 @@
/*
* 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.view.holder
import android.support.v7.widget.RecyclerView
import android.view.View
import kotlinx.android.synthetic.main.header_user_timeline_filter.view.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
import org.mariotaku.twidere.model.timeline.TimelineFilter
/**
* Created by mariotaku on 2017/3/31.
*/
class TimelineFilterHeaderViewHolder(val adapter: IStatusesAdapter<*>, itemView: View) : RecyclerView.ViewHolder(itemView) {
private val filterLabel = itemView.filterLabel
private val filterButton = itemView.filterButton
init {
filterButton.setOnClickListener {
adapter.statusClickListener?.onFilterClick(this)
}
}
companion object {
const val layoutResource = R.layout.header_user_timeline_filter
}
fun display(filter: TimelineFilter) {
filterLabel.text = filter.getSummary(itemView.context)
}
}

View File

@ -28,6 +28,7 @@ import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.view.CardMediaContainer import org.mariotaku.twidere.view.CardMediaContainer
import org.mariotaku.twidere.view.holder.TimelineFilterHeaderViewHolder
/** /**
* Created by mariotaku on 15/10/26. * Created by mariotaku on 15/10/26.
@ -62,6 +63,8 @@ interface IStatusViewHolder : CardMediaContainer.OnMediaClickListener {
fun onStatusLongClick(holder: IStatusViewHolder, position: Int): Boolean = false fun onStatusLongClick(holder: IStatusViewHolder, position: Int): Boolean = false
fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {} fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {}
fun onFilterClick(holder: TimelineFilterHeaderViewHolder) {}
} }
} }

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="@dimen/element_size_msmall"
android:orientation="horizontal"
android:padding="@dimen/element_spacing_small">
<TextView
android:id="@+id/filterLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:padding="@dimen/element_spacing_normal"
tools:text="Tweets, retweets, replies"/>
<org.mariotaku.twidere.view.IconActionButton
android:id="@+id/filterButton"
android:layout_width="@dimen/element_size_msmall"
android:layout_height="@dimen/element_size_msmall"
android:layout_weight="0"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_action_filter"
app:iabColor="?android:textColorSecondary"/>
</LinearLayout>

View File

@ -80,4 +80,8 @@
<item>List</item> <item>List</item>
<item>List timeline</item> <item>List timeline</item>
</string-array> </string-array>
<string-array name="entries_user_timeline_filter">
<item>Replies</item>
<item>Retweets</item>
</string-array>
</resources> </resources>

View File

@ -103,4 +103,8 @@
<item>list</item> <item>list</item>
<item>list_timeline</item> <item>list_timeline</item>
</string-array> </string-array>
<string-array name="values_user_timeline_filter">
<item>replies</item>
<item>retweets</item>
</string-array>
</resources> </resources>

View File

@ -1289,4 +1289,9 @@
<string name="users_lists_with_name"><xliff:g id="name">%s</xliff:g>\'s lists</string> <string name="users_lists_with_name"><xliff:g id="name">%s</xliff:g>\'s lists</string>
<string name="users_statuses">User\'s tweets</string> <string name="users_statuses">User\'s tweets</string>
<string name="hint_search_gif">Search GIF</string> <string name="hint_search_gif">Search GIF</string>
<string name="title_user_timeline_filter">Timeline filter</string>
<string name="label_statuses">Tweets</string>
<string name="label_statuses_retweets">Tweets and retweets</string>
<string name="label_statuses_replies">Tweets and replies</string>
<string name="label_statuses_retweets_replies">Tweets, retweets and replies</string>
</resources> </resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
<title>ic_action_filter-mdpi</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Action-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="ic_action_filter-mdpi">
<path d="M14,22 L18,22 L18,20 L14,20 L14,22 Z M7,10 L7,12 L25,12 L25,10 L7,10 Z M10,17 L22,17 L22,15 L10,15 L10,17 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
<polygon id="Shape" points="4 4 28 4 28 28 4 28"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 774 B