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

improved statuses fragment load more

This commit is contained in:
Mariotaku Lee 2016-02-04 03:27:43 +08:00
parent adaf9402a5
commit 7a85746eeb
39 changed files with 596 additions and 699 deletions

View File

@ -43,9 +43,9 @@ dependencies {
compile 'com.android.support:support-v4:23.1.1'
compile 'com.bluelinelabs:logansquare:1.3.4'
compile 'org.apache.commons:commons-lang3:3.4'
compile 'com.github.mariotaku.RestFu:library:0.9.14'
compile 'com.github.mariotaku.RestFu:library:0.9.16'
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.2'
compile 'com.github.mariotaku.SQLiteQB:library:0.9.3'
compile 'com.github.mariotaku.SQLiteQB:library:0.9.3-SNAPSHOT'
compile 'com.github.mariotaku.ObjectCursor:core:0.9.3'
compile fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -208,6 +208,8 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
int VIRTUAL_TABLE_ID_EMPTY = 200;
int VIRTUAL_TABLE_ID_RAW_QUERY = 300;
int NOTIFICATION_ID_HOME_TIMELINE = 1;
int NOTIFICATION_ID_INTERACTIONS_TIMELINE = 2;
int NOTIFICATION_ID_DIRECT_MESSAGES = 3;

View File

@ -19,6 +19,7 @@
package org.mariotaku.twidere.api.twitter.util;
import android.support.annotation.NonNull;
import android.support.v4.util.SimpleArrayMap;
import com.bluelinelabs.logansquare.LoganSquare;
@ -66,15 +67,16 @@ public class TwitterConverterFactory extends RestConverter.SimpleFactory<Twitter
}
}
private static <T> T parseOrThrow(HttpResponse resp, InputStream stream, Type type)
@NonNull
private static <T> T parseOrThrow(InputStream stream, Type type)
throws IOException, TwitterException, RestConverter.ConvertException {
try {
final ParameterizedType<T> parameterizedType = ParameterizedTypeAccessor.create(type);
final T parse = LoganSquare.parse(stream, parameterizedType);
if (TwitterException.class == type && parse == null) {
throw new TwitterException();
final T parsed = LoganSquare.parse(stream, parameterizedType);
if (parsed == null) {
throw new TwitterException("Empty data");
}
return parse;
return parsed;
} catch (JsonParseException e) {
throw new RestConverter.ConvertException("Malformed JSON Data");
}
@ -121,7 +123,7 @@ public class TwitterConverterFactory extends RestConverter.SimpleFactory<Twitter
public Object convert(HttpResponse httpResponse) throws IOException, ConvertException, TwitterException {
final Body body = httpResponse.getBody();
final InputStream stream = body.stream();
final Object object = parseOrThrow(httpResponse, stream, type);
final Object object = parseOrThrow(stream, type);
checkResponse(type, object, httpResponse);
if (object instanceof TwitterResponseObject) {
((TwitterResponseObject) object).processResponseHeader(httpResponse);

View File

@ -37,6 +37,7 @@ import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@JsonObject
@ -307,58 +308,61 @@ public class ParcelableMedia implements Parcelable {
}
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("media_url", media_url)
.append("page_url", url)
.append("preview_url", preview_url)
.append("start", start)
.append("end", end)
.append("type", type)
.append("width", width)
.append("height", height)
.append("video_info", video_info)
.append("card", card)
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParcelableMedia media = (ParcelableMedia) o;
return new EqualsBuilder()
.append(start, media.start)
.append(end, media.end)
.append(type, media.type)
.append(width, media.width)
.append(height, media.height)
.append(media_url, media.media_url)
.append(url, media.url)
.append(preview_url, media.preview_url)
.append(video_info, media.video_info)
.append(card, media.card)
.isEquals();
if (start != media.start) return false;
if (end != media.end) return false;
if (type != media.type) return false;
if (width != media.width) return false;
if (height != media.height) return false;
if (!url.equals(media.url)) return false;
if (media_url != null ? !media_url.equals(media.media_url) : media.media_url != null)
return false;
if (preview_url != null ? !preview_url.equals(media.preview_url) : media.preview_url != null)
return false;
if (video_info != null ? !video_info.equals(media.video_info) : media.video_info != null)
return false;
if (card != null ? !card.equals(media.card) : media.card != null) return false;
return !(page_url != null ? !page_url.equals(media.page_url) : media.page_url != null);
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(media_url)
.append(url)
.append(preview_url)
.append(start)
.append(end)
.append(type)
.append(width)
.append(height)
.append(video_info)
.append(card)
.toHashCode();
int result = url.hashCode();
result = 31 * result + (media_url != null ? media_url.hashCode() : 0);
result = 31 * result + (preview_url != null ? preview_url.hashCode() : 0);
result = 31 * result + start;
result = 31 * result + end;
result = 31 * result + type;
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + (video_info != null ? video_info.hashCode() : 0);
result = 31 * result + (card != null ? card.hashCode() : 0);
result = 31 * result + (page_url != null ? page_url.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ParcelableMedia{" +
"url='" + url + '\'' +
", media_url='" + media_url + '\'' +
", preview_url='" + preview_url + '\'' +
", start=" + start +
", end=" + end +
", type=" + type +
", width=" + width +
", height=" + height +
", video_info=" + video_info +
", card=" + card +
", page_url='" + page_url + '\'' +
'}';
}
@Override
@ -439,34 +443,32 @@ public class ParcelableMedia implements Parcelable {
return new VideoInfo(videoInfo);
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("variants", variants)
.append("duration", duration)
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VideoInfo videoInfo = (VideoInfo) o;
return new EqualsBuilder()
.append(duration, videoInfo.duration)
.append(variants, videoInfo.variants)
.isEquals();
if (duration != videoInfo.duration) return false;
// Probably incorrect - comparing Object[] arrays with Arrays.equals
return Arrays.equals(variants, videoInfo.variants);
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(variants)
.append(duration)
.toHashCode();
int result = variants != null ? Arrays.hashCode(variants) : 0;
result = 31 * result + (int) (duration ^ (duration >>> 32));
return result;
}
@Override
public String toString() {
return "VideoInfo{" +
"variants=" + Arrays.toString(variants) +
", duration=" + duration +
'}';
}
@Override
@ -524,34 +526,32 @@ public class ParcelableMedia implements Parcelable {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Variant variant = (Variant) o;
return new EqualsBuilder()
.append(bitrate, variant.bitrate)
.append(content_type, variant.content_type)
.append(url, variant.url)
.isEquals();
if (bitrate != variant.bitrate) return false;
if (content_type != null ? !content_type.equals(variant.content_type) : variant.content_type != null)
return false;
return !(url != null ? !url.equals(variant.url) : variant.url != null);
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(content_type)
.append(url)
.append(bitrate)
.toHashCode();
int result = content_type != null ? content_type.hashCode() : 0;
result = 31 * result + (url != null ? url.hashCode() : 0);
result = 31 * result + (int) (bitrate ^ (bitrate >>> 32));
return result;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("content_type", content_type)
.append("url", url)
.append("bitrate", bitrate)
.toString();
return "Variant{" +
"content_type='" + content_type + '\'' +
", url='" + url + '\'' +
", bitrate=" + bitrate +
'}';
}
@Override

View File

@ -43,6 +43,8 @@ public interface TwidereDataStore {
String CONTENT_PATH_EMPTY = "empty_content";
String CONTENT_PATH_RAW_QUERY = "raw_query";
String CONTENT_PATH_DATABASE_READY = "database_ready";
Uri BASE_CONTENT_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
@ -52,6 +54,8 @@ public interface TwidereDataStore {
Uri CONTENT_URI_EMPTY = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH_EMPTY);
Uri CONTENT_URI_RAW_QUERY = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH_RAW_QUERY);
Uri CONTENT_URI_DATABASE_READY = Uri.withAppendedPath(BASE_CONTENT_URI,
CONTENT_PATH_DATABASE_READY);

View File

@ -45,7 +45,6 @@ public final class TwidereArrayUtils {
public static boolean contentMatch(final long[] array1, final long[] array2) {
if (array1 == null || array2 == null) return array1 == array2;
if (array1.length != array2.length) return false;
final int length = array1.length;
for (long anArray1 : array1) {
if (!ArrayUtils.contains(array2, anArray1)) return false;
}
@ -122,46 +121,6 @@ public final class TwidereArrayUtils {
return array;
}
public static void reverse(@NonNull Object[] array) {
for (int i = 0; i < array.length / 2; i++) {
Object temp = array[i];
array[i] = array[array.length - i - 1];
array[array.length - i - 1] = temp;
}
}
public static int[] subArray(final int[] array, final int start, final int end) {
final int length = end - start;
if (length < 0) throw new IllegalArgumentException();
final int[] result = new int[length];
System.arraycopy(array, start, result, 0, length);
return result;
}
public static long[] subArray(final long[] array, final int start, final int end) {
final int length = end - start;
if (length < 0) throw new IllegalArgumentException();
final long[] result = new long[length];
System.arraycopy(array, start, result, 0, length);
return result;
}
public static Object[] subArray(final Object[] array, final int start, final int end) {
final int length = end - start;
if (length < 0) throw new IllegalArgumentException();
final Object[] result = new Object[length];
System.arraycopy(array, start, result, 0, length);
return result;
}
public static String[] subArray(final String[] array, final int start, final int end) {
final int length = end - start;
if (length < 0) throw new IllegalArgumentException();
final String[] result = new String[length];
System.arraycopy(array, start, result, 0, length);
return result;
}
public static String toString(final long[] array, final char token, final boolean include_space) {
final StringBuilder builder = new StringBuilder();
final int length = array.length;
@ -193,24 +152,24 @@ public final class TwidereArrayUtils {
public static String[] toStringArray(final Object[] array) {
if (array == null) return null;
final int length = array.length;
final String[] string_array = new String[length];
for (int i = 0; i < length; i++) {
string_array[i] = ParseUtils.parseString(array[i]);
}
return string_array;
}
public static String[] toStringArray(final List<?> list) {
if (list == null) return null;
final int length = list.size();
final String[] stringArray = new String[length];
for (int i = 0; i < length; i++) {
stringArray[i] = ParseUtils.parseString(list.get(i));
stringArray[i] = ParseUtils.parseString(array[i]);
}
return stringArray;
}
public static String[] toStringArray(final long[] array) {
if (array == null) return null;
final int length = array.length;
final String[] stringArray = new String[length];
for (int i = 0; i < length; i++) {
stringArray[i] = ParseUtils.parseString(array[i]);
}
return stringArray;
}
public static String toStringForSQL(final String[] array) {
final int size = array != null ? array.length : 0;
final StringBuilder builder = new StringBuilder();

View File

@ -27,6 +27,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.CancellationSignal;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.twidere.util.TwidereArrayUtils;
import java.util.Collection;
@ -47,28 +48,28 @@ public class ContentResolverUtils {
final T[] colValues, final String extraWhere, final boolean valuesIsString) {
if (resolver == null || uri == null || isEmpty(inColumn) || colValues == null || colValues.length == 0)
return 0;
final int col_values_length = colValues.length, blocks_count = col_values_length / MAX_BULK_COUNT + 1;
int rows_deleted = 0;
final int colValuesLength = colValues.length, blocks_count = colValuesLength / MAX_BULK_COUNT + 1;
int rowsDeleted = 0;
for (int i = 0; i < blocks_count; i++) {
final int start = i * MAX_BULK_COUNT, end = Math.min(start + MAX_BULK_COUNT, col_values_length);
final String[] block = TwidereArrayUtils.toStringArray(TwidereArrayUtils.subArray(colValues, start, end));
final int start = i * MAX_BULK_COUNT, end = Math.min(start + MAX_BULK_COUNT, colValuesLength);
final String[] block = TwidereArrayUtils.toStringArray(ArrayUtils.subarray(colValues, start, end));
if (valuesIsString) {
final StringBuilder where = new StringBuilder(inColumn + " IN(" + TwidereArrayUtils.toStringForSQL(block)
+ ")");
if (!isEmpty(extraWhere)) {
where.append("AND ").append(extraWhere);
}
rows_deleted += resolver.delete(uri, where.toString(), block);
rowsDeleted += resolver.delete(uri, where.toString(), block);
} else {
final StringBuilder where = new StringBuilder(inColumn + " IN("
+ TwidereArrayUtils.toString(block, ',', true) + ")");
if (!isEmpty(extraWhere)) {
where.append("AND ").append(extraWhere);
}
rows_deleted += resolver.delete(uri, where.toString(), null);
rowsDeleted += resolver.delete(uri, where.toString(), null);
}
}
return rows_deleted;
return rowsDeleted;
}
public static int bulkInsert(final ContentResolver resolver, final Uri uri, final Collection<ContentValues> values) {

View File

@ -108,8 +108,8 @@ dependencies {
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.2'
compile 'com.github.mariotaku:PickNCrop:0.9.2'
compile 'com.github.mariotaku.RestFu:library:0.9.15'
compile 'com.github.mariotaku.RestFu:okhttp:0.9.15'
compile 'com.github.mariotaku.RestFu:library:0.9.16'
compile 'com.github.mariotaku.RestFu:okhttp:0.9.16'
compile 'com.github.mariotaku:InetAddressJni:0.9.1'
compile 'com.lnikkila:extendedtouchview:0.1.0'
compile 'com.google.dagger:dagger:2.0.2'

View File

@ -0,0 +1,25 @@
package android.support.v7.widget;
import android.view.View;
/**
* Created by mariotaku on 16/2/4.
*/
public class LinearLayoutManagerAccessor {
public static OrientationHelper getOrientationHelper(LinearLayoutManager llm) {
return llm.mOrientationHelper;
}
public static void ensureLayoutState(LinearLayoutManager llm) {
llm.ensureLayoutState();
}
public static boolean getShouldReverseLayout(LinearLayoutManager llm) {
return llm.mShouldReverseLayout;
}
public static View findOneVisibleChild(LinearLayoutManager llm, int fromIndex, int toIndex, boolean completelyVisible, boolean acceptPartiallyVisible) {
return llm.findOneVisibleChild(fromIndex, toIndex, completelyVisible, acceptPartiallyVisible);
}
}

View File

@ -647,7 +647,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements OnMenuIte
mAccountsAdapter.setAccounts(accounts);
mMediaPreviewAdapter = new MediaPreviewAdapter(this, new PreviewGridOnStartDragListener(mItemTouchHelper));
mMediaPreviewAdapter = new MediaPreviewAdapter(this, new PreviewGridOnStartDragListener(this));
mItemTouchHelper = new ItemTouchHelper(new AttachedMediaItemTouchHelperCallback(mMediaPreviewAdapter));
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
@ -817,7 +817,8 @@ public class ComposeActivity extends ThemedFragmentActivity implements OnMenuIte
}
@Override
public boolean handleKeyboardShortcutRepeat(@NonNull KeyboardShortcutsHandler handler, int keyCode, int repeatCount, @NonNull KeyEvent event, int metaState) {
public boolean handleKeyboardShortcutRepeat(@NonNull KeyboardShortcutsHandler handler, int keyCode,
int repeatCount, @NonNull KeyEvent event, int metaState) {
return super.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState);
}
@ -1102,7 +1103,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements OnMenuIte
private void setMenu() {
if (mMenuBar == null) return;
final Menu menu = mMenuBar.getMenu();
final boolean hasMedia = hasMedia(), hasInReplyTo = mInReplyToStatus != null;
final boolean hasMedia = hasMedia();
/*
* No media & Not reply: [Take photo][Add image][Attach location][Drafts]
@ -1199,7 +1200,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements OnMenuIte
if (provider != null) {
mLocationText.setText(R.string.getting_location);
mLocationListener = new ComposeLocationListener(this);
// mLocationManager.requestLocationUpdates(provider, 0, 0, mLocationListener);
mLocationManager.requestLocationUpdates(provider, 0, 0, mLocationListener);
final Location location = Utils.getCachedLocation(this);
if (location != null) {
mLocationListener.onLocationChanged(location);
@ -1880,49 +1881,48 @@ public class ComposeActivity extends ThemedFragmentActivity implements OnMenuIte
}
private static class PreviewGridItemDecoration extends ItemDecoration {
private final int mPreviewGridSpacing;
private final int previewGridSpacing;
public PreviewGridItemDecoration(int previewGridSpacing) {
mPreviewGridSpacing = previewGridSpacing;
this.previewGridSpacing = previewGridSpacing;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
outRect.left = outRect.right = mPreviewGridSpacing;
outRect.left = outRect.right = previewGridSpacing;
}
}
private static class PreviewGridOnStartDragListener implements SimpleItemTouchHelperCallback.OnStartDragListener {
private final WeakReference<ItemTouchHelper> helperRef;
@NonNull
private final ComposeActivity activity;
public PreviewGridOnStartDragListener(ItemTouchHelper helper) {
helperRef = new WeakReference<>(helper);
public PreviewGridOnStartDragListener(@NonNull ComposeActivity activity) {
this.activity = activity;
}
@Override
public void onStartDrag(ViewHolder viewHolder) {
final ItemTouchHelper helper = helperRef.get();
final ItemTouchHelper helper = activity.mItemTouchHelper;
if (helper == null) return;
helper.startDrag(viewHolder);
}
}
private static class ComposeEnterListener implements EnterListener {
private final WeakReference<ComposeActivity> activityRef;
private final ComposeActivity activity;
public ComposeEnterListener(ComposeActivity activity) {
activityRef = new WeakReference<>(activity);
this.activity = activity;
}
@Override
public boolean shouldCallListener() {
final ComposeActivity activity = activityRef.get();
return activity != null && activity.mKeyMetaState == 0;
}
@Override
public boolean onHitEnter() {
final ComposeActivity activity = activityRef.get();
if (activity == null) return false;
activity.confirmAndUpdateStatus();
return true;

View File

@ -114,7 +114,7 @@ import java.util.Collections;
import java.util.List;
import static org.mariotaku.twidere.util.CompareUtils.classEquals;
import static org.mariotaku.twidere.util.Utils.cleanDatabasesByItemLimit;
import static org.mariotaku.twidere.util.DataStoreUtils.cleanDatabasesByItemLimit;
import static org.mariotaku.twidere.util.Utils.getDefaultAccountId;
import static org.mariotaku.twidere.util.Utils.getTabDisplayOptionInt;
import static org.mariotaku.twidere.util.Utils.isDatabaseReady;

View File

@ -173,7 +173,7 @@ public final class MediaViewerActivity extends AbsMediaViewerActivity implements
ExternalBrowserPageFragment.class.getName(), args);
}
}
throw new UnsupportedOperationException();
throw new UnsupportedOperationException(String.valueOf(media));
}
@Override

View File

@ -148,12 +148,13 @@ public final class DummyStatusHolderAdapter implements IStatusesAdapter<Object>
}
@Override
public boolean isLoadMoreSupported() {
return false;
@IndicatorPosition
public int getLoadMoreSupportedPosition() {
return IndicatorPosition.NONE;
}
@Override
public void setLoadMoreSupported(boolean supported) {
public void setLoadMoreSupportedPosition(@IndicatorPosition int supported) {
}

View File

@ -30,10 +30,10 @@ import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter;
public abstract class LoadMoreSupportAdapter<VH extends ViewHolder> extends BaseRecyclerViewAdapter<VH>
implements ILoadMoreSupportAdapter {
private boolean mLoadMoreSupported;
private
@IndicatorPosition
int mLoadMoreIndicatorPosition;
private int mLoadMoreSupportedPosition;
@IndicatorPosition
private int mLoadMoreIndicatorPosition;
public LoadMoreSupportAdapter(Context context) {
super(context);
@ -48,21 +48,20 @@ public abstract class LoadMoreSupportAdapter<VH extends ViewHolder> extends Base
@Override
public final void setLoadMoreIndicatorPosition(@IndicatorPosition int position) {
if (mLoadMoreIndicatorPosition == position) return;
mLoadMoreIndicatorPosition = mLoadMoreSupported ? position : IndicatorPosition.NONE;
mLoadMoreIndicatorPosition = IndicatorPositionUtils.apply(position, mLoadMoreSupportedPosition);
notifyDataSetChanged();
}
@Override
public final boolean isLoadMoreSupported() {
return mLoadMoreSupported;
@IndicatorPosition
public final int getLoadMoreSupportedPosition() {
return mLoadMoreSupportedPosition;
}
@Override
public final void setLoadMoreSupported(boolean supported) {
mLoadMoreSupported = supported;
if (!supported) {
mLoadMoreIndicatorPosition = IndicatorPosition.NONE;
}
public final void setLoadMoreSupportedPosition(@IndicatorPosition int supportedPosition) {
mLoadMoreSupportedPosition = supportedPosition;
mLoadMoreIndicatorPosition = IndicatorPositionUtils.apply(mLoadMoreIndicatorPosition, supportedPosition);
notifyDataSetChanged();
}

View File

@ -32,9 +32,10 @@ public interface ILoadMoreSupportAdapter {
void setLoadMoreIndicatorPosition(@IndicatorPosition int position);
boolean isLoadMoreSupported();
@IndicatorPosition
int getLoadMoreSupportedPosition();
void setLoadMoreSupported(boolean supported);
void setLoadMoreSupportedPosition(@IndicatorPosition int supported);
@IntDef(flag = true, value = {IndicatorPosition.NONE, IndicatorPosition.START,
IndicatorPosition.END, IndicatorPosition.BOTH})
@ -44,4 +45,16 @@ public interface ILoadMoreSupportAdapter {
int END = 0b10;
int BOTH = START | END;
}
class IndicatorPositionUtils {
@IndicatorPosition
public static int apply(@IndicatorPosition int orig, @IndicatorPosition int supported) {
return orig & supported;
}
@IndicatorPosition
public static boolean has(@IndicatorPosition int flags, @IndicatorPosition int compare) {
return (flags & compare) != 0;
}
}
}

View File

@ -42,6 +42,7 @@ import com.squareup.otto.Subscribe;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.AbsActivitiesAdapter;
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration;
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
import org.mariotaku.twidere.annotation.ReadPositionTag;
import org.mariotaku.twidere.loader.iface.IExtendedLoader;
import org.mariotaku.twidere.model.ParcelableActivity;
@ -271,7 +272,7 @@ public abstract class AbsActivitiesFragment<Data> extends AbsContentListRecycler
adapter.setData(data);
setRefreshEnabled(true);
if (!(loader instanceof IExtendedLoader) || ((IExtendedLoader) loader).isFromUser()) {
adapter.setLoadMoreSupported(hasMoreData(data));
adapter.setLoadMoreSupportedPosition(hasMoreData(data) ? IndicatorPosition.END : IndicatorPosition.NONE);
int pos = -1;
for (int i = 0, j = adapter.getItemCount(); i < j; i++) {
if (lastReadId != -1 && lastReadId == adapter.getTimestamp(i)) {

View File

@ -249,6 +249,7 @@ public abstract class AbsContentRecyclerViewFragment<A extends LoadMoreSupportAd
mRecyclerView.setAdapter(mAdapter);
mScrollListener = new ContentListScrollListener(this);
mRecyclerView.setOnTouchListener(mScrollListener.getOnTouchListener());
mScrollListener.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
}

View File

@ -46,6 +46,7 @@ import com.squareup.otto.Subscribe;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.AbsStatusesAdapter;
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter.StatusAdapterListener;
import org.mariotaku.twidere.annotation.ReadPositionTag;
import org.mariotaku.twidere.loader.iface.IExtendedLoader;
@ -282,7 +283,7 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentListRecyclerVi
adapter.setData(data);
setRefreshEnabled(true);
if (!(loader instanceof IExtendedLoader) || ((IExtendedLoader) loader).isFromUser()) {
adapter.setLoadMoreSupported(hasMoreData(data));
adapter.setLoadMoreSupportedPosition(hasMoreData(data) ? IndicatorPosition.END : IndicatorPosition.NONE);
int pos = -1;
for (int i = 0, j = adapter.getItemCount(); i < j; i++) {
if (lastReadId != -1 && lastReadId == adapter.getStatusId(i)) {

View File

@ -23,7 +23,6 @@ import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v7.widget.LinearLayoutManager;
@ -31,6 +30,7 @@ import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import org.mariotaku.twidere.adapter.AbsUserListsAdapter;
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
import org.mariotaku.twidere.loader.iface.IExtendedLoader;
import org.mariotaku.twidere.loader.support.iface.ICursorSupportLoader;
import org.mariotaku.twidere.model.ParcelableUserList;
@ -94,7 +94,7 @@ abstract class AbsUserListsFragment<Data> extends AbsContentListRecyclerViewFrag
final AbsUserListsAdapter<Data> adapter = getAdapter();
adapter.setData(data);
if (!(loader instanceof IExtendedLoader) || ((IExtendedLoader) loader).isFromUser()) {
adapter.setLoadMoreSupported(hasMoreData(data));
adapter.setLoadMoreSupportedPosition(hasMoreData(data) ? IndicatorPosition.END : IndicatorPosition.NONE);
setRefreshEnabled(true);
}
if (loader instanceof IExtendedLoader) {

View File

@ -34,6 +34,7 @@ import android.view.View;
import org.mariotaku.twidere.adapter.AbsUsersAdapter;
import org.mariotaku.twidere.adapter.AbsUsersAdapter.UserAdapterListener;
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
import org.mariotaku.twidere.loader.iface.IExtendedLoader;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
@ -93,7 +94,7 @@ abstract class AbsUsersFragment<Data> extends AbsContentListRecyclerViewFragment
final AbsUsersAdapter<Data> adapter = getAdapter();
adapter.setData(data);
if (!(loader instanceof IExtendedLoader) || ((IExtendedLoader) loader).isFromUser()) {
adapter.setLoadMoreSupported(hasMoreData(data));
adapter.setLoadMoreSupportedPosition(hasMoreData(data) ? IndicatorPosition.END : IndicatorPosition.NONE);
setRefreshEnabled(true);
}
if (loader instanceof IExtendedLoader) {

View File

@ -226,7 +226,7 @@ public abstract class CursorActivitiesFragment extends AbsActivitiesFragment<Lis
}
protected long[] getNewestActivityIds(long[] accountIds) {
return DataStoreUtils.getActivityMaxPositionsFromDatabase(getActivity(), getContentUri(), accountIds);
return DataStoreUtils.getNewestActivityMaxPositions(getActivity(), getContentUri(), accountIds);
}
protected abstract int getNotificationType();

View File

@ -263,7 +263,7 @@ public abstract class CursorStatusesFragment extends AbsStatusesFragment<List<Pa
}
protected long[] getNewestStatusIds(long[] accountIds) {
return DataStoreUtils.getNewestStatusIdsFromDatabase(getActivity(), getContentUri(), accountIds);
return DataStoreUtils.getNewestStatusIds(getActivity(), getContentUri(), accountIds);
}
protected abstract int getNotificationType();

View File

@ -160,8 +160,7 @@ public class DirectMessagesFragment extends AbsContentListRecyclerViewFragment<M
final MessageEntriesAdapter adapter = getAdapter();
adapter.setCursor(cursor);
adapter.setLoadMoreIndicatorPosition(IndicatorPosition.NONE);
adapter.setLoadMoreSupported(!isEmpty);
adapter.setLoadMoreSupported(hasMoreData(cursor));
adapter.setLoadMoreSupportedPosition(hasMoreData(cursor) ? IndicatorPosition.END : IndicatorPosition.NONE);
final long[] accountIds = getAccountIds();
adapter.setShowAccountsColor(accountIds.length > 1);
setRefreshEnabled(true);
@ -233,8 +232,9 @@ public class DirectMessagesFragment extends AbsContentListRecyclerViewFragment<M
@Override
protected long[][] doInBackground(final Object... params) {
final long[][] result = new long[2][];
result[0] = DataStoreUtils.getActivatedAccountIds(getActivity());
result[1] = DataStoreUtils.getNewestMessageIdsFromDatabase(getActivity(), DirectMessages.Inbox.CONTENT_URI);
result[0] = getAccountIds();
result[1] = DataStoreUtils.getNewestMessageIds(getActivity(),
DirectMessages.Inbox.CONTENT_URI, result[0]);
return result;
}
@ -367,9 +367,11 @@ public class DirectMessagesFragment extends AbsContentListRecyclerViewFragment<M
@Override
protected long[][] doInBackground(final Object... params) {
final long[][] result = new long[3][];
result[0] = DataStoreUtils.getActivatedAccountIds(getActivity());
result[1] = DataStoreUtils.getOldestMessageIdsFromDatabase(getActivity(), DirectMessages.Inbox.CONTENT_URI);
result[2] = DataStoreUtils.getOldestMessageIdsFromDatabase(getActivity(), DirectMessages.Outbox.CONTENT_URI);
result[0] = getAccountIds();
result[1] = DataStoreUtils.getOldestMessageIds(getActivity(),
DirectMessages.Inbox.CONTENT_URI, result[0]);
result[2] = DataStoreUtils.getOldestMessageIds(getActivity(),
DirectMessages.Outbox.CONTENT_URI, result[0]);
return result;
}

View File

@ -97,7 +97,7 @@ public class ScheduledStatusesFragment extends AbsContentListRecyclerViewFragmen
super(context);
mContext = context;
mInflater = LayoutInflater.from(context);
setLoadMoreSupported(false);
setLoadMoreSupportedPosition(IndicatorPosition.NONE);
}
@Override

View File

@ -87,7 +87,7 @@ import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.support.ColorPickerDialogActivity;
import org.mariotaku.twidere.adapter.AbsStatusesAdapter;
import org.mariotaku.twidere.adapter.ArrayRecyclerAdapter;
import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter;
import org.mariotaku.twidere.adapter.LoadMoreSupportAdapter;
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration;
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter;
@ -218,6 +218,23 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
@Override
public void onLoadFinished(Loader<List<ParcelableStatus>> loader, List<ParcelableStatus> data) {
mStatusAdapter.updateItemDecoration();
ConversationLoader conversationLoader = (ConversationLoader) loader;
int supportedPositions = 0;
if (data != null && !data.isEmpty()) {
if (conversationLoader.getSinceId() < data.get(data.size() - 1).id) {
supportedPositions |= IndicatorPosition.END;
}
if (data.get(0).in_reply_to_status_id > 0) {
supportedPositions |= IndicatorPosition.START;
}
} else {
supportedPositions |= IndicatorPosition.END;
final ParcelableStatus status = getStatus();
if (status != null && status.in_reply_to_status_id > 0) {
supportedPositions |= IndicatorPosition.START;
}
}
mStatusAdapter.setLoadMoreSupportedPosition(supportedPositions);
setConversation(data);
final ParcelableCredentials account = mStatusAdapter.getStatusAccount();
if (Utils.hasOfficialAPIAccess(loader.getContext(), mPreferences, account)) {
@ -547,8 +564,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final ParcelableStatus status = data.getData();
final Bundle dataExtra = data.getExtras();
final ParcelableCredentials credentials = dataExtra.getParcelable(EXTRA_ACCOUNT);
mStatusAdapter.setLoadMoreSupported(true);
if (mStatusAdapter.setStatus(status, credentials)) {
mStatusAdapter.setLoadMoreSupportedPosition(IndicatorPosition.BOTH);
mStatusAdapter.setData(null);
loadConversation(status, -1, -1);
loadActivity(status);
@ -566,7 +583,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
setState(STATE_LOADED);
} else {
mStatusAdapter.setLoadMoreSupported(false);
mStatusAdapter.setLoadMoreSupportedPosition(IndicatorPosition.NONE);
//TODO show errors
setState(STATE_ERROR);
}
@ -1406,7 +1423,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
}
private static class StatusAdapter extends BaseRecyclerViewAdapter<ViewHolder>
private static class StatusAdapter extends LoadMoreSupportAdapter<ViewHolder>
implements IStatusesAdapter<List<ParcelableStatus>> {
private static final int VIEW_TYPE_LIST_STATUS = 0;
@ -1416,13 +1433,14 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private static final int VIEW_TYPE_REPLY_ERROR = 4;
private static final int VIEW_TYPE_CONVERSATION_ERROR = 5;
private static final int VIEW_TYPE_SPACE = 6;
private static final int ITEM_IDX_CONVERSATION_ERROR = 0;
private static final int ITEM_IDX_CONVERSATION_LOAD_MORE = 1;
private static final int ITEM_IDX_CONVERSATION_LOAD_MORE = 0;
private static final int ITEM_IDX_CONVERSATION_ERROR = 1;
private static final int ITEM_IDX_CONVERSATION = 2;
private static final int ITEM_IDX_STATUS = 3;
private static final int ITEM_IDX_REPLY = 4;
private static final int ITEM_IDX_REPLY_LOAD_MORE = 5;
private static final int ITEM_IDX_REPLY_ERROR = 6;
private static final int ITEM_IDX_REPLY_ERROR = 5;
private static final int ITEM_IDX_REPLY_LOAD_MORE = 6;
private static final int ITEM_IDX_SPACE = 7;
private static final int ITEM_TYPES_SUM = 8;
@ -1461,7 +1479,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private List<ParcelableStatus> mData;
private CharSequence mReplyError, mConversationError;
private boolean mRepliesLoading, mConversationsLoading;
private int mReplyStart;
public StatusAdapter(StatusFragment fragment, boolean compact) {
@ -1546,7 +1563,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
case ITEM_IDX_REPLY: {
if (mData == null || mReplyStart < 0) return null;
return mData.get(position - getIndexStart(ITEM_IDX_CONVERSATION)
- mItemCounts[ITEM_IDX_CONVERSATION] - mItemCounts[ITEM_IDX_STATUS]
- getTypeCount(ITEM_IDX_CONVERSATION) - getTypeCount(ITEM_IDX_STATUS)
+ mReplyStart);
}
case ITEM_IDX_STATUS: {
@ -1585,7 +1602,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
@Override
public int getStatusesCount() {
return mItemCounts[ITEM_IDX_CONVERSATION] + mItemCounts[ITEM_IDX_STATUS] + mItemCounts[ITEM_IDX_REPLY];
return getTypeCount(ITEM_IDX_CONVERSATION) + getTypeCount(ITEM_IDX_STATUS)
+ getTypeCount(ITEM_IDX_REPLY);
}
@Override
@ -1619,8 +1637,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
if (status == null) return;
mData = data;
if (data == null || data.isEmpty()) {
setCount(ITEM_IDX_CONVERSATION, 0);
setCount(ITEM_IDX_REPLY, 0);
setTypeCount(ITEM_IDX_CONVERSATION, 0);
setTypeCount(ITEM_IDX_REPLY, 0);
mReplyStart = -1;
} else {
int conversationCount = 0, replyCount = 0;
@ -1637,8 +1655,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
replyCount++;
}
}
setCount(ITEM_IDX_CONVERSATION, conversationCount);
setCount(ITEM_IDX_REPLY, replyCount);
setTypeCount(ITEM_IDX_CONVERSATION, conversationCount);
setTypeCount(ITEM_IDX_REPLY, replyCount);
mReplyStart = replyStart;
}
notifyDataSetChanged();
@ -1707,36 +1725,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
@Override
@IndicatorPosition
public int getLoadMoreIndicatorPosition() {
int position = 0;
if (mConversationsLoading) {
position |= IndicatorPosition.START;
}
if (mRepliesLoading) {
position |= IndicatorPosition.END;
}
return position;
}
@Override
public void setLoadMoreIndicatorPosition(@IndicatorPosition int position) {
setConversationsLoading((position & IndicatorPosition.START) != 0);
setRepliesLoading((position & IndicatorPosition.END) != 0);
updateItemDecoration();
}
@Override
public boolean isLoadMoreSupported() {
return true;
}
@Override
public void setLoadMoreSupported(boolean supported) {
// No-op
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
@ -1819,12 +1807,12 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
case VIEW_TYPE_CONVERSATION_LOAD_INDICATOR: {
LoadIndicatorViewHolder indicatorHolder = ((LoadIndicatorViewHolder) holder);
indicatorHolder.setLoadProgressVisible(mConversationsLoading);
indicatorHolder.setLoadProgressVisible(isConversationsLoading());
break;
}
case VIEW_TYPE_REPLIES_LOAD_INDICATOR: {
LoadIndicatorViewHolder indicatorHolder = ((LoadIndicatorViewHolder) holder);
indicatorHolder.setLoadProgressVisible(mRepliesLoading);
indicatorHolder.setLoadProgressVisible(isRepliesLoading());
break;
}
}
@ -1888,7 +1876,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private int getItemType(int position) {
int typeStart = 0;
for (int type = 0; type < ITEM_TYPES_SUM; type++) {
int typeCount = mItemCounts[type];
int typeCount = getTypeCount(type);
final int typeEnd = typeStart + typeCount;
if (position >= typeStart && position < typeEnd) return type;
typeStart = typeEnd;
@ -1899,7 +1887,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private int getItemTypeStart(int position) {
int typeStart = 0;
for (int type = 0; type < ITEM_TYPES_SUM; type++) {
int typeCount = mItemCounts[type];
int typeCount = getTypeCount(type);
final int typeEnd = typeStart + typeCount;
if (position >= typeStart && position < typeEnd) return typeStart;
typeStart = typeEnd;
@ -1932,24 +1920,28 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
mRecyclerView = null;
}
private void setCount(int idx, int size) {
private void setTypeCount(int idx, int size) {
mItemCounts[idx] = size;
notifyDataSetChanged();
}
public int getTypeCount(int idx) {
return mItemCounts[idx];
}
public void setEventListener(StatusAdapterListener listener) {
mStatusAdapterListener = listener;
}
public void setReplyError(CharSequence error) {
mReplyError = error;
setCount(ITEM_IDX_REPLY_ERROR, error != null ? 1 : 0);
setTypeCount(ITEM_IDX_REPLY_ERROR, error != null ? 1 : 0);
updateItemDecoration();
}
public void setConversationError(CharSequence error) {
mConversationError = error;
setCount(ITEM_IDX_CONVERSATION_ERROR, error != null ? 1 : 0);
setTypeCount(ITEM_IDX_CONVERSATION_ERROR, error != null ? 1 : 0);
updateItemDecoration();
}
@ -1967,7 +1959,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final DividerItemDecoration decoration = mFragment.getItemDecoration();
decoration.setDecorationStart(0);
// Is loading replies
if (mRepliesLoading) {
if (isRepliesLoading()) {
decoration.setDecorationEndOffset(2);
} else {
decoration.setDecorationEndOffset(1);
@ -1976,14 +1968,20 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
public void setRepliesLoading(boolean loading) {
mRepliesLoading = loading;
notifyItemChanged(getFirstPositionOfItem(ITEM_IDX_REPLY_LOAD_MORE));
if (loading) {
setLoadMoreIndicatorPosition(getLoadMoreIndicatorPosition() | IndicatorPosition.END);
} else {
setLoadMoreIndicatorPosition(getLoadMoreIndicatorPosition() & ~IndicatorPosition.END);
}
updateItemDecoration();
}
public void setConversationsLoading(boolean loading) {
mConversationsLoading = loading;
notifyItemChanged(getFirstPositionOfItem(ITEM_IDX_CONVERSATION_LOAD_MORE));
if (loading) {
setLoadMoreIndicatorPosition(getLoadMoreIndicatorPosition() | IndicatorPosition.START);
} else {
setLoadMoreIndicatorPosition(getLoadMoreIndicatorPosition() & ~IndicatorPosition.START);
}
updateItemDecoration();
}
@ -1991,7 +1989,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
int position = 0;
for (int i = 0; i < ITEM_TYPES_SUM; i++) {
if (itemIdx == i) return position;
position += mItemCounts[i];
position += getTypeCount(i);
}
return RecyclerView.NO_POSITION;
}
@ -2010,6 +2008,14 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
return mData;
}
public boolean isConversationsLoading() {
return IndicatorPositionUtils.has(getLoadMoreIndicatorPosition(), IndicatorPosition.START);
}
public boolean isRepliesLoading() {
return IndicatorPositionUtils.has(getLoadMoreIndicatorPosition(), IndicatorPosition.END);
}
public static class StatusErrorItemViewHolder extends ViewHolder {
private final TextView textView;
@ -2029,6 +2035,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private static class StatusListLinearLayoutManager extends FixedLinearLayoutManager {
private final RecyclerView recyclerView;
private int mSpaceHeight;
public StatusListLinearLayoutManager(Context context, RecyclerView recyclerView) {
super(context);
@ -2054,7 +2061,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
if (heightBeforeSpace != 0) {
final int spaceHeight = recyclerView.getMeasuredHeight() - heightBeforeSpace;
return Math.max(0, spaceHeight);
return mSpaceHeight = Math.max(0, spaceHeight);
}
}
return super.getDecoratedMeasuredHeight(child);
@ -2067,6 +2074,65 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
super.setOrientation(orientation);
}
@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
int offset = getScrollBarStartOffset();
final int firstVisiblePosition = findFirstVisibleItemPosition();
final View firstVisibleView = findViewByPosition(firstVisiblePosition);
final float decoratedTop = getDecoratedTop(firstVisibleView),
decoratedBottom = getDecoratedBottom(firstVisibleView);
final float heightRatio = decoratedTop / (decoratedBottom - decoratedTop);
return Math.round((Math.max(0, firstVisiblePosition - offset) - heightRatio)
* getAvgItemSize());
}
@Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
return getAvgItemSize();
}
@Override
public int computeVerticalScrollRange(RecyclerView.State state) {
final int count = getScrollBarValidItemCount();
final int avgItemSize = getAvgItemSize();
return count * avgItemSize;
}
protected int getAvgItemSize() {
final int firstVisiblePosition = findFirstVisibleItemPosition();
final int lastVisiblePosition = findLastVisibleItemPosition();
final View firstVisibleView = findViewByPosition(firstVisiblePosition);
final View lastVisibleView = findViewByPosition(lastVisiblePosition);
return (lastVisibleView.getBottom() - firstVisibleView.getTop()) / (lastVisiblePosition - firstVisiblePosition);
}
protected int getScrollBarValidItemCount() {
final StatusAdapter adapter = (StatusAdapter) recyclerView.getAdapter();
int count = 0;
if (adapter.isConversationsLoading()) {
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE);
}
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION_ERROR);
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION);
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_STATUS);
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_REPLY);
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_REPLY_ERROR);
if (adapter.isRepliesLoading() && mSpaceHeight <= 0) {
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_REPLY_LOAD_MORE);
}
return count;
}
protected int getScrollBarStartOffset() {
final StatusAdapter adapter = (StatusAdapter) recyclerView.getAdapter();
int offset = 0;
if (!adapter.isConversationsLoading()) {
offset = adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE);
}
return offset;
}
}
public static class StatusActivitySummaryLoader extends AsyncTaskLoader<StatusActivity> {

View File

@ -1701,7 +1701,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Override
public SingleResponse<UserRelationship> loadInBackground() {
final boolean isFiltering = Utils.isFilteringUser(context, userId);
final boolean isFiltering = DataStoreUtils.isFilteringUser(context, userId);
if (accountId == userId)
return SingleResponse.getInstance();
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(context, accountId, false);

View File

@ -111,7 +111,7 @@ public class UserMediaTimelineFragment extends AbsContentRecyclerViewFragment<St
final StaggeredGridParcelableStatusesAdapter adapter = getAdapter();
adapter.setData(data);
if (!(loader instanceof IExtendedLoader) || ((IExtendedLoader) loader).isFromUser()) {
adapter.setLoadMoreSupported(hasMoreData(data));
adapter.setLoadMoreSupportedPosition(hasMoreData(data) ? IndicatorPosition.END : IndicatorPosition.NONE);
}
if (loader instanceof IExtendedLoader) {
((IExtendedLoader) loader).setFromUser(false);

View File

@ -51,6 +51,7 @@ import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.InboxStyle;
import android.support.v4.text.BidiFormatter;
import android.support.v4.util.LongSparseArray;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
@ -168,6 +169,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
Bus mBus;
@Inject
UserColorNameManager mUserColorNameManager;
@Inject
BidiFormatter mBidiFormatter;
private Handler mHandler;
private ContentResolver mContentResolver;
@ -745,6 +748,12 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
case VIRTUAL_TABLE_ID_EMPTY: {
return new MatrixCursor(projection);
}
case VIRTUAL_TABLE_ID_RAW_QUERY: {
if (projection != null || selection != null || sortOrder != null) {
throw new IllegalArgumentException();
}
return mDatabaseWrapper.rawQuery(uri.getLastPathSegment(), selectionArgs);
}
}
if (table == null) return null;
final Cursor c = mDatabaseWrapper.query(table, projection, selection, selectionArgs, null, null, sortOrder);
@ -1163,8 +1172,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
if (uri == null) return;
switch (tableId) {
case TABLE_ID_ACCOUNTS: {
Utils.clearAccountColor();
Utils.clearAccountName();
DataStoreUtils.clearAccountColor();
DataStoreUtils.clearAccountName();
break;
}
}

View File

@ -39,7 +39,6 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.receiver.PowerStateReceiver;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.DataStoreUtils;
import org.mariotaku.twidere.util.DebugModeUtils;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
@ -88,7 +87,7 @@ public class RefreshService extends Service implements Constants {
final AccountPreferences[] accountPrefs = AccountPreferences.getAccountPreferences(context, accountIds);
if (BROADCAST_REFRESH_HOME_TIMELINE.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new HomeRefreshableFilter());
final long[] sinceIds = DataStoreUtils.getNewestStatusIdsFromDatabase(context, Statuses.CONTENT_URI, refreshIds);
final long[] sinceIds = DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, refreshIds);
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, String.format("Auto refreshing home for %s", Arrays.toString(refreshIds)));
}
@ -105,7 +104,7 @@ public class RefreshService extends Service implements Constants {
}
} else if (BROADCAST_REFRESH_DIRECT_MESSAGES.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new MessagesRefreshableFilter());
final long[] sinceIds = DataStoreUtils.getNewestMessageIdsFromDatabase(context,
final long[] sinceIds = DataStoreUtils.getNewestMessageIds(context,
DirectMessages.Inbox.CONTENT_URI,
refreshIds);
if (BuildConfig.DEBUG) {

View File

@ -49,10 +49,10 @@ public abstract class GetActivitiesTask extends ManagedAsyncTask<Object, Object,
final Context context = twitterWrapper.getContext();
final ContentResolver cr = context.getContentResolver();
final int loadItemLimit = twitterWrapper.getPreferences().getInt(KEY_LOAD_ITEM_LIMIT);
boolean getReadPosition = false;
boolean saveReadPosition = false;
for (int i = 0; i < accountIds.length; i++) {
final long accountId = accountIds[i];
final boolean noItemsBefore = DataStoreUtils.getActivityCountInDatabase(context,
final boolean noItemsBefore = DataStoreUtils.getActivitiesCount(context,
getContentUri(), accountId) <= 0;
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(context, accountId,
true);
@ -65,46 +65,16 @@ public abstract class GetActivitiesTask extends ManagedAsyncTask<Object, Object,
paging.sinceId(sinceIds[i]);
if (maxIds == null || maxIds[i] <= 0) {
paging.setLatestResults(true);
getReadPosition = true;
saveReadPosition = true;
}
}
// We should delete old activities has intersection with new items
long[] deleteBound = new long[2];
Arrays.fill(deleteBound, -1);
try {
List<ContentValues> valuesList = new ArrayList<>();
for (Activity activity : getActivities(accountId, twitter, paging)) {
final ParcelableActivity parcelableActivity = new ParcelableActivity(activity, accountId, false);
if (deleteBound[0] < 0) {
deleteBound[0] = parcelableActivity.min_position;
} else {
deleteBound[0] = Math.min(deleteBound[0], parcelableActivity.min_position);
}
if (deleteBound[1] < 0) {
deleteBound[1] = parcelableActivity.max_position;
} else {
deleteBound[1] = Math.max(deleteBound[1], parcelableActivity.max_position);
}
final ContentValues values = ContentValuesCreator.createActivity(parcelableActivity);
values.put(Statuses.INSERTED_DATE, System.currentTimeMillis());
valuesList.add(values);
}
if (deleteBound[0] > 0 && deleteBound[1] > 0) {
Expression where = Expression.and(
Expression.equals(Activities.ACCOUNT_ID, accountId),
Expression.greaterEquals(Activities.MIN_POSITION, deleteBound[0]),
Expression.lesserEquals(Activities.MAX_POSITION, deleteBound[1])
);
int rowsDeleted = cr.delete(getContentUri(), where.getSQL(), null);
boolean insertGap = valuesList.size() >= loadItemLimit && !noItemsBefore
&& rowsDeleted <= 0;
if (insertGap && !valuesList.isEmpty()) {
valuesList.get(valuesList.size() - 1).put(Activities.IS_GAP, true);
}
}
ContentResolverUtils.bulkInsert(cr, getContentUri(), valuesList);
if (getReadPosition) {
getReadPosition(accountId, twitter);
final ResponseList<Activity> activities = getActivities(accountId, twitter, paging);
storeActivities(cr, loadItemLimit, accountId, noItemsBefore, activities);
// if (saveReadPosition && TwitterAPIFactory.isOfficialTwitterInstance(context, twitter)) {
if (saveReadPosition) {
saveReadPosition(accountId, twitter);
}
} catch (TwitterException e) {
@ -113,7 +83,44 @@ public abstract class GetActivitiesTask extends ManagedAsyncTask<Object, Object,
return null;
}
protected abstract void getReadPosition(long accountId, Twitter twitter);
private void storeActivities(ContentResolver cr, int loadItemLimit, long accountId,
boolean noItemsBefore, ResponseList<Activity> activities) {
long[] deleteBound = new long[2];
Arrays.fill(deleteBound, -1);
List<ContentValues> valuesList = new ArrayList<>();
for (Activity activity : activities) {
final ParcelableActivity parcelableActivity = new ParcelableActivity(activity, accountId, false);
if (deleteBound[0] < 0) {
deleteBound[0] = parcelableActivity.min_position;
} else {
deleteBound[0] = Math.min(deleteBound[0], parcelableActivity.min_position);
}
if (deleteBound[1] < 0) {
deleteBound[1] = parcelableActivity.max_position;
} else {
deleteBound[1] = Math.max(deleteBound[1], parcelableActivity.max_position);
}
final ContentValues values = ContentValuesCreator.createActivity(parcelableActivity);
values.put(Statuses.INSERTED_DATE, System.currentTimeMillis());
valuesList.add(values);
}
if (deleteBound[0] > 0 && deleteBound[1] > 0) {
Expression where = Expression.and(
Expression.equals(Activities.ACCOUNT_ID, accountId),
Expression.greaterEquals(Activities.MIN_POSITION, deleteBound[0]),
Expression.lesserEquals(Activities.MAX_POSITION, deleteBound[1])
);
int rowsDeleted = cr.delete(getContentUri(), where.getSQL(), null);
boolean insertGap = valuesList.size() >= loadItemLimit && !noItemsBefore
&& rowsDeleted <= 0;
if (insertGap && !valuesList.isEmpty()) {
valuesList.get(valuesList.size() - 1).put(Activities.IS_GAP, true);
}
}
ContentResolverUtils.bulkInsert(cr, getContentUri(), valuesList);
}
protected abstract void saveReadPosition(long accountId, Twitter twitter);
protected abstract ResponseList<Activity> getActivities(long accountId, Twitter twitter, Paging paging) throws TwitterException;

View File

@ -81,7 +81,7 @@ public abstract class GetStatusesTask extends ManagedAsyncTask<Object, TwitterWr
final Uri uri = getDatabaseUri();
final Context context = twitterWrapper.getContext();
final ContentResolver resolver = context.getContentResolver();
final boolean noItemsBefore = DataStoreUtils.getStatusCountInDatabase(context, uri, accountId) <= 0;
final boolean noItemsBefore = DataStoreUtils.getStatusCount(context, uri, accountId) <= 0;
final ContentValues[] values = new ContentValues[statuses.size()];
final long[] statusIds = new long[statuses.size()];
long minId = -1;

View File

@ -400,13 +400,13 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
@Override
protected Object[] doInBackground(long[][] params) {
final Object[] result = new Object[8];
result[0] = DataStoreUtils.getNewestStatusIdsFromDatabase(mContext, Statuses.CONTENT_URI, accountIds);
result[0] = DataStoreUtils.getNewestStatusIds(mContext, Statuses.CONTENT_URI, accountIds);
if (Boolean.TRUE.equals(result[1] = mPreferences.getBoolean(KEY_HOME_REFRESH_MENTIONS))) {
result[2] = DataStoreUtils.getActivityMaxPositionsFromDatabase(mContext,
result[2] = DataStoreUtils.getNewestActivityMaxPositions(mContext,
Activities.AboutMe.CONTENT_URI, accountIds);
}
if (Boolean.TRUE.equals(result[3] = mPreferences.getBoolean(KEY_HOME_REFRESH_DIRECT_MESSAGES))) {
result[4] = DataStoreUtils.getNewestMessageIdsFromDatabase(mContext, DirectMessages.Inbox.CONTENT_URI, accountIds);
result[4] = DataStoreUtils.getNewestMessageIds(mContext, DirectMessages.Inbox.CONTENT_URI, accountIds);
}
if (Boolean.TRUE.equals(result[5] = mPreferences.getBoolean(KEY_HOME_REFRESH_TRENDS))) {
result[6] = Utils.getDefaultAccountId(mContext);
@ -535,7 +535,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
mAsyncTaskManager.add(new GetActivitiesTask(this, TASK_TAG_GET_MENTIONS, accountIds, maxIds, sinceIds) {
@Override
protected void getReadPosition(long accountId, Twitter twitter) {
protected void saveReadPosition(long accountId, Twitter twitter) {
try {
CursorTimestampResponse response = twitter.getActivitiesAboutMeUnread(true);
final String tag = Utils.getReadPositionTagWithAccounts(ReadPositionTag.ACTIVITIES_ABOUT_ME, accountIds);
@ -568,7 +568,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
mAsyncTaskManager.add(new GetActivitiesTask(this, "get_activities_by_friends", accountIds, maxIds, sinceIds) {
@Override
protected void getReadPosition(long accountId, Twitter twitter) {
protected void saveReadPosition(long accountId, Twitter twitter) {
}
@ -1078,7 +1078,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
values.put(CachedRelationships.FOLLOWED_BY, false);
mResolver.update(CachedRelationships.CONTENT_URI, values,
Expression.inArgs(CachedRelationships.USER_ID, list.size()).getSQL(),
TwidereArrayUtils.toStringArray(list));
TwidereListUtils.toStringArray(list));
}
@Override

View File

@ -78,7 +78,7 @@ public class ContentListScrollListener extends OnScrollListener {
final Object adapter = mContentListSupport.getAdapter();
if (!(adapter instanceof ILoadMoreSupportAdapter)) return;
final ILoadMoreSupportAdapter loadMoreAdapter = (ILoadMoreSupportAdapter) adapter;
if (!mContentListSupport.isRefreshing() && loadMoreAdapter.isLoadMoreSupported()
if (!mContentListSupport.isRefreshing() && loadMoreAdapter.getLoadMoreSupportedPosition() != IndicatorPosition.NONE
&& loadMoreAdapter.getLoadMoreIndicatorPosition() == IndicatorPosition.NONE) {
int position = 0;
if (mContentListSupport.isReachingEnd() && mScrollDirection >= 0) {
@ -87,6 +87,7 @@ public class ContentListScrollListener extends OnScrollListener {
if (mContentListSupport.isReachingStart() && mScrollDirection <= 0) {
position |= IndicatorPosition.START;
}
resetScrollDirection();
mContentListSupport.onLoadMoreContents(position);
}
}

View File

@ -25,12 +25,17 @@ import android.content.UriMatcher;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.LongSparseArray;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.sqliteqb.library.ArgsArray;
import org.mariotaku.sqliteqb.library.Columns;
import org.mariotaku.sqliteqb.library.Columns.Column;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.sqliteqb.library.OrderBy;
import org.mariotaku.sqliteqb.library.RawItemArray;
import org.mariotaku.sqliteqb.library.SQLFunctions;
import org.mariotaku.sqliteqb.library.SQLQueryBuilder;
@ -67,6 +72,9 @@ import org.mariotaku.twidere.util.content.ContentResolverUtils;
import java.util.Arrays;
import static android.text.TextUtils.isEmpty;
import static org.mariotaku.twidere.provider.TwidereDataStore.CACHE_URIS;
import static org.mariotaku.twidere.provider.TwidereDataStore.DIRECT_MESSAGES_URIS;
import static org.mariotaku.twidere.provider.TwidereDataStore.STATUSES_URIS;
/**
* Created by mariotaku on 15/11/28.
@ -74,6 +82,8 @@ import static android.text.TextUtils.isEmpty;
public class DataStoreUtils implements Constants {
static final UriMatcher CONTENT_PROVIDER_URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static LongSparseArray<Integer> sAccountColors = new LongSparseArray<>();
static LongSparseArray<String> sAccountScreenNames = new LongSparseArray<>();
static LongSparseArray<String> sAccountNames = new LongSparseArray<>();
static {
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Accounts.CONTENT_PATH,
@ -167,236 +177,61 @@ public class DataStoreUtils implements Constants {
VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH);
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_EMPTY,
VIRTUAL_TABLE_ID_EMPTY);
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_RAW_QUERY,
VIRTUAL_TABLE_ID_RAW_QUERY);
}
static LongSparseArray<String> sAccountScreenNames = new LongSparseArray<>();
static LongSparseArray<String> sAccountNames = new LongSparseArray<>();
public static long[] getNewestMessageIdsFromDatabase(final Context context, final Uri uri) {
final long[] accountIds = getActivatedAccountIds(context);
return getNewestMessageIdsFromDatabase(context, uri, accountIds);
@NonNull
public static long[] getNewestMessageIds(final Context context, final Uri uri, final long[] accountIds) {
return getLongFieldArray(context, uri, accountIds, DirectMessages.ACCOUNT_ID, DirectMessages.MESSAGE_ID,
new OrderBy(SQLFunctions.MAX(DirectMessages.MESSAGE_TIMESTAMP)));
}
public static long[] getNewestMessageIdsFromDatabase(final Context context, final Uri uri, final long[] accountIds) {
if (context == null || uri == null || accountIds == null) return null;
final String[] cols = new String[]{DirectMessages.MESSAGE_ID};
final ContentResolver resolver = context.getContentResolver();
final long[] messageIds = new long[accountIds.length];
int idx = 0;
for (final long accountId : accountIds) {
final String where = Expression.equals(DirectMessages.ACCOUNT_ID, accountId).getSQL();
final Cursor cur = ContentResolverUtils.query(resolver, uri, cols, where, null,
DirectMessages.DEFAULT_SORT_ORDER);
if (cur == null) {
continue;
}
if (cur.getCount() > 0) {
cur.moveToFirst();
messageIds[idx] = cur.getLong(cur.getColumnIndexOrThrow(DirectMessages.MESSAGE_ID));
}
cur.close();
idx++;
}
return messageIds;
@NonNull
public static long[] getNewestStatusIds(final Context context, final Uri uri, final long[] accountIds) {
return getLongFieldArray(context, uri, accountIds, Statuses.ACCOUNT_ID, Statuses.STATUS_ID,
new OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP)));
}
public static long[] getNewestStatusIdsFromDatabase(final Context context, final Uri uri) {
final long[] account_ids = getActivatedAccountIds(context);
return getNewestStatusIdsFromDatabase(context, uri, account_ids);
}
public static long[] getNewestStatusIdsFromDatabase(final Context context, final Uri uri, final long[] accountIds) {
if (context == null || uri == null || accountIds == null) return null;
final String[] cols = new String[]{Statuses.STATUS_ID};
final ContentResolver resolver = context.getContentResolver();
final long[] status_ids = new long[accountIds.length];
int idx = 0;
for (final long accountId : accountIds) {
final String where = Expression.equals(Statuses.ACCOUNT_ID, accountId).getSQL();
final Cursor cur = ContentResolverUtils
.query(resolver, uri, cols, where, null, Statuses.DEFAULT_SORT_ORDER);
if (cur == null) {
continue;
}
if (cur.getCount() > 0) {
cur.moveToFirst();
status_ids[idx] = cur.getLong(cur.getColumnIndexOrThrow(Statuses.STATUS_ID));
}
cur.close();
idx++;
}
return status_ids;
}
public static long[] getActivityMaxPositionsFromDatabase(final Context context, final Uri uri, final long[] accountIds) {
if (context == null || uri == null || accountIds == null) return null;
final String[] cols = new String[]{Activities.MAX_POSITION};
final ContentResolver resolver = context.getContentResolver();
final long[] maxPositions = new long[accountIds.length];
int idx = 0;
for (final long accountId : accountIds) {
final String where = Expression.equals(Activities.ACCOUNT_ID, accountId).getSQL();
final Cursor cur = ContentResolverUtils
.query(resolver, uri, cols, where, null, Activities.DEFAULT_SORT_ORDER);
if (cur == null) {
continue;
}
if (cur.getCount() > 0) {
cur.moveToFirst();
maxPositions[idx] = cur.getLong(cur.getColumnIndexOrThrow(Activities.MAX_POSITION));
}
cur.close();
idx++;
}
return maxPositions;
}
public static long[] getNewestActivityTimestampsFromDatabase(final Context context, final Uri uri, final long[] accountIds) {
if (context == null || uri == null || accountIds == null) return null;
final String[] cols = new String[]{Activities.TIMESTAMP};
final ContentResolver resolver = context.getContentResolver();
final long[] maxPositions = new long[accountIds.length];
int idx = 0;
for (final long accountId : accountIds) {
final String where = Expression.equals(Activities.ACCOUNT_ID, accountId).getSQL();
final Cursor cur = ContentResolverUtils
.query(resolver, uri, cols, where, null, Activities.DEFAULT_SORT_ORDER);
if (cur == null) {
continue;
}
if (cur.getCount() > 0) {
cur.moveToFirst();
maxPositions[idx] = cur.getLong(cur.getColumnIndexOrThrow(Activities.TIMESTAMP));
}
cur.close();
idx++;
}
return maxPositions;
}
public static long[] getOldestMessageIdsFromDatabase(final Context context, final Uri uri) {
final long[] account_ids = getActivatedAccountIds(context);
return getOldestMessageIdsFromDatabase(context, uri, account_ids);
}
public static long[] getOldestMessageIdsFromDatabase(final Context context, final Uri uri, final long[] accountIds) {
if (context == null || uri == null) return null;
final String[] cols = new String[]{DirectMessages.MESSAGE_ID};
final ContentResolver resolver = context.getContentResolver();
final long[] status_ids = new long[accountIds.length];
int idx = 0;
for (final long accountId : accountIds) {
final String where = Expression.equals(DirectMessages.ACCOUNT_ID, accountId).getSQL();
final Cursor cur = ContentResolverUtils.query(resolver, uri, cols, where, null, DirectMessages.MESSAGE_ID);
if (cur == null) {
continue;
}
if (cur.getCount() > 0) {
cur.moveToFirst();
status_ids[idx] = cur.getLong(cur.getColumnIndexOrThrow(DirectMessages.MESSAGE_ID));
}
cur.close();
idx++;
}
return status_ids;
@NonNull
public static long[] getOldestMessageIds(@NonNull final Context context, @NonNull final Uri uri,
@NonNull final long[] accountIds) {
return getLongFieldArray(context, uri, accountIds, DirectMessages.ACCOUNT_ID,
DirectMessages.MESSAGE_ID, new OrderBy(SQLFunctions.MIN(DirectMessages.MESSAGE_TIMESTAMP)));
}
@NonNull
public static long[] getOldestStatusIds(@NonNull final Context context, @NonNull final Uri uri,
@NonNull final long[] accountIds) {
final String[] cols = new String[]{Statuses.STATUS_ID};
final ContentResolver resolver = context.getContentResolver();
final long[] statusIds = new long[accountIds.length];
Arrays.fill(statusIds, -1);
for (int i = 0, j = accountIds.length; i < j; i++) {
long accountId = accountIds[i];
final String where = Expression.equals(Statuses.ACCOUNT_ID, accountId).getSQL();
final Cursor cur = ContentResolverUtils.query(resolver, uri, cols, where, null, Statuses.STATUS_ID);
if (cur == null) {
continue;
}
try {
if (cur.moveToFirst()) {
statusIds[i] = cur.getLong(cur.getColumnIndexOrThrow(Statuses.STATUS_ID));
}
} finally {
cur.close();
}
}
return statusIds;
return getLongFieldArray(context, uri, accountIds, Statuses.ACCOUNT_ID, Statuses.STATUS_ID,
new OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)));
}
@NonNull
public static long[] getOldestActivityMinPositions(@NonNull final Context context,
@NonNull final Uri uri,
@NonNull final long[] accountIds) {
return getOldestActivityLongField(context, uri, accountIds, Activities.MIN_POSITION);
public static long[] getNewestActivityMaxPositions(final Context context, final Uri uri, final long[] accountIds) {
return getLongFieldArray(context, uri, accountIds, Activities.ACCOUNT_ID,
Activities.MAX_POSITION, new OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)));
}
@NonNull
public static long[] getOldestActivityMaxPositions(@NonNull final Context context,
@NonNull final Uri uri,
@NonNull final long[] accountIds) {
return getOldestActivityLongField(context, uri, accountIds, Activities.MAX_POSITION);
return getLongFieldArray(context, uri, accountIds, Activities.ACCOUNT_ID,
Activities.MAX_POSITION, new OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP)));
}
@NonNull
public static long[] getOldestActivityLongField(@NonNull final Context context,
@NonNull final Uri uri,
@NonNull final long[] accountIds,
@NonNull final String column) {
final String[] cols = new String[]{column};
final ContentResolver resolver = context.getContentResolver();
final long[] activityIds = new long[accountIds.length];
for (int i = 0, j = accountIds.length; i < j; i++) {
long accountId = accountIds[i];
final String where = Expression.equals(Activities.ACCOUNT_ID, accountId).getSQL();
final Cursor cur = ContentResolverUtils.query(resolver, uri, cols, where, null, Activities.TIMESTAMP);
if (cur == null) {
continue;
}
try {
if (cur.moveToFirst()) {
activityIds[i] = cur.getLong(cur.getColumnIndexOrThrow(column));
}
} finally {
cur.close();
}
}
return activityIds;
}
public static int getStatusCountInDatabase(final Context context, final Uri uri, final long accountId) {
public static int getStatusCount(final Context context, final Uri uri, final long accountId) {
final String where = Expression.equals(Statuses.ACCOUNT_ID, accountId).getSQL();
return queryCount(context, uri, where, null);
}
public static int getActivityCountInDatabase(final Context context, final Uri uri, final long accountId) {
public static int getActivitiesCount(final Context context, final Uri uri, final long accountId) {
final String where = Expression.equals(Activities.ACCOUNT_ID, accountId).getSQL();
return queryCount(context, uri, where, null);
}
public static int queryCount(final Context context, final Uri uri, final String selection, final String[] selectionArgs) {
if (context == null) return -1;
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{SQLFunctions.COUNT()};
final Cursor cur = ContentResolverUtils.query(resolver, uri, projection, selection, selectionArgs, null);
if (cur == null) return -1;
try {
if (cur.moveToFirst()) {
return cur.getInt(0);
}
return -1;
} finally {
cur.close();
}
}
@NonNull
public static long[] getFilteredUserIds(Context context) {
@ -424,48 +259,48 @@ public class DataStoreUtils implements Constants {
@NonNull
public static Expression buildStatusFilterWhereClause(@NonNull final String table, final Expression extraSelection) {
final SQLSelectQuery filteredUsersQuery = SQLQueryBuilder
.select(new Columns.Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_ID))
.select(new Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_ID))
.from(new Tables(Filters.Users.TABLE_NAME))
.build();
final Expression filteredUsersWhere = Expression.or(
Expression.in(new Columns.Column(new Table(table), Statuses.USER_ID), filteredUsersQuery),
Expression.in(new Columns.Column(new Table(table), Statuses.RETWEETED_BY_USER_ID), filteredUsersQuery),
Expression.in(new Columns.Column(new Table(table), Statuses.QUOTED_USER_ID), filteredUsersQuery)
Expression.in(new Column(new Table(table), Statuses.USER_ID), filteredUsersQuery),
Expression.in(new Column(new Table(table), Statuses.RETWEETED_BY_USER_ID), filteredUsersQuery),
Expression.in(new Column(new Table(table), Statuses.QUOTED_USER_ID), filteredUsersQuery)
);
final SQLSelectQuery.Builder filteredIdsQueryBuilder = SQLQueryBuilder
.select(new Columns.Column(new Table(table), Statuses._ID))
.select(new Column(new Table(table), Statuses._ID))
.from(new Tables(table))
.where(filteredUsersWhere)
.union()
.select(new Columns(new Columns.Column(new Table(table), Statuses._ID)))
.select(new Columns(new Column(new Table(table), Statuses._ID)))
.from(new Tables(table, Filters.Sources.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(new Columns.Column(new Table(table), Statuses.SOURCE),
Expression.likeRaw(new Column(new Table(table), Statuses.SOURCE),
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"),
Expression.likeRaw(new Columns.Column(new Table(table), Statuses.QUOTED_SOURCE),
Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_SOURCE),
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'")
))
.union()
.select(new Columns(new Columns.Column(new Table(table), Statuses._ID)))
.select(new Columns(new Column(new Table(table), Statuses._ID)))
.from(new Tables(table, Filters.Keywords.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(new Columns.Column(new Table(table), Statuses.TEXT_PLAIN),
Expression.likeRaw(new Column(new Table(table), Statuses.TEXT_PLAIN),
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"),
Expression.likeRaw(new Columns.Column(new Table(table), Statuses.QUOTED_TEXT_PLAIN),
Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_TEXT_PLAIN),
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'")
))
.union()
.select(new Columns(new Columns.Column(new Table(table), Statuses._ID)))
.select(new Columns(new Column(new Table(table), Statuses._ID)))
.from(new Tables(table, Filters.Links.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(new Columns.Column(new Table(table), Statuses.TEXT_HTML),
Expression.likeRaw(new Column(new Table(table), Statuses.TEXT_HTML),
"'%>%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%</a>%'"),
Expression.likeRaw(new Columns.Column(new Table(table), Statuses.QUOTED_TEXT_HTML),
Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_TEXT_HTML),
"'%>%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%</a>%'")
));
final Expression filterExpression = Expression.or(
Expression.notIn(new Columns.Column(new Table(table), Statuses._ID), filteredIdsQueryBuilder.build()),
Expression.equals(new Columns.Column(new Table(table), Statuses.IS_GAP), 1)
Expression.notIn(new Column(new Table(table), Statuses._ID), filteredIdsQueryBuilder.build()),
Expression.equals(new Column(new Table(table), Statuses.IS_GAP), 1)
);
if (extraSelection != null) {
return Expression.and(filterExpression, extraSelection);
@ -473,28 +308,6 @@ public class DataStoreUtils implements Constants {
return filterExpression;
}
public static String[] getAccountNames(final Context context, final long[] accountIds) {
if (context == null) return new String[0];
final String[] cols = new String[]{Accounts.NAME};
final String where = accountIds != null ? Expression.in(new Columns.Column(Accounts.ACCOUNT_ID),
new RawItemArray(accountIds)).getSQL() : null;
final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, cols, where,
null, null);
if (cur == null) return new String[0];
try {
cur.moveToFirst();
final String[] names = new String[cur.getCount()];
int i = 0;
while (!cur.isAfterLast()) {
names[i++] = cur.getString(0);
cur.moveToNext();
}
return names;
} finally {
cur.close();
}
}
public static String getAccountScreenName(final Context context, final long accountId) {
if (context == null) return null;
final String cached = sAccountScreenNames.get(accountId);
@ -515,22 +328,13 @@ public class DataStoreUtils implements Constants {
}
public static String[] getAccountScreenNames(final Context context) {
return getAccountScreenNames(context, false);
}
public static String[] getAccountScreenNames(final Context context, final boolean includeAtChar) {
return getAccountScreenNames(context, null, includeAtChar);
return getAccountScreenNames(context, null);
}
public static String[] getAccountScreenNames(final Context context, final long[] accountIds) {
return getAccountScreenNames(context, accountIds, false);
}
public static String[] getAccountScreenNames(final Context context, final long[] accountIds,
final boolean includeAtChar) {
if (context == null) return new String[0];
final String[] cols = new String[]{Accounts.SCREEN_NAME};
final String where = accountIds != null ? Expression.in(new Columns.Column(Accounts.ACCOUNT_ID),
final String where = accountIds != null ? Expression.in(new Column(Accounts.ACCOUNT_ID),
new RawItemArray(accountIds)).getSQL() : null;
final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, cols, where,
null, null);
@ -568,25 +372,8 @@ public class DataStoreUtils implements Constants {
}
}
public static int getAllStatusesCount(final Context context, @NonNull final Uri uri) {
if (context == null) return 0;
final ContentResolver resolver = context.getContentResolver();
final String table = getTableNameByUri(uri);
if (table == null) return 0;
final Cursor cur = ContentResolverUtils.query(resolver, uri, new String[]{Statuses.STATUS_ID},
buildStatusFilterWhereClause(table, null).getSQL(),
null, null);
if (cur == null) return 0;
try {
return cur.getCount();
} finally {
cur.close();
}
}
public static int getStatusesCount(final Context context, final Uri uri, final long sinceId, final long... accountIds) {
if (context == null) return 0;
final ContentResolver resolver = context.getContentResolver();
final RawItemArray idsIn;
if (accountIds == null || accountIds.length == 0 || (accountIds.length == 1 && accountIds[0] < 0)) {
idsIn = new RawItemArray(getActivatedAccountIds(context));
@ -594,7 +381,7 @@ public class DataStoreUtils implements Constants {
idsIn = new RawItemArray(accountIds);
}
final Expression selection = Expression.and(
Expression.in(new Columns.Column(Statuses.ACCOUNT_ID), idsIn),
Expression.in(new Column(Statuses.ACCOUNT_ID), idsIn),
Expression.greaterThan(Statuses.STATUS_ID, sinceId),
buildStatusFilterWhereClause(getTableNameByUri(uri), null)
);
@ -618,7 +405,7 @@ public class DataStoreUtils implements Constants {
} else {
expressions = new Expression[3];
}
expressions[0] = Expression.in(new Columns.Column(Activities.ACCOUNT_ID), idsIn);
expressions[0] = Expression.in(new Column(Activities.ACCOUNT_ID), idsIn);
expressions[1] = Expression.greaterThan(Activities.TIMESTAMP, sinceTimestamp);
expressions[2] = buildActivityFilterWhereClause(getTableNameByUri(uri), null);
final Expression selection = Expression.and(expressions);
@ -687,48 +474,48 @@ public class DataStoreUtils implements Constants {
@NonNull
public static Expression buildActivityFilterWhereClause(@NonNull final String table, final Expression extraSelection) {
final SQLSelectQuery filteredUsersQuery = SQLQueryBuilder
.select(new Columns.Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_ID))
.select(new Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_ID))
.from(new Tables(Filters.Users.TABLE_NAME))
.build();
final Expression filteredUsersWhere = Expression.or(
Expression.in(new Columns.Column(new Table(table), Activities.STATUS_USER_ID), filteredUsersQuery),
Expression.in(new Columns.Column(new Table(table), Activities.STATUS_RETWEETED_BY_USER_ID), filteredUsersQuery),
Expression.in(new Columns.Column(new Table(table), Activities.STATUS_QUOTED_USER_ID), filteredUsersQuery)
Expression.in(new Column(new Table(table), Activities.STATUS_USER_ID), filteredUsersQuery),
Expression.in(new Column(new Table(table), Activities.STATUS_RETWEETED_BY_USER_ID), filteredUsersQuery),
Expression.in(new Column(new Table(table), Activities.STATUS_QUOTED_USER_ID), filteredUsersQuery)
);
final SQLSelectQuery.Builder filteredIdsQueryBuilder = SQLQueryBuilder
.select(new Columns.Column(new Table(table), Activities._ID))
.select(new Column(new Table(table), Activities._ID))
.from(new Tables(table))
.where(filteredUsersWhere)
.union()
.select(new Columns(new Columns.Column(new Table(table), Activities._ID)))
.select(new Columns(new Column(new Table(table), Activities._ID)))
.from(new Tables(table, Filters.Sources.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(new Columns.Column(new Table(table), Activities.STATUS_SOURCE),
Expression.likeRaw(new Column(new Table(table), Activities.STATUS_SOURCE),
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"),
Expression.likeRaw(new Columns.Column(new Table(table), Activities.STATUS_QUOTE_SOURCE),
Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_SOURCE),
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'")
))
.union()
.select(new Columns(new Columns.Column(new Table(table), Activities._ID)))
.select(new Columns(new Column(new Table(table), Activities._ID)))
.from(new Tables(table, Filters.Keywords.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(new Columns.Column(new Table(table), Activities.STATUS_TEXT_PLAIN),
Expression.likeRaw(new Column(new Table(table), Activities.STATUS_TEXT_PLAIN),
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"),
Expression.likeRaw(new Columns.Column(new Table(table), Activities.STATUS_QUOTE_TEXT_PLAIN),
Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_TEXT_PLAIN),
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'")
))
.union()
.select(new Columns(new Columns.Column(new Table(table), Activities._ID)))
.select(new Columns(new Column(new Table(table), Activities._ID)))
.from(new Tables(table, Filters.Links.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(new Columns.Column(new Table(table), Activities.STATUS_TEXT_HTML),
Expression.likeRaw(new Column(new Table(table), Activities.STATUS_TEXT_HTML),
"'%>%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%</a>%'"),
Expression.likeRaw(new Columns.Column(new Table(table), Activities.STATUS_QUOTE_TEXT_HTML),
Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_TEXT_HTML),
"'%>%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%</a>%'")
));
final Expression filterExpression = Expression.or(
Expression.notIn(new Columns.Column(new Table(table), Activities._ID), filteredIdsQueryBuilder.build()),
Expression.equals(new Columns.Column(new Table(table), Activities.IS_GAP), 1)
Expression.notIn(new Column(new Table(table), Activities._ID), filteredIdsQueryBuilder.build()),
Expression.equals(new Column(new Table(table), Activities.IS_GAP), 1)
);
if (extraSelection != null) {
return Expression.and(filterExpression, extraSelection);
@ -759,7 +546,7 @@ public class DataStoreUtils implements Constants {
public static int[] getAccountColors(final Context context, final long[] accountIds) {
if (context == null || accountIds == null) return new int[0];
final String[] cols = new String[]{Accounts.ACCOUNT_ID, Accounts.COLOR};
final String where = Expression.in(new Columns.Column(Accounts.ACCOUNT_ID), new RawItemArray(accountIds)).getSQL();
final String where = Expression.in(new Column(Accounts.ACCOUNT_ID), new RawItemArray(accountIds)).getSQL();
final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, cols, where,
null, null);
if (cur == null) return new int[0];
@ -823,6 +610,7 @@ public class DataStoreUtils implements Constants {
if (context == null) return false;
final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI,
new String[]{SQLFunctions.COUNT()}, null, null, null);
if (cur == null) return false;
try {
cur.moveToFirst();
return cur.getInt(0) > 0;
@ -850,7 +638,115 @@ public class DataStoreUtils implements Constants {
}
}
public static String[] getAccountNames(final Context context) {
return getAccountScreenNames(context, null);
public static synchronized void cleanDatabasesByItemLimit(final Context context) {
if (context == null) return;
final ContentResolver resolver = context.getContentResolver();
final int itemLimit = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE).getInt(
KEY_DATABASE_ITEM_LIMIT, DEFAULT_DATABASE_ITEM_LIMIT);
for (final long accountId : getAccountIds(context)) {
// Clean statuses.
for (final Uri uri : STATUSES_URIS) {
if (CachedStatuses.CONTENT_URI.equals(uri)) {
continue;
}
final String table = getTableNameByUri(uri);
final Expression account_where = new Expression(Statuses.ACCOUNT_ID + " = " + accountId);
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(Statuses._ID)).from(new Tables(table));
qb.where(Expression.equals(Statuses.ACCOUNT_ID, accountId));
qb.orderBy(new OrderBy(Statuses.STATUS_ID, false));
qb.limit(itemLimit);
final Expression where = Expression.and(Expression.notIn(new Column(Statuses._ID), qb.build()), account_where);
resolver.delete(uri, where.getSQL(), null);
}
for (final Uri uri : DIRECT_MESSAGES_URIS) {
final String table = getTableNameByUri(uri);
final Expression account_where = new Expression(DirectMessages.ACCOUNT_ID + " = " + accountId);
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(DirectMessages._ID)).from(new Tables(table));
qb.where(Expression.equals(DirectMessages.ACCOUNT_ID, accountId));
qb.orderBy(new OrderBy(DirectMessages.MESSAGE_ID, false));
qb.limit(itemLimit * 10);
final Expression where = Expression.and(Expression.notIn(new Column(DirectMessages._ID), qb.build()), account_where);
resolver.delete(uri, where.getSQL(), null);
}
}
// Clean cached values.
for (final Uri uri : CACHE_URIS) {
final String table = getTableNameByUri(uri);
if (table == null) continue;
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(BaseColumns._ID));
qb.from(new Tables(table));
qb.orderBy(new OrderBy(BaseColumns._ID, false));
qb.limit(itemLimit * 20);
final Expression where = Expression.notIn(new Column(BaseColumns._ID), qb.build());
resolver.delete(uri, where.getSQL(), null);
}
}
public static void clearAccountColor() {
sAccountColors.clear();
}
public static void clearAccountName() {
sAccountScreenNames.clear();
}
public static boolean isFilteringUser(Context context, long userId) {
final ContentResolver cr = context.getContentResolver();
final Expression where = Expression.equals(Filters.Users.USER_ID, userId);
final Cursor c = cr.query(Filters.Users.CONTENT_URI, new String[0], where.getSQL(), null, null);
if (c == null) return false;
try {
return c.getCount() > 0;
} finally {
c.close();
}
}
@NonNull
static long[] getLongFieldArray(@NonNull Context context, @NonNull Uri uri, @NonNull long[] keys,
@NonNull String keyField, @NonNull String valueField,
@Nullable OrderBy sortExpression) {
final ContentResolver resolver = context.getContentResolver();
final long[] messageIds = new long[keys.length];
Arrays.fill(messageIds, -1);
final String[] selectionArgs = TwidereArrayUtils.toStringArray(keys);
final SQLSelectQuery.Builder builder = SQLQueryBuilder.select(new Columns(keyField, valueField))
.from(new Table(getTableNameByUri(uri)))
.groupBy(new Column(keyField))
.having(Expression.in(new Column(keyField), new ArgsArray(keys.length)));
if (sortExpression != null) {
builder.orderBy(sortExpression);
}
final Cursor cur = ContentResolverUtils.query(resolver,
Uri.withAppendedPath(TwidereDataStore.CONTENT_URI_DATABASE_READY, builder.buildSQL()),
null, null, selectionArgs, null);
if (cur == null) return messageIds;
while (cur.moveToNext()) {
final long accountId = cur.getLong(0);
int idx = ArrayUtils.indexOf(keys, accountId);
if (idx < 0) continue;
messageIds[idx] = cur.getLong(1);
}
return messageIds;
}
static int queryCount(@NonNull final Context context, @NonNull final Uri uri,
@Nullable final String selection, @Nullable final String[] selectionArgs) {
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{SQLFunctions.COUNT()};
final Cursor cur = ContentResolverUtils.query(resolver, uri, projection, selection, selectionArgs, null);
if (cur == null) return -1;
try {
if (cur.moveToFirst()) {
return cur.getInt(0);
}
return -1;
} finally {
cur.close();
}
}
}

View File

@ -48,15 +48,13 @@ public class TwidereListUtils {
return builder.toString();
}
public static String toStringForSQL(final List<String> list) {
final int size = list != null ? list.size() : 0;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < size; i++) {
if (i > 0) {
builder.append(',');
}
builder.append('?');
public static String[] toStringArray(final List<?> list) {
if (list == null) return null;
final int length = list.size();
final String[] stringArray = new String[length];
for (int i = 0; i < length; i++) {
stringArray[i] = ParseUtils.parseString(list.get(i));
}
return builder.toString();
return stringArray;
}
}

View File

@ -60,7 +60,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
@ -119,11 +118,7 @@ import org.mariotaku.sqliteqb.library.AllColumns;
import org.mariotaku.sqliteqb.library.Columns;
import org.mariotaku.sqliteqb.library.Columns.Column;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.sqliteqb.library.OrderBy;
import org.mariotaku.sqliteqb.library.RawItemArray;
import org.mariotaku.sqliteqb.library.Selectable;
import org.mariotaku.sqliteqb.library.Tables;
import org.mariotaku.sqliteqb.library.query.SQLSelectQuery;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
@ -239,7 +234,6 @@ import edu.tsinghua.hotmobi.model.NotificationEvent;
import static android.text.TextUtils.isEmpty;
import static android.text.format.DateUtils.getRelativeTimeSpanString;
import static org.mariotaku.twidere.provider.TwidereDataStore.CACHE_URIS;
import static org.mariotaku.twidere.provider.TwidereDataStore.DIRECT_MESSAGES_URIS;
import static org.mariotaku.twidere.provider.TwidereDataStore.STATUSES_URIS;
import static org.mariotaku.twidere.util.TwidereLinkify.PATTERN_TWITTER_PROFILE_IMAGES;
@ -407,19 +401,6 @@ public final class Utils implements Constants {
accessibilityManager.sendAccessibilityEvent(event);
}
public static String buildActivatedStatsWhereClause(final Context context, final String selection) {
if (context == null) return null;
final long[] account_ids = DataStoreUtils.getActivatedAccountIds(context);
final Expression accountWhere = Expression.in(new Column(Statuses.ACCOUNT_ID), new RawItemArray(account_ids));
final Expression where;
if (selection != null) {
where = Expression.and(accountWhere, new Expression(selection));
} else {
where = accountWhere;
}
return where.getSQL();
}
public static Uri buildDirectMessageConversationUri(final long account_id, final long conversation_id,
final String screen_name) {
if (conversation_id <= 0 && screen_name == null) return TwidereDataStore.CONTENT_URI_NULL;
@ -442,62 +423,6 @@ public final class Utils implements Constants {
return !pm.queryIntentActivities(intent, 0).isEmpty();
}
public static synchronized void cleanDatabasesByItemLimit(final Context context) {
if (context == null) return;
final ContentResolver resolver = context.getContentResolver();
final int itemLimit = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE).getInt(
KEY_DATABASE_ITEM_LIMIT, DEFAULT_DATABASE_ITEM_LIMIT);
for (final long accountId : DataStoreUtils.getAccountIds(context)) {
// Clean statuses.
for (final Uri uri : STATUSES_URIS) {
if (CachedStatuses.CONTENT_URI.equals(uri)) {
continue;
}
final String table = DataStoreUtils.getTableNameByUri(uri);
final Expression account_where = new Expression(Statuses.ACCOUNT_ID + " = " + accountId);
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(Statuses._ID)).from(new Tables(table));
qb.where(Expression.equals(Statuses.ACCOUNT_ID, accountId));
qb.orderBy(new OrderBy(Statuses.STATUS_ID, false));
qb.limit(itemLimit);
final Expression where = Expression.and(Expression.notIn(new Column(Statuses._ID), qb.build()), account_where);
resolver.delete(uri, where.getSQL(), null);
}
for (final Uri uri : DIRECT_MESSAGES_URIS) {
final String table = DataStoreUtils.getTableNameByUri(uri);
final Expression account_where = new Expression(DirectMessages.ACCOUNT_ID + " = " + accountId);
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(DirectMessages._ID)).from(new Tables(table));
qb.where(Expression.equals(DirectMessages.ACCOUNT_ID, accountId));
qb.orderBy(new OrderBy(DirectMessages.MESSAGE_ID, false));
qb.limit(itemLimit * 10);
final Expression where = Expression.and(Expression.notIn(new Column(DirectMessages._ID), qb.build()), account_where);
resolver.delete(uri, where.getSQL(), null);
}
}
// Clean cached values.
for (final Uri uri : CACHE_URIS) {
final String table = DataStoreUtils.getTableNameByUri(uri);
if (table == null) continue;
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(BaseColumns._ID));
qb.from(new Tables(table));
qb.orderBy(new OrderBy(BaseColumns._ID, false));
qb.limit(itemLimit * 20);
final Expression where = Expression.notIn(new Column(BaseColumns._ID), qb.build());
resolver.delete(uri, where.getSQL(), null);
}
}
public static void clearAccountColor() {
DataStoreUtils.sAccountColors.clear();
}
public static void clearAccountName() {
DataStoreUtils.sAccountScreenNames.clear();
}
public static void clearListViewChoices(final AbsListView view) {
if (view == null) return;
final ListAdapter adapter = view.getAdapter();
@ -2997,35 +2922,16 @@ public final class Utils implements Constants {
final ListView listView = fragment.getListView();
listView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
listView.setClipToPadding(false);
// if (listView instanceof RefreshNowListView) {
// final View indicatorView = ((RefreshNowListView) listView).getRefreshIndicatorView();
// final LayoutParams lp = indicatorView.getLayoutParams();
// if (lp instanceof MarginLayoutParams) {
// ((MarginLayoutParams) lp).topMargin = insets.top;
// indicatorView.setLayoutParams(lp);
// }
// }
}
public static boolean isFilteringUser(Context context, long userId) {
final ContentResolver cr = context.getContentResolver();
final Expression where = Expression.equals(Users.USER_ID, userId);
final Cursor c = cr.query(Users.CONTENT_URI, new String[0], where.getSQL(), null, null);
//noinspection TryFinallyCanBeTryWithResources
try {
return c.getCount() > 0;
} finally {
c.close();
}
}
@Nullable
public static ParcelableUser getUserForConversation(Context context, long accountId,
long conversationId) {
final ContentResolver cr = context.getContentResolver();
final Expression where = Expression.and(Expression.equals(ConversationEntries.ACCOUNT_ID, accountId),
Expression.equals(ConversationEntries.CONVERSATION_ID, conversationId));
final Cursor c = cr.query(ConversationEntries.CONTENT_URI, null, where.getSQL(), null, null);
//noinspection TryFinallyCanBeTryWithResources
if (c == null) return null;
try {
if (c.moveToFirst()) return ParcelableUser.fromDirectMessageConversationEntry(c);
} finally {

View File

@ -159,7 +159,7 @@ public final class DatabaseUpgradeHelper {
notNullCols[count++] = column.getName();
}
}
return TwidereArrayUtils.subArray(notNullCols, 0, count);
return ArrayUtils.subarray(notNullCols, 0, count);
}
private static Map<String, String> getTypeMapByCreateQuery(final String query) {

View File

@ -37,6 +37,7 @@ import org.mariotaku.inetaddrjni.library.InetAddressUtils;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.TwidereMathUtils;
import org.xbill.DNS.AAAARecord;
import org.xbill.DNS.ARecord;
import org.xbill.DNS.DClass;
@ -217,7 +218,7 @@ public class TwidereDns implements Constants, Dns {
} else {
continue;
}
if (mConnnectTimeout == 0 || inetAddress.isReachable((int) mConnnectTimeout)) {
if (mConnnectTimeout == 0 || inetAddress.isReachable(TwidereMathUtils.clamp((int) mConnnectTimeout / 2, 1000, 3000))) {
resolvedAddresses.add(InetAddress.getByAddress(originalHost, inetAddress.getAddress()));
}
}

View File

@ -36,7 +36,9 @@
<org.mariotaku.twidere.view.ExtendedRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:fadeScrollbars="false"
android:scrollbars="vertical">
<requestFocus/>
</org.mariotaku.twidere.view.ExtendedRecyclerView>