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

fixed conversation order for retweet

supports notification enabled user displaying
This commit is contained in:
Mariotaku Lee 2017-03-18 17:10:56 +08:00
parent 0f86fc75a4
commit 215272b447
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
17 changed files with 103 additions and 54 deletions

View File

@ -231,6 +231,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
int NOTIFICATION_ID_DIRECT_MESSAGES = 3;
int NOTIFICATION_ID_DRAFTS = 4;
int NOTIFICATION_ID_DATA_PROFILING = 5;
int NOTIFICATION_ID_USER_NOTIFICATION = 10;
int NOTIFICATION_ID_UPDATE_STATUS = 101;
int NOTIFICATION_ID_SEND_DIRECT_MESSAGE = 102;

View File

@ -29,6 +29,7 @@ import org.mariotaku.sqliteqb.library.Tables;
import org.mariotaku.sqliteqb.library.query.SQLSelectQuery;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships;
import org.mariotaku.twidere.util.TwidereArrayUtils;
import org.mariotaku.twidere.util.Utils;
@ -60,17 +61,17 @@ public final class CachedUsersQueryBuilder {
final UserKey accountKey) {
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(select).from(new Tables(TwidereDataStore.CachedUsers.TABLE_NAME));
final Columns.Column relationshipsUserId = new Columns.Column(new Table(TwidereDataStore.CachedRelationships.TABLE_NAME),
TwidereDataStore.CachedRelationships.USER_KEY);
final Columns.Column relationshipsUserId = new Columns.Column(new Table(CachedRelationships.TABLE_NAME),
CachedRelationships.USER_KEY);
final Columns.Column usersUserId = new Columns.Column(new Table(TwidereDataStore.CachedUsers.TABLE_NAME),
TwidereDataStore.CachedRelationships.USER_KEY);
final Columns.Column relationshipsAccountId = new Columns.Column(new Table(TwidereDataStore.CachedRelationships.TABLE_NAME),
TwidereDataStore.CachedRelationships.ACCOUNT_KEY);
CachedRelationships.USER_KEY);
final Columns.Column relationshipsAccountId = new Columns.Column(new Table(CachedRelationships.TABLE_NAME),
CachedRelationships.ACCOUNT_KEY);
final Expression on = Expression.and(
Expression.equals(relationshipsUserId, usersUserId),
Expression.equalsArgs(relationshipsAccountId.getSQL())
);
qb.join(new Join(false, Join.Operation.LEFT, new Table(TwidereDataStore.CachedRelationships.TABLE_NAME), on));
qb.join(new Join(false, Join.Operation.LEFT, new Table(CachedRelationships.TABLE_NAME), on));
final Expression userTypeExpression;
final String host = accountKey.getHost();
final String[] accountKeyArgs;
@ -113,10 +114,10 @@ public final class CachedUsersQueryBuilder {
columns[i] = new Columns.Column(column);
}
}
final String expr = String.format(Locale.ROOT, "%s * 100 + %s * 50 - %s * 100 - %s * 100 - %s * 100",
valueOrZero(TwidereDataStore.CachedRelationships.FOLLOWING, TwidereDataStore.CachedRelationships.FOLLOWED_BY,
TwidereDataStore.CachedRelationships.BLOCKING, TwidereDataStore.CachedRelationships.BLOCKED_BY,
TwidereDataStore.CachedRelationships.MUTING));
final String expr = String.format(Locale.ROOT, "%s * 100 + %s * 50 + %s * 50 - %s * 100 - %s * 100 - %s * 100",
valueOrZero(CachedRelationships.FOLLOWING, CachedRelationships.NOTIFICATIONS_ENABLED,
CachedRelationships.FOLLOWED_BY, CachedRelationships.BLOCKING,
CachedRelationships.BLOCKED_BY, CachedRelationships.MUTING));
columns[columns.length - 1] = new Columns.Column(expr, "score");
qb.select(select);
final Pair<SQLSelectQuery, String[]> pair = withRelationship(new Columns(columns), null,

View File

@ -151,7 +151,8 @@ class ParcelableActivitiesAdapter(
override fun getActivity(position: Int, raw: Boolean): ParcelableActivity {
val dataPosition = position - activityStartIndex
if (dataPosition < 0 || dataPosition >= data!!.size) {
val activityCount = getActivityCount(raw)
if (dataPosition < 0 || dataPosition >= activityCount) {
val validRange = rangeOfSize(activityStartIndex, getActivityCount(raw))
throw IndexOutOfBoundsException("index: $position, valid range is $validRange")
}

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.adapter
import android.content.Context
import android.database.Cursor
import android.database.CursorIndexOutOfBoundsException
import android.support.v4.widget.Space
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
@ -411,7 +412,9 @@ abstract class ParcelableStatusesAdapter(
defValue: T, raw: Boolean = false): T {
if (data is ObjectCursor) {
val dataPosition = position - statusStartIndex
if (dataPosition < 0 || dataPosition >= getStatusCount(true)) return defValue
if (dataPosition < 0 || dataPosition >= getStatusCount(true)) {
throw CursorIndexOutOfBoundsException("index: $position, valid range is $0..${getStatusCount(true)}")
}
val cursor = (data as ObjectCursor).cursor
if (!cursor.safeMoveToPosition(dataPosition)) return defValue
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices

View File

@ -463,7 +463,7 @@ abstract class AbsActivitiesFragment protected constructor() :
protected fun saveReadPosition(position: Int) {
if (host == null) return
if (position == RecyclerView.NO_POSITION) return
if (position == RecyclerView.NO_POSITION || adapter.getActivityCount(false) <= 0) return
val item = adapter.getActivity(position)
var positionUpdated = false
readPositionTag?.let {

View File

@ -58,8 +58,7 @@ import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.analyzer.Share
import org.mariotaku.twidere.model.event.StatusListChangedEvent
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback
import org.mariotaku.twidere.util.glide.PauseRecyclerViewOnScrollListener
@ -485,7 +484,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
protected fun saveReadPosition(position: Int) {
if (host == null) return
if (position == RecyclerView.NO_POSITION) return
if (position == RecyclerView.NO_POSITION || adapter.getStatusCount(false) <= 0) return
val status = adapter.getStatus(position)
val positionKey = if (status.position_key > 0) status.position_key else status.timestamp
readPositionTagWithArguments?.let {

View File

@ -835,7 +835,7 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
private fun loadAccountsInfo(): AccountsInfo {
val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(context), true)
val draftsCount = DataStoreUtils.queryCount(context, Drafts.CONTENT_URI_UNSENT, null, null)
val draftsCount = DataStoreUtils.queryCount(context.contentResolver, Drafts.CONTENT_URI_UNSENT, null, null)
return AccountsInfo(accounts, draftsCount)
}
}

View File

@ -285,6 +285,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
if (status == null || data == null || data.isEmpty()) return
val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
val lastVisiblePosition = layoutManager.findLastVisibleItemPosition()
if (firstVisiblePosition < 0 || lastVisiblePosition < 0) return
val startIndex = adapter.statusStartIndex
for (i in firstVisiblePosition..lastVisiblePosition) {
if (status.account_key == adapter.getAccountKey(i) && status.id == adapter.getStatusId(i)) {

View File

@ -1628,13 +1628,11 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
replyStart = -1
} else {
var sortId = status.sort_id
if (status.is_retweet) {
for (item in data) {
if (TextUtils.equals(status.retweet_id, item.id)) {
sortId = item.sort_id
break
}
}
sortId = data.find {
it.id == status.retweet_id
}?.sort_id ?: status.retweet_timestamp
}
var conversationCount = 0
var replyCount = 0

View File

@ -260,7 +260,7 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
val valueWhereArgs = arrayOf(text)
val idWhere = Expression.equalsArgs(Filters._ID).sql
val idWhereArgs = arrayOf(id.toString())
if (DataStoreUtils.queryCount(context, uri, valueWhere, valueWhereArgs) == 0) {
if (DataStoreUtils.queryCount(resolver, uri, valueWhere, valueWhereArgs) == 0) {
resolver.update(uri, values, idWhere, idWhereArgs)
} else {
Toast.makeText(context, R.string.message_toast_duplicate_filter_rule,

View File

@ -93,6 +93,9 @@ class AccountPreferences(private val context: Context, val accountKey: UserKey)
val isStreamDirectMessagesEnabled: Boolean
get() = preferences.getBoolean("stream_direct_messages", true)
val isStreamNotificationUsersEnabled: Boolean
get() = preferences.getBoolean("stream_notification_users", true)
val isDirectMessagesNotificationEnabled: Boolean
get() = preferences.getBoolean(KEY_DIRECT_MESSAGES_NOTIFICATION, DEFAULT_DIRECT_MESSAGES_NOTIFICATION)

View File

@ -28,7 +28,7 @@ import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.model.ParcelableRelationship
import org.mariotaku.twidere.model.ParcelableUser
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships
import org.mariotaku.twidere.util.content.ContentResolverUtils
object ParcelableRelationshipUtils {

View File

@ -25,7 +25,10 @@ import org.mariotaku.sqliteqb.library.Columns
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.TwidereConstants.NOTIFICATION_ID_USER_NOTIFICATION
import org.mariotaku.twidere.activity.LinkHandlerActivity
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.streamingEnabledKey
import org.mariotaku.twidere.constant.streamingNonMeteredNetworkKey
import org.mariotaku.twidere.constant.streamingPowerSavingKey
@ -36,14 +39,11 @@ import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.model.util.ParcelableActivityUtils
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.model.util.UserKeyUtils
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.task.twitter.GetActivitiesAboutMeTask
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.DebugLog
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.DependencyHolder
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.streaming.TwitterTimelineStreamCallback
@ -57,7 +57,7 @@ class StreamingService : BaseService() {
internal lateinit var threadPoolExecutor: ExecutorService
internal lateinit var handler: Handler
private val submittedTasks = WeakHashMap<UserKey, StreamingRunnable<*>>()
private val submittedTasks: MutableMap<UserKey, StreamingRunnable<*>> = WeakHashMap()
private val accountChangeObserver = OnAccountsUpdateListener {
if (!setupStreaming()) {
@ -181,7 +181,7 @@ class StreamingService : BaseService() {
private fun newStreamingRunnable(account: AccountDetails, preferences: AccountPreferences): StreamingRunnable<*>? {
when (account.type) {
AccountType.TWITTER -> {
return TwitterStreamingRunnable(this, handler, account, preferences)
return TwitterStreamingRunnable(this, account, preferences)
}
}
return null
@ -190,7 +190,7 @@ class StreamingService : BaseService() {
internal abstract class StreamingRunnable<T>(
val context: Context,
val account: AccountDetails,
val preferences: AccountPreferences
val accountPreferences: AccountPreferences
) : Runnable {
var cancelled: Boolean = false
@ -222,12 +222,11 @@ class StreamingService : BaseService() {
abstract fun onCancelled()
}
internal class TwitterStreamingRunnable(
internal inner class TwitterStreamingRunnable(
context: Context,
val handler: Handler,
account: AccountDetails,
preferences: AccountPreferences
) : StreamingRunnable<TwitterUserStream>(context, account, preferences) {
accountPreferences: AccountPreferences
) : StreamingRunnable<TwitterUserStream>(context, account, accountPreferences) {
private val profileImageSize = context.getString(R.string.profile_image_size)
private val isOfficial = account.isOfficial(context)
@ -257,7 +256,7 @@ class StreamingService : BaseService() {
}
override fun onHomeTimeline(status: Status): Boolean {
if (!preferences.isStreamHomeTimelineEnabled) {
if (!accountPreferences.isStreamHomeTimelineEnabled) {
homeInsertGap = true
return false
}
@ -284,7 +283,7 @@ class StreamingService : BaseService() {
}
override fun onActivityAboutMe(activity: Activity): Boolean {
if (!preferences.isStreamInteractionsEnabled) {
if (!accountPreferences.isStreamInteractionsEnabled) {
interactionsInsertGap = true
return false
}
@ -309,7 +308,7 @@ class StreamingService : BaseService() {
@WorkerThread
override fun onDirectMessage(directMessage: DirectMessage): Boolean {
if (!preferences.isStreamDirectMessagesEnabled) {
if (!accountPreferences.isStreamDirectMessagesEnabled) {
return false
}
if (canGetMessages) {
@ -321,6 +320,38 @@ class StreamingService : BaseService() {
return true
}
override fun onAllStatus(status: Status) {
if (!accountPreferences.isStreamNotificationUsersEnabled) {
return
}
val user = status.user ?: return
val userKey = UserKeyUtils.fromUser(user)
val where = Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY),
Expression.equalsArgs(CachedRelationships.USER_KEY),
Expression.equalsArgs(CachedRelationships.NOTIFICATIONS_ENABLED)).sql
val whereArgs = arrayOf(account.key.toString(), userKey.toString(), "1")
if (DataStoreUtils.queryCount(context.contentResolver, CachedRelationships.CONTENT_URI,
where, whereArgs) <= 0) return
// Build favorited user notifications
val userDisplayName = userColorNameManager.getDisplayName(user,
preferences[nameFirstKey])
val statusUri = LinkCreator.getTwidereStatusLink(account.key, status.id)
val builder = NotificationCompat.Builder(context)
builder.color = userColorNameManager.getUserColor(userKey)
builder.setAutoCancel(true)
builder.setWhen(status.createdAt?.time ?: 0)
builder.setSmallIcon(R.drawable.ic_stat_twitter)
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
builder.setContentTitle(context.getString(R.string.notification_title_new_status_by_user, userDisplayName))
builder.setContentText(InternalTwitterContentUtils.formatStatusTextWithIndices(status).text)
builder.setContentIntent(PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, statusUri).apply {
setClass(context, LinkHandlerActivity::class.java)
}, PendingIntent.FLAG_UPDATE_CURRENT))
val tag = "${account.key}:$userKey:${status.id}"
notificationManager.notify(tag, NOTIFICATION_ID_USER_NOTIFICATION, builder.build())
}
override fun onStatusDeleted(event: DeletionEvent): Boolean {
val deleteWhere = Expression.and(Expression.likeRaw(Columns.Column(Statuses.ACCOUNT_KEY), "'%@'||?"),
Expression.equalsArgs(Columns.Column(Statuses.STATUS_ID))).sql

View File

@ -185,9 +185,8 @@ object DataStoreUtils {
fun getOldestStatusSortIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY,
Statuses.SORT_ID, OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)), null,
null)
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID,
OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)), null, null)
}
fun getNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
@ -217,13 +216,13 @@ object DataStoreUtils {
fun getStatusCount(context: Context, uri: Uri, accountId: UserKey): Int {
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
val whereArgs = arrayOf(accountId.toString())
return queryCount(context, uri, where, whereArgs)
return queryCount(context.contentResolver, uri, where, whereArgs)
}
fun getActivitiesCount(context: Context, uri: Uri,
accountKey: UserKey): Int {
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
return queryCount(context, uri, where, arrayOf(accountKey.toString()))
return queryCount(context.contentResolver, uri, where, arrayOf(accountKey.toString()))
}
@ -307,7 +306,7 @@ object DataStoreUtils {
}
val selection = Expression.and(*expressions.toTypedArray())
return queryCount(context, uri, selection.sql, expressionArgs.toTypedArray())
return queryCount(context.contentResolver, uri, selection.sql, expressionArgs.toTypedArray())
}
fun getActivitiesCount(context: Context, uri: Uri, compare: Long,
@ -321,7 +320,7 @@ object DataStoreUtils {
val whereArgs = arrayListOf<String>()
keys.mapTo(whereArgs) { it.toString() }
whereArgs.add(compare.toString())
return queryCount(context, uri, selection.sql, whereArgs.toTypedArray())
return queryCount(context.contentResolver, uri, selection.sql, whereArgs.toTypedArray())
}
fun getActivitiesCount(context: Context, uri: Uri,
@ -344,8 +343,8 @@ object DataStoreUtils {
selectionArgs = keys + since.toString()
}
// If followingOnly option is on, we have to iterate over items
val resolver = context.contentResolver
if (followingOnly) {
val resolver = context.contentResolver
val projection = arrayOf(Activities.SOURCES)
val cur = resolver.query(uri, projection, selection.sql, selectionArgs, null) ?: return -1
try {
@ -377,7 +376,7 @@ object DataStoreUtils {
cur.close()
}
}
return queryCount(context, uri, selection.sql, selectionArgs)
return queryCount(resolver, uri, selection.sql, selectionArgs)
}
fun getTableId(uri: Uri?): Int {
@ -763,10 +762,9 @@ object DataStoreUtils {
fun assign(array: T, arrayIdx: Int, cur: Cursor, colIdx: I)
}
fun queryCount(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val resolver = context.contentResolver
fun queryCount(cr: ContentResolver, uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val projection = arrayOf(SQLFunctions.COUNT())
val cur = resolver.query(uri, projection, selection, selectionArgs, null) ?: return -1
val cur = cr.query(uri, projection, selection, selectionArgs, null) ?: return -1
try {
if (cur.moveToFirst()) {
return cur.getInt(0)
@ -913,5 +911,4 @@ object DataStoreUtils {
}
}

View File

@ -53,6 +53,7 @@ abstract class TwitterTimelineStreamCallback(val accountId: String) : SimpleUser
// Mention
handled = handled or onActivityAboutMe(InternalActivityCreator.status(accountId, status))
}
onAllStatus(status)
return handled
}
@ -139,4 +140,8 @@ abstract class TwitterTimelineStreamCallback(val accountId: String) : SimpleUser
@WorkerThread
override abstract fun onDirectMessage(directMessage: DirectMessage): Boolean
@WorkerThread
protected open fun onAllStatus(status: Status) {
}
}

View File

@ -713,6 +713,8 @@
<string name="notification_ringtone">Ringtone</string>
<string name="notification_status">New tweet by <xliff:g id="user">%s</xliff:g></string>
<string name="notification_status_multiple">New Tweet by <xliff:g id="user">%1$s</xliff:g> and <xliff:g id="count">%2$d</xliff:g> others</string>
<string name="notification_title_new_status_by_user">New tweet by <xliff:g id="user">%1$s</xliff:g></string>
<string name="notification_title_new_retweet_by_user"><xliff:g id="user">%1$s</xliff:g> retweeted</string>
<string name="notification_type_home">Home</string>
<string name="notification_type_interactions">Interactions</string>
<string name="notification_type_messages">Messages</string>
@ -1040,9 +1042,11 @@
<!-- [noun] Accessibility label for retweet icon -->
<string name="status_type_retweet">Retweet</string>
<string name="stream_summary_notification_users">Users turned "Notifications" on, not 100% reliable.</string>
<string name="stream_type_home">Home</string>
<string name="stream_type_interactions">Interactions</string>
<string name="stream_type_messages">Messages</string>
<string name="stream_type_notification_users">Notification enabled users</string>
<string name="streaming">Streaming</string>

View File

@ -34,4 +34,9 @@
android:defaultValue="true"
android:key="stream_direct_messages"
android:title="@string/stream_type_messages"/>
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="stream_notification_users"
android:summary="@string/stream_summary_notification_users"
android:title="@string/stream_type_notification_users"/>
</PreferenceScreen>