improved swipe refresh progress position

updated sdk version
This commit is contained in:
Mariotaku Lee 2015-08-20 10:19:02 +08:00
parent aca39a6016
commit 470ecc3726
24 changed files with 603 additions and 172 deletions

View File

@ -17,8 +17,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
android {
compileSdkVersion 22
buildToolsVersion '22.0.1'
compileSdkVersion 23
buildToolsVersion '23.0.0'
lintOptions {
abortOnError false

View File

@ -1,6 +1,6 @@
#Sun Aug 16 21:56:31 CST 2015
#Wed Aug 19 14:17:02 CST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-all.zip

View File

@ -5,7 +5,7 @@ include ':twidere.wear'
include ':twidere.donate.nyanwp'
include ':twidere.donate.nyanwp.wear'
include ':twidere.component.nyan'
include ':twidere.extension.twitlonger'
//include ':twidere.extension.twitlonger'
include ':twidere.extension.push.xiaomi'
include ':twidere.extension.launcher.compose'
include ':twidere.extension.shortener.gist'

View File

@ -13,7 +13,7 @@ android {
defaultConfig {
applicationId "org.mariotaku.twidere"
minSdkVersion 14
targetSdkVersion 22
targetSdkVersion 23
versionCode 117
versionName "0.3.0"
multiDexEnabled true
@ -59,10 +59,10 @@ dependencies {
apt 'com.bluelinelabs:logansquare-compiler:1.1.0'
apt 'com.hannesdorfmann.parcelableplease:processor:1.0.1'
compile 'com.android.support:multidex:1.0.1'
compile 'com.android.support:support-v13:22.2.1'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:cardview-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'
compile 'com.android.support:support-v13:23.0.0'
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'com.android.support:cardview-v7:23.0.0'
compile 'com.android.support:recyclerview-v7:23.0.0'
compile 'com.twitter:twitter-text:1.12.1'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'
compile 'com.squareup:otto:1.3.8'
@ -91,7 +91,7 @@ dependencies {
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.1'
compile 'com.github.mariotaku:PickNCrop:44b09cbc69'
compile 'com.diogobernardino:williamchart:2.0.1'
googleCompile 'com.google.android.gms:play-services-maps:7.5.0'
googleCompile 'com.google.android.gms:play-services-maps:7.8.0'
googleCompile 'com.google.maps.android:android-maps-utils:0.4'
fdroidCompile 'org.osmdroid:osmdroid-android:4.3'
fdroidCompile 'org.slf4j:slf4j-simple:1.7.12'

View File

@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.osmdroid.views.MapView xmlns:android="http://schemas.android.com/apk/res/android"
<org.osmdroid.views.MapView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -20,59 +20,69 @@
package edu.tsinghua.hotmobi;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.text.TextUtils;
import android.util.Log;
import com.bluelinelabs.logansquare.LoganSquare;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.util.Utils;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import edu.tsinghua.hotmobi.model.BaseEvent;
import edu.tsinghua.hotmobi.model.FirstLaunchEvent;
import edu.tsinghua.hotmobi.model.LatLng;
import edu.tsinghua.hotmobi.model.MediaEvent;
import edu.tsinghua.hotmobi.model.RefreshEvent;
import edu.tsinghua.hotmobi.model.SessionEvent;
import edu.tsinghua.hotmobi.model.TweetEvent;
import edu.tsinghua.hotmobi.model.TweetType;
/**
* Created by mariotaku on 15/8/10.
*/
public class HotMobiLogger {
public static final long ACCOUNT_ID_NOT_NEEDED = -1;
private static final String LOGTAG = "HotMobiLogger";
private final Executor mExecutor;
public HotMobiLogger() {
mExecutor = Executors.newSingleThreadExecutor();
}
public void log(BaseEvent event) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
}
});
}
private static String getLogFilename(BaseEvent event) {
if (event instanceof FirstLaunchEvent) {
return "first_launch";
} else if (event instanceof RefreshEvent) {
if (event instanceof RefreshEvent) {
return "refresh";
} else if (event instanceof SessionEvent) {
return "session";
} else if (event instanceof TweetEvent) {
return "tweet";
} else if (event instanceof MediaEvent) {
return "media";
}
return null;
}
public static int getTweetType(ParcelableStatus status) {
return TweetType.TEXT;
public static String getInstallationSerialId(Context context) {
final SharedPreferences prefs = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
final String persistedDeviceId = prefs.getString(Constants.KEY_DEVICE_SERIAL, null);
final String uuid;
if (!TextUtils.isEmpty(persistedDeviceId)) {
uuid = persistedDeviceId.replaceAll("[^\\w\\d]", "");
} else {
uuid = UUID.randomUUID().toString().replaceAll("[^\\w\\d]", "");
prefs.edit().putString(Constants.KEY_DEVICE_SERIAL, uuid).apply();
}
return uuid;
}
public static HotMobiLogger getInstance(Context context) {
@ -84,4 +94,22 @@ public class HotMobiLogger {
if (location == null) return null;
return new LatLng(location.getLatitude(), location.getLongitude());
}
public void log(long accountId, final Object event) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Log.d(LOGTAG, LoganSquare.serialize(event));
} catch (IOException e) {
Log.w(LOGTAG, e);
}
}
});
}
public void log(Object event) {
log(ACCOUNT_ID_NOT_NEEDED, event);
}
}

View File

@ -1,37 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.tsinghua.hotmobi.model;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import java.util.Map;
/**
* Created by mariotaku on 15/8/8.
*/
@JsonObject
public class FirstLaunchEvent extends BaseEvent{
@JsonField(name = "device_id")
String deviceId;
@JsonField(name = "configuration")
Map<String, String> configuration;
}

View File

@ -27,8 +27,6 @@ import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import edu.tsinghua.hotmobi.HotMobiLogger;
/**
* Created by mariotaku on 15/8/7.
*/
@ -39,12 +37,10 @@ public class MediaEvent extends BaseEvent {
long id;
@JsonField(name = "user_id")
long userId;
@JsonField(name = "tweet_type")
int tweetType;
@JsonField(name = "timeline_type")
int timelineType;
@JsonField(name = "action")
int action;
@JsonField(name = "tweet_type", typeConverter = TweetType.TweetTypeConverter.class)
TweetType tweetType;
@JsonField(name = "timeline_type", typeConverter = TimelineType.TimelineTypeConverter.class)
TimelineType timelineType;
@JsonField(name = "preview_url")
String previewUrl;
@JsonField(name = "media_url")
@ -52,7 +48,7 @@ public class MediaEvent extends BaseEvent {
@JsonField(name = "preview_enabled")
boolean previewEnabled;
public static MediaEvent create(Context context, ParcelableStatus status, ParcelableMedia media, int timelineType, boolean previewEnabled) {
public static MediaEvent create(Context context, ParcelableStatus status, ParcelableMedia media, TimelineType timelineType, boolean previewEnabled) {
final MediaEvent event = new MediaEvent();
event.markStart(context);
event.setId(status.id);
@ -61,7 +57,7 @@ public class MediaEvent extends BaseEvent {
event.setPreviewUrl(media.preview_url);
event.setPreviewEnabled(previewEnabled);
event.setTimelineType(timelineType);
event.setTweetType(HotMobiLogger.getTweetType(status));
event.setTweetType(TweetType.getTweetType(status));
return event;
}
@ -77,10 +73,6 @@ public class MediaEvent extends BaseEvent {
this.previewUrl = previewUrl;
}
public void setAction(int action) {
this.action = action;
}
public void setId(long id) {
this.id = id;
}
@ -89,20 +81,13 @@ public class MediaEvent extends BaseEvent {
this.userId = userId;
}
public void setTweetType(int tweetType) {
public void setTweetType(TweetType tweetType) {
this.tweetType = tweetType;
}
public void setTimelineType(int timelineType) {
public void setTimelineType(TimelineType timelineType) {
this.timelineType = timelineType;
}
public interface Action {
int OPEN = 0;
int RETWEET = 1;
int FAVORITE = 2;
int UNFAVORITE = -2;
}
}

View File

@ -32,18 +32,18 @@ public class RefreshEvent extends BaseEvent {
@JsonField(name = "ids")
long[] ids;
@JsonField(name = "timeline_type")
int timelineType;
@JsonField(name = "timeline_type", typeConverter = TimelineType.TimelineTypeConverter.class)
TimelineType timelineType;
public void setIds(long[] ids) {
this.ids = ids;
}
public void setTimelineType(int timelineType) {
public void setTimelineType(TimelineType timelineType) {
this.timelineType = timelineType;
}
public static RefreshEvent create(final Context context, long[] ids, int timelineType) {
public static RefreshEvent create(final Context context, long[] ids, TimelineType timelineType) {
final RefreshEvent event = new RefreshEvent();
event.markStart(context);
event.setIds(ids);

View File

@ -27,8 +27,30 @@ import com.bluelinelabs.logansquare.annotation.JsonObject;
*/
@JsonObject
public class ScrollRecord {
@JsonField(name = "count")
long count;
@JsonField(name = "total")
long total;
@JsonField(name = "id")
long id;
@JsonField(name = "timestamp")
long timestamp;
@JsonField(name = "scroll_state")
int scrollState;
public void setId(long id) {
this.id = id;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public void setScrollState(int scrollState) {
this.scrollState = scrollState;
}
public static ScrollRecord create(long id, long timestamp, int scrollState) {
final ScrollRecord record = new ScrollRecord();
record.setId(id);
record.setTimestamp(timestamp);
record.setScrollState(scrollState);
return record;
}
}

View File

@ -19,14 +19,32 @@
package edu.tsinghua.hotmobi.model;
import java.util.Map;
import android.content.Context;
import android.content.res.Configuration;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
/**
* Created by mariotaku on 15/8/8.
*/
@JsonObject
public class SessionEvent extends BaseEvent {
Map<String, ScrollRecord> scrollRecords;
@JsonField(name = "configuration")
String configuration;
public static SessionEvent create(Context context) {
final SessionEvent event = new SessionEvent();
event.markStart(context);
final Context appContext = context.getApplicationContext();
final Configuration conf = appContext.getResources().getConfiguration();
event.setConfiguration(conf.toString());
return event;
}
public void setConfiguration(String configuration) {
this.configuration = configuration;
}
}

View File

@ -0,0 +1,58 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.tsinghua.hotmobi.model;
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter;
/**
* Created by mariotaku on 15/8/18.
*/
public enum TimelineType {
HOME("home"), INTERACTIONS("interactions"), OTHER("other");
private final String value;
TimelineType(String value) {
this.value = value;
}
public static TimelineType parse(String type) {
if (HOME.value.equalsIgnoreCase(type)) {
return HOME;
} else if (INTERACTIONS.value.equalsIgnoreCase(type)) {
return INTERACTIONS;
}
return OTHER;
}
public static class TimelineTypeConverter extends StringBasedTypeConverter<TimelineType> {
@Override
public TimelineType getFromString(String string) {
return TimelineType.parse(string);
}
@Override
public String convertToString(TimelineType timelineType) {
if (timelineType == null) return null;
return timelineType.value;
}
}
}

View File

@ -23,11 +23,10 @@ import android.content.Context;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter;
import org.mariotaku.twidere.model.ParcelableStatus;
import edu.tsinghua.hotmobi.HotMobiLogger;
/**
* Created by mariotaku on 15/8/7.
*/
@ -36,16 +35,33 @@ public class TweetEvent extends BaseEvent {
@JsonField(name = "id")
long id;
@JsonField(name = "account_id")
long accountId;
@JsonField(name = "user_id")
long userId;
@JsonField(name = "tweet_type")
int tweetType;
@JsonField(name = "timeline_type")
int timelineType;
@JsonField(name = "action")
int action;
@JsonField(name = "tweet_type", typeConverter = TweetType.TweetTypeConverter.class)
TweetType tweetType;
@JsonField(name = "timeline_type", typeConverter = TimelineType.TimelineTypeConverter.class)
TimelineType timelineType;
@JsonField(name = "action", typeConverter = Action.TweetActionConverter.class)
Action action;
public void setAction(int action) {
public static TweetEvent create(Context context, ParcelableStatus status, TimelineType timelineType) {
final TweetEvent event = new TweetEvent();
event.markStart(context);
event.setId(status.id);
event.setAccountId(status.account_id);
event.setUserId(status.user_id);
event.setTimelineType(timelineType);
event.setTweetType(TweetType.getTweetType(status));
return event;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
public void setAction(Action action) {
this.action = action;
}
@ -57,30 +73,54 @@ public class TweetEvent extends BaseEvent {
this.userId = userId;
}
public void setTweetType(int tweetType) {
public void setTweetType(TweetType tweetType) {
this.tweetType = tweetType;
}
public void setTimelineType(int timelineType) {
public void setTimelineType(TimelineType timelineType) {
this.timelineType = timelineType;
}
public static TweetEvent create(Context context, ParcelableStatus status, int timelineType) {
final TweetEvent event = new TweetEvent();
event.markStart(context);
event.setId(status.id);
event.setUserId(status.user_id);
event.setTimelineType(timelineType);
event.setTweetType(HotMobiLogger.getTweetType(status));
return event;
public long getAccountId() {
return accountId;
}
public interface Action {
int OPEN = 0;
int RETWEET = 1;
int FAVORITE = 2;
public enum Action {
OPEN("open"), RETWEET("retweet"), FAVORITE("favorite"), UNFAVORITE("unfavorite"), UNKNOWN("unknown");
int UNFAVORITE =-2;
private final String value;
Action(String value) {
this.value = value;
}
public static Action parse(String action) {
if (OPEN.value.equalsIgnoreCase(action)) {
return OPEN;
} else if (RETWEET.value.equalsIgnoreCase(action)) {
return RETWEET;
} else if (FAVORITE.value.equalsIgnoreCase(action)) {
return FAVORITE;
} else if (UNFAVORITE.value.equalsIgnoreCase(action)) {
return UNFAVORITE;
}
return UNKNOWN;
}
public static class TweetActionConverter extends StringBasedTypeConverter<Action> {
@Override
public Action getFromString(String string) {
return Action.parse(string);
}
@Override
public String convertToString(Action action) {
if (action == null) return null;
return action.value;
}
}
}
}

View File

@ -19,12 +19,71 @@
package edu.tsinghua.hotmobi.model;
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
/**
* Created by mariotaku on 15/8/13.
*/
public interface TweetType {
int TEXT = 0;
int PHOTO = 1;
int VIDEO = 2;
int OTHER = 3;
public enum TweetType {
TEXT("text"), PHOTO("photo"), VIDEO("video"), OTHER("other");
public static TweetType getTweetType(ParcelableStatus status) {
if (status.media != null) {
boolean hasImage = false;
for (ParcelableMedia media : status.media) {
switch (media.type) {
case ParcelableMedia.TYPE_ANIMATED_GIF:
case ParcelableMedia.TYPE_CARD_ANIMATED_GIF:
case ParcelableMedia.TYPE_VIDEO:
return VIDEO;
case ParcelableMedia.TYPE_IMAGE: {
hasImage = true;
break;
}
}
}
if (hasImage) {
return PHOTO;
}
}
return TEXT;
}
public String getValue() {
return value;
}
private final String value;
TweetType(String value) {
this.value = value;
}
public static TweetType parse(String type) {
if (TEXT.value.equalsIgnoreCase(type)) {
return TEXT;
} else if (PHOTO.value.equalsIgnoreCase(type)) {
return PHOTO;
} else if (VIDEO.value.equalsIgnoreCase(type)) {
return VIDEO;
}
return OTHER;
}
public static class TweetTypeConverter extends StringBasedTypeConverter<TweetType> {
@Override
public TweetType getFromString(String string) {
return TweetType.parse(string);
}
@Override
public String convertToString(TweetType tweetType) {
if (tweetType == null) return null;
return tweetType.value;
}
}
}

View File

@ -114,6 +114,8 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.SessionEvent;
import edu.tsinghua.spice.Utilies.NetworkStateUtil;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
@ -168,6 +170,7 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
private ControlBarShowHideHelper mControlBarShowHideHelper = new ControlBarShowHideHelper(this);
private int mTabColumns;
private View mActionBarContainer;
private SessionEvent mSessionEvent;
public void closeAccountsDrawer() {
if (mDrawerLayout == null) return;
@ -450,12 +453,14 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
final Bus bus = TwidereApplication.getInstance(this).getMessageBus();
assert bus != null;
bus.register(this);
// spice
// BEGIN HotMobi
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_APP, "App Launch" + "," + Build.MODEL
+ "," + "mediaPreview=" + mPreferences.getBoolean(KEY_MEDIA_PREVIEW, false));
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_ONLAUNCH, "App Launch"
+ "," + NetworkStateUtil.getConnectedType(this) + "," + Build.MODEL);
//end
SessionEvent event = SessionEvent.create(this);
mSessionEvent = event;
// END HotMobi
mReadStateManager.registerOnSharedPreferenceChangeListener(mReadStateChangeListener);
updateUnreadCount();
}
@ -488,11 +493,15 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
mPreferences.edit().putInt(KEY_SAVED_TAB_POSITION, mViewPager.getCurrentItem()).apply();
sendBroadcast(new Intent(BROADCAST_HOME_ACTIVITY_ONSTOP));
// spice
// BEGIN HotMobi
final SessionEvent event = mSessionEvent;
event.markEnd();
HotMobiLogger.getInstance(this).log(event);
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_APP, "App Stop");
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_ONLAUNCH, "App Stop" + ","
+ NetworkStateUtil.getConnectedType(this) + "," + Build.MODEL);
//end
// END HotMobi
super.onStop();
}

View File

@ -52,6 +52,8 @@ import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereColorUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.view.HeaderDrawerLayout.DrawerCallback;
import org.mariotaku.twidere.view.iface.IExtendedView;
import org.mariotaku.twidere.view.themed.AccentSwipeRefreshLayout;
/**
* Comment, blah, blah, blah.
@ -179,10 +181,10 @@ public abstract class AbsContentRecyclerViewFragment<A extends LoadMoreSupportAd
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof IControlBarActivity) {
((IControlBarActivity) activity).registerControlBarOffsetListener(this);
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof IControlBarActivity) {
((IControlBarActivity) context).registerControlBarOffsetListener(this);
}
}
@ -210,15 +212,28 @@ public abstract class AbsContentRecyclerViewFragment<A extends LoadMoreSupportAd
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
updateRefreshProgressOffset();
if (mSwipeRefreshLayout instanceof AccentSwipeRefreshLayout) {
((AccentSwipeRefreshLayout) mSwipeRefreshLayout).setTouchInterceptor(new IExtendedView.TouchInterceptor() {
@Override
public boolean dispatchTouchEvent(View view, MotionEvent event) {
return false;
}
return false;
}
});
@Override
public boolean onInterceptTouchEvent(View view, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
updateRefreshProgressOffset();
}
return false;
}
@Override
public boolean onTouchEvent(View view, MotionEvent event) {
return false;
}
});
}
if (compact) {
mItemDecoration = new DividerItemDecoration(context, mLayoutManager.getOrientation());
mRecyclerView.addItemDecoration(mItemDecoration);
@ -326,17 +341,20 @@ public abstract class AbsContentRecyclerViewFragment<A extends LoadMoreSupportAd
protected void updateRefreshProgressOffset() {
final FragmentActivity activity = getActivity();
if (!(activity instanceof IControlBarActivity) || mSystemWindowsInsets.top == 0 || mSwipeRefreshLayout == null
final Rect insets = this.mSystemWindowsInsets;
final SwipeRefreshLayout layout = this.mSwipeRefreshLayout;
if (!(activity instanceof IControlBarActivity) || insets.top == 0 || layout == null
|| isRefreshing()) {
return;
}
final int progressCircleDiameter = layout.getProgressCircleDiameter();
if (progressCircleDiameter == 0) return;
final float density = getResources().getDisplayMetrics().density;
final int progressCircleDiameter = mSwipeRefreshLayout.getProgressCircleDiameter();
final IControlBarActivity control = (IControlBarActivity) activity;
final int controlBarOffsetPixels = Math.round(control.getControlBarHeight() * (1 - control.getControlBarOffset()));
final int swipeStart = (mSystemWindowsInsets.top - controlBarOffsetPixels) - progressCircleDiameter;
final int swipeStart = (insets.top - controlBarOffsetPixels) - progressCircleDiameter;
// 64: SwipeRefreshLayout.DEFAULT_CIRCLE_TARGET
final int swipeDistance = Math.round(64 * density);
mSwipeRefreshLayout.setProgressViewOffset(false, swipeStart, swipeStart + swipeDistance);
layout.setProgressViewOffset(false, swipeStart, swipeStart + swipeDistance);
}
}

View File

@ -44,6 +44,8 @@ import org.mariotaku.twidere.view.holder.StatusViewHolder;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.MediaEvent;
import edu.tsinghua.hotmobi.model.ScrollRecord;
import edu.tsinghua.hotmobi.model.TimelineType;
import static org.mariotaku.twidere.util.Utils.setMenuForStatus;
@ -85,6 +87,38 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
}
};
private OnScrollListener mHotMobiScrollTracker = new OnScrollListener() {
private long mFirstVisibleId = -1;
private int mFirstVisiblePosition = -1;
private int mScrollState;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
final int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
if (firstVisiblePosition != mFirstVisiblePosition) {
//noinspection unchecked
final AbsStatusesAdapter<Data> adapter = (AbsStatusesAdapter<Data>) recyclerView.getAdapter();
final ParcelableStatus status = adapter.getStatus(firstVisiblePosition);
if (status != null) {
final long id = status.id, accountId = status.account_id;
if (id != mFirstVisibleId) {
final ScrollRecord record = ScrollRecord.create(id, System.currentTimeMillis(), mScrollState);
HotMobiLogger.getInstance(getActivity()).log(accountId, record);
}
mFirstVisibleId = id;
}
}
mFirstVisiblePosition = firstVisiblePosition;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
mScrollState = newState;
}
};
protected AbsStatusesFragment() {
mStatusesBusCallback = createMessageBusCallback();
}
@ -259,8 +293,9 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
final Bundle options = Utils.createMediaViewerActivityOption(view);
Utils.openMedia(getActivity(), status, media, options);
// BEGIN HotMobi
final MediaEvent event = MediaEvent.create(getActivity(), status, media, 0, adapter.isMediaPreviewEnabled());
HotMobiLogger.getInstance(getActivity()).log(event);
final MediaEvent event = MediaEvent.create(getActivity(), status, media, TimelineType.OTHER,
adapter.isMediaPreviewEnabled());
HotMobiLogger.getInstance(getActivity()).log(status.account_id, event);
// END HotMobi
}
@ -345,6 +380,7 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
super.onStart();
final RecyclerView recyclerView = getRecyclerView();
recyclerView.addOnScrollListener(mOnScrollListener);
recyclerView.addOnScrollListener(mHotMobiScrollTracker);
final Bus bus = TwidereApplication.getInstance(getActivity()).getMessageBus();
assert bus != null;
bus.register(mStatusesBusCallback);
@ -356,6 +392,7 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
assert bus != null;
bus.unregister(mStatusesBusCallback);
final RecyclerView recyclerView = getRecyclerView();
recyclerView.removeOnScrollListener(mHotMobiScrollTracker);
recyclerView.removeOnScrollListener(mOnScrollListener);
super.onStop();
}

View File

@ -124,6 +124,7 @@ import java.util.Locale;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.MediaEvent;
import edu.tsinghua.hotmobi.model.TimelineType;
import edu.tsinghua.hotmobi.model.TweetEvent;
/**
@ -294,9 +295,9 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final Bundle options = Utils.createMediaViewerActivityOption(view);
Utils.openMedia(getActivity(), status, media, options);
MediaEvent event = MediaEvent.create(getActivity(), status, media, 0,
MediaEvent event = MediaEvent.create(getActivity(), status, media, TimelineType.OTHER,
mStatusAdapter.isMediaPreviewEnabled());
HotMobiLogger.getInstance(getActivity()).log(event);
HotMobiLogger.getInstance(getActivity()).log(status.account_id, event);
}
@Override
@ -374,9 +375,9 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final Bundle options = Utils.createMediaViewerActivityOption(view);
Utils.openMediaDirectly(getActivity(), accountId, status, media, status.media, options);
// BEGIN HotMobi
MediaEvent event = MediaEvent.create(getActivity(), status, media, 0,
MediaEvent event = MediaEvent.create(getActivity(), status, media, TimelineType.OTHER,
mStatusAdapter.isMediaPreviewEnabled());
HotMobiLogger.getInstance(getActivity()).log(event);
HotMobiLogger.getInstance(getActivity()).log(status.account_id, event);
// END HotMobi
}
@ -526,7 +527,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
mStatusAdapter.setReplies(null);
loadReplies(status);
loadConversation(status);
final TweetEvent event = TweetEvent.create(getActivity(), status, 0);
final TweetEvent event = TweetEvent.create(getActivity(), status, TimelineType.OTHER);
event.setAction(TweetEvent.Action.OPEN);
mStatusEvent = event;
} else {
@ -867,6 +868,9 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
status.user_screen_name, null);
break;
}
case R.id.quote_original_link: {
Utils.openStatus(adapter.getContext(), status.account_id, status.quote_id);
}
}
}
@ -902,6 +906,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
favoritesContainer.setOnClickListener(this);
retweetedByView.setOnClickListener(this);
locationView.setOnClickListener(this);
quoteOriginalLink.setOnClickListener(this);
final float defaultTextSize = adapter.getTextSize();
nameView.setTextSize(defaultTextSize * 1.25f);
@ -1574,7 +1579,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final TweetEvent event = mStatusEvent;
if (event == null) return;
event.markEnd();
HotMobiLogger.getInstance(getActivity()).log(event);
HotMobiLogger.getInstance(getActivity()).log(event.getAccountId(), event);
}

View File

@ -107,6 +107,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.RefreshEvent;
import edu.tsinghua.hotmobi.model.TimelineType;
import edu.tsinghua.hotmobi.model.TweetEvent;
public class AsyncTwitterWrapper extends TwitterWrapper {
@ -920,9 +921,9 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
// BEGIN HotMobi
final TweetEvent event = TweetEvent.create(getContext(), status, 0);
final TweetEvent event = TweetEvent.create(getContext(), status, TimelineType.OTHER);
event.setAction(TweetEvent.Action.FAVORITE);
HotMobiLogger.getInstance(getContext()).log(event);
HotMobiLogger.getInstance(getContext()).log(account_id, event);
// END HotMobi
@ -1556,9 +1557,9 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
final ParcelableStatus status = result.getData();
// BEGIN HotMobi
final TweetEvent event = TweetEvent.create(getContext(), status, 0);
final TweetEvent event = TweetEvent.create(getContext(), status, TimelineType.OTHER);
event.setAction(TweetEvent.Action.UNFAVORITE);
HotMobiLogger.getInstance(getContext()).log(event);
HotMobiLogger.getInstance(getContext()).log(account_id, event);
// END HotMobi
@ -1998,6 +1999,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return Statuses.CONTENT_URI;
}
@Override
protected TimelineType getTimelineType() {
return TimelineType.HOME;
}
@Override
protected void onPostExecute(final List<StatusListResponse> result) {
@ -2056,6 +2062,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return Mentions.CONTENT_URI;
}
@Override
protected TimelineType getTimelineType() {
return TimelineType.INTERACTIONS;
}
@Override
protected void onPostExecute(final List<StatusListResponse> result) {
super.onPostExecute(result);
@ -2202,8 +2213,8 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
countCur.close();
// BEGIN HotMobi
final RefreshEvent event = RefreshEvent.create(mContext, statusIds, 0);
HotMobiLogger.getInstance(mContext).log(event);
final RefreshEvent event = RefreshEvent.create(mContext, statusIds, getTimelineType());
HotMobiLogger.getInstance(mContext).log(accountId, event);
// END HotMobi
// Insert a gap.
@ -2220,6 +2231,8 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
protected abstract TimelineType getTimelineType();
@SafeVarargs
@Override
protected final void onProgressUpdate(TwitterListResponse<org.mariotaku.twidere.api.twitter.model.Status>... values) {
@ -2626,9 +2639,9 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
// BEGIN HotMobi
final TweetEvent event = TweetEvent.create(getContext(), status, 0);
final TweetEvent event = TweetEvent.create(getContext(), status, TimelineType.OTHER);
event.setAction(TweetEvent.Action.RETWEET);
HotMobiLogger.getInstance(getContext()).log(event);
HotMobiLogger.getInstance(getContext()).log(account_id, event);
// END HotMobi

View File

@ -22,7 +22,6 @@ package org.mariotaku.twidere.util;
import android.content.Context;
import android.util.SparseIntArray;
import org.apache.http.HttpStatus;
import org.mariotaku.twidere.R;
public class StatusCodeMessageUtils {
@ -53,7 +52,8 @@ public class StatusCodeMessageUtils {
TWITTER_ERROR_CODE_MESSAGES.put(STATUS_IS_DUPLICATE, R.string.error_twitter_187);
TWITTER_ERROR_CODE_MESSAGES.put(193, R.string.error_twitter_193);
TWITTER_ERROR_CODE_MESSAGES.put(215, R.string.error_twitter_215);
HTTP_STATUS_CODE_MESSAGES.put(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, R.string.error_http_407);
HTTP_STATUS_CODE_MESSAGES.put(407, R.string.error_http_407);
}
public static boolean containsHttpStatus(final int code) {

View File

@ -114,7 +114,6 @@ import android.widget.Toast;
import com.bluelinelabs.logansquare.LoganSquare;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -3696,8 +3695,8 @@ public final class Utils implements Constants {
try {
final String template = "http://translate.google.com/#%s|%s|%s";
final String sourceLang = "auto";
final String targetLang = URLEncoder.encode(locale.getLanguage(), HTTP.UTF_8);
final String text = URLEncoder.encode(status.text_unescaped, HTTP.UTF_8);
final String targetLang = URLEncoder.encode(locale.getLanguage(), "UTF-8");
final String text = URLEncoder.encode(status.text_unescaped, "UTF-8");
final Uri uri = Uri.parse(String.format(Locale.ROOT, template, sourceLang, targetLang, text));
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addCategory(Intent.CATEGORY_BROWSABLE);

View File

@ -0,0 +1,111 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.mariotaku.twidere.util.net;
import java.util.regex.Pattern;
/**
* A collection of utilities relating to InetAddresses.
*
* @since 4.0
*/
public class InetAddressUtils {
private InetAddressUtils() {
}
private static final String IPV4_BASIC_PATTERN_STRING =
"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + // initial 3 fields, 0-255 followed by .
"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; // final field, 0-255
private static final Pattern IPV4_PATTERN =
Pattern.compile("^" + IPV4_BASIC_PATTERN_STRING + "$");
private static final Pattern IPV4_MAPPED_IPV6_PATTERN = // TODO does not allow for redundant leading zeros
Pattern.compile("^::[fF]{4}:" + IPV4_BASIC_PATTERN_STRING + "$");
private static final Pattern IPV6_STD_PATTERN =
Pattern.compile(
"^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$");
private static final Pattern IPV6_HEX_COMPRESSED_PATTERN =
Pattern.compile(
"^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)" + // 0-6 hex fields
"::" +
"(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)$"); // 0-6 hex fields
/*
* The above pattern is not totally rigorous as it allows for more than 7 hex fields in total
*/
private static final char COLON_CHAR = ':';
// Must not have more than 7 colons (i.e. 8 fields)
private static final int MAX_COLON_COUNT = 7;
/**
* Checks whether the parameter is a valid IPv4 address
*
* @param input the address string to check for validity
* @return true if the input parameter is a valid IPv4 address
*/
public static boolean isIPv4Address(final String input) {
return IPV4_PATTERN.matcher(input).matches();
}
public static boolean isIPv4MappedIPv64Address(final String input) {
return IPV4_MAPPED_IPV6_PATTERN.matcher(input).matches();
}
/**
* Checks whether the parameter is a valid standard (non-compressed) IPv6 address
*
* @param input the address string to check for validity
* @return true if the input parameter is a valid standard (non-compressed) IPv6 address
*/
public static boolean isIPv6StdAddress(final String input) {
return IPV6_STD_PATTERN.matcher(input).matches();
}
/**
* Checks whether the parameter is a valid compressed IPv6 address
*
* @param input the address string to check for validity
* @return true if the input parameter is a valid compressed IPv6 address
*/
public static boolean isIPv6HexCompressedAddress(final String input) {
int colonCount = 0;
for (int i = 0; i < input.length(); i++) {
if (input.charAt(i) == COLON_CHAR) {
colonCount++;
}
}
return colonCount <= MAX_COLON_COUNT && IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
}
/**
* Checks whether the parameter is a valid IPv6 address (including compressed).
*
* @param input the address string to check for validity
* @return true if the input parameter is a valid standard or compressed IPv6 address
*/
public static boolean isIPv6Address(final String input) {
return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input);
}
}

View File

@ -27,7 +27,6 @@ import android.util.LruCache;
import com.squareup.okhttp.internal.Network;
import org.apache.http.conn.util.InetAddressUtils;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.HostsFileParser;

View File

@ -21,16 +21,24 @@ package org.mariotaku.twidere.view.themed;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import org.mariotaku.twidere.view.iface.IExtendedView;
import org.mariotaku.twidere.view.iface.IThemeAccentView;
/**
* Created by mariotaku on 15/4/25.
*/
public class AccentSwipeRefreshLayout extends SwipeRefreshLayout implements IThemeAccentView {
public class AccentSwipeRefreshLayout extends SwipeRefreshLayout implements IThemeAccentView, IExtendedView {
private TouchInterceptor mTouchInterceptor;
private OnSizeChangedListener mOnSizeChangedListener;
private OnFitSystemWindowsListener mOnFitSystemWindowsListener;
public AccentSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@ -43,4 +51,63 @@ public class AccentSwipeRefreshLayout extends SwipeRefreshLayout implements IThe
public void setAccentTintColor(@NonNull ColorStateList color) {
setColorSchemeColors(color.getDefaultColor());
}
@Override
public final boolean dispatchTouchEvent(@NonNull final MotionEvent event) {
if (mTouchInterceptor != null) {
final boolean ret = mTouchInterceptor.dispatchTouchEvent(this, event);
if (ret) return true;
}
return super.dispatchTouchEvent(event);
}
@Override
public final boolean onInterceptTouchEvent(final MotionEvent event) {
if (mTouchInterceptor != null) {
final boolean ret = mTouchInterceptor.onInterceptTouchEvent(this, event);
if (ret) return true;
}
return super.onInterceptTouchEvent(event);
}
@Override
public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener) {
mOnFitSystemWindowsListener = listener;
}
@Override
public final void setOnSizeChangedListener(final OnSizeChangedListener listener) {
mOnSizeChangedListener = listener;
}
@Override
public final void setTouchInterceptor(final TouchInterceptor listener) {
mTouchInterceptor = listener;
}
@Override
@SuppressWarnings("deprecation")
protected boolean fitSystemWindows(@NonNull Rect insets) {
if (mOnFitSystemWindowsListener != null) {
mOnFitSystemWindowsListener.onFitSystemWindows(insets);
}
return super.fitSystemWindows(insets);
}
@Override
public final boolean onTouchEvent(@NonNull final MotionEvent event) {
if (mTouchInterceptor != null) {
final boolean ret = mTouchInterceptor.onTouchEvent(this, event);
if (ret) return true;
}
return super.onTouchEvent(event);
}
@Override
protected final void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mOnSizeChangedListener != null) {
mOnSizeChangedListener.onSizeChanged(this, w, h, oldw, oldh);
}
}
}