From 79e80bfaadee3e63f9d411f4e2fd934685c24946 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Wed, 14 Jan 2015 16:47:51 +0800 Subject: [PATCH] redesigned compose dialog --- build.gradle | 4 +- .../mariotaku/twidere/TwidereConstants.java | 2 + .../twidere/model/ParcelableAccount.java | 64 +- .../twidere/model/ParcelableUser.java | 28 +- .../twidere/provider/TwidereDataStore.java | 14 +- .../java/org/mariotaku/querybuilder/Join.java | 71 ++ .../querybuilder/query/SQLSelectQuery.java | 23 +- .../java/twitter4j/PagableResponseList.java | 26 - .../java/twitter4j/PageableResponseList.java | 29 + .../src/main/java/twitter4j/TwitterImpl.java | 116 +- .../api/FriendsFollowersResources.java | 14 +- .../java/twitter4j/api/ListsResources.java | 32 +- .../java/twitter4j/api/UsersResources.java | 33 +- .../internal/json/InternalJSONFactory.java | 6 +- .../json/InternalJSONFactoryImpl.java | 6 +- ...mpl.java => PageableResponseListImpl.java} | 29 +- .../twitter4j/internal/json/UserJSONImpl.java | 6 +- .../internal/json/UserListJSONImpl.java | 6 +- twidere.extension.streaming/build.gradle | 2 +- .../extension/streaming/StreamingService.java | 38 +- twidere/build.gradle | 8 +- .../fragment/support/StatusesMapFragment.java | 49 + .../twidere/model/ClusterStatus.java | 44 + .../twidere/util/StatusClusterRenderer.java | 947 +++++++++++++++ .../gallery3d/ImageViewerGLActivityOld.java | 568 --------- .../mariotaku/gallery3d/PhotoViewAdapter.java | 241 ---- .../mariotaku/gallery3d/anim/Animation.java | 88 -- .../gallery3d/anim/CanvasAnimation.java | 26 - .../mariotaku/gallery3d/ui/AnimationTime.java | 44 - .../mariotaku/gallery3d/ui/BasicTexture.java | 204 ---- .../gallery3d/ui/BitmapScreenNail.java | 58 - .../mariotaku/gallery3d/ui/BitmapTexture.java | 54 - .../gallery3d/ui/DownUpDetector.java | 63 - .../mariotaku/gallery3d/ui/EdgeEffect.java | 451 -------- .../org/mariotaku/gallery3d/ui/EdgeView.java | 128 --- .../mariotaku/gallery3d/ui/FlingScroller.java | 138 --- .../org/mariotaku/gallery3d/ui/GLCanvas.java | 98 -- .../mariotaku/gallery3d/ui/GLCanvasImpl.java | 588 ---------- .../java/org/mariotaku/gallery3d/ui/GLId.java | 44 - .../org/mariotaku/gallery3d/ui/GLRoot.java | 53 - .../mariotaku/gallery3d/ui/GLRootView.java | 541 --------- .../org/mariotaku/gallery3d/ui/GLView.java | 307 ----- .../gallery3d/ui/GalleryEGLConfigChooser.java | 112 -- .../gallery3d/ui/GestureRecognizer.java | 134 --- .../org/mariotaku/gallery3d/ui/PhotoView.java | 592 ---------- .../gallery3d/ui/PositionController.java | 1019 ----------------- .../gallery3d/ui/ResourceTexture.java | 51 - .../mariotaku/gallery3d/ui/ScreenNail.java | 36 - .../gallery3d/ui/SynchronizedHandler.java | 44 - .../org/mariotaku/gallery3d/ui/Texture.java | 47 - .../mariotaku/gallery3d/ui/TileImageView.java | 759 ------------ .../gallery3d/ui/UploadedTexture.java | 312 ----- .../mariotaku/gallery3d/util/ApiHelper.java | 26 - .../mariotaku/gallery3d/util/BitmapPool.java | 74 -- .../mariotaku/gallery3d/util/DecodeUtils.java | 33 - .../org/mariotaku/gallery3d/util/Future.java | 38 - .../gallery3d/util/FutureListener.java | 23 - .../gallery3d/util/GalleryUtils.java | 125 -- .../mariotaku/gallery3d/util/IntArray.java | 49 - .../gallery3d/util/MotionEventHelper.java | 33 - .../gallery3d/util/PriorityThreadFactory.java | 49 - .../mariotaku/gallery3d/util/ThreadPool.java | 249 ---- .../java/org/mariotaku/twidere/Constants.java | 2 +- .../activity/support/ComposeActivity.java | 580 +++++----- .../activity/support/MediaViewerActivity.java | 56 +- .../support/QuickSearchBarActivity.java | 57 +- .../activity/support/SignInActivity.java | 12 +- .../twidere/adapter/AbsActivitiesAdapter.java | 6 +- .../twidere/adapter/AbsStatusesAdapter.java | 25 +- .../adapter/CursorStatusesAdapter.java | 2 +- .../adapter/ParcelableActivitiesAdapter.java | 5 +- .../adapter/ParcelableStatusesAdapter.java | 2 +- .../iface/ContentCardClickListener.java | 3 +- .../adapter/iface/IContentCardAdapter.java | 2 + .../twidere/app/TwidereApplication.java | 4 +- .../fragment/ExtensionsListFragment.java | 138 +-- .../fragment/support/AbsStatusesFragment.java | 31 +- .../support/BaseUserListsListFragment.java | 2 - .../support/RetweetQuoteDialogFragment.java | 2 +- .../fragment/support/StatusFragment.java | 29 +- .../StatusTranslateDialogFragment.java | 2 +- .../fragment/support/UserFragment.java | 7 +- .../fragment/support/UserListsFragment.java | 9 +- .../loader/support/BaseUserListsLoader.java | 4 +- .../support/CursorSupportUsersLoader.java | 6 +- .../loader/support/MutesUsersLoader.java | 4 +- .../loader/support/ParcelableUserLoader.java | 4 +- .../loader/support/TileImageLoader.java | 61 +- .../loader/support/UserBlocksLoader.java | 4 +- .../loader/support/UserFollowersLoader.java | 4 +- .../loader/support/UserFriendsLoader.java | 4 +- .../loader/support/UserListMembersLoader.java | 4 +- .../support/UserListMembershipsLoader.java | 4 +- .../support/UserListSubscribersLoader.java | 4 +- .../popup/AccountSelectorPopupWindow.java | 166 +++ .../preference/ThemeBackgroundPreference.java | 6 +- .../twidere/provider/TwidereDataProvider.java | 50 +- .../twidere/task/CacheUsersStatusesTask.java | 83 +- .../twidere/text/TwidereURLSpan.java | 3 +- .../twidere/util/StatisticUtils.java | 49 + .../mariotaku/twidere/util/ThemeUtils.java | 9 +- .../twidere/util/ThemedViewFactory.java | 47 + .../twidere/util/TwidereQueryBuilder.java | 103 +- .../org/mariotaku/twidere/util/Utils.java | 60 +- ...iew.java => ActionIconThemedTextView.java} | 10 +- .../view/ComposeSelectAccountButton.java | 157 ++- .../twidere/view/TwidereMenuBar.java | 4 + .../twidere/view/holder/StatusViewHolder.java | 53 +- .../twidere/view/themed/ThemedSwitch.java | 13 +- .../twidere/view/themed/ThemedTextView.java | 6 +- .../ic_activity_action_reply.png | Bin 0 -> 617 bytes .../ic_activity_action_reply.png | Bin 0 -> 434 bytes .../ic_activity_action_reply.png | Bin 0 -> 832 bytes .../ic_activity_action_reply.png | Bin 0 -> 1249 bytes .../res/layout/action_item_compose_send.xml | 5 +- .../src/main/res/layout/activity_compose.xml | 104 +- .../res/layout/activity_compose_actionbar.xml | 2 +- .../res/layout/activity_compose_bottombar.xml | 18 +- .../src/main/res/layout/activity_sign_in.xml | 19 +- .../layout/activity_user_profile_editor.xml | 8 +- .../layout/adapter_item_compose_account.xml | 23 +- .../res/layout/card_item_status_common.xml | 52 +- .../res/layout/card_item_status_compact.xml | 44 +- .../res/layout/grid_item_selector_account.xml | 37 + .../layout/header_drawer_account_selector.xml | 6 +- twidere/src/main/res/layout/header_status.xml | 5 +- .../main/res/layout/header_status_compact.xml | 4 +- twidere/src/main/res/layout/image_viewer.xml | 2 +- .../src/main/res/layout/image_viewer_gl.xml | 45 - .../main/res/layout/include_cancel_button.xml | 2 +- .../main/res/layout/include_done_button.xml | 2 +- .../main/res/layout/layout_media_preview.xml | 2 +- .../res/layout/popup_account_selector.xml | 30 + twidere/src/main/res/menu/menu_compose.xml | 1 + twidere/src/main/res/values/dimens.xml | 3 +- twidere/src/main/res/values/strings.xml | 5 +- 136 files changed, 2803 insertions(+), 8610 deletions(-) create mode 100644 twidere.component.querybuilder/src/main/java/org/mariotaku/querybuilder/Join.java delete mode 100644 twidere.component.twitter4j/src/main/java/twitter4j/PagableResponseList.java create mode 100644 twidere.component.twitter4j/src/main/java/twitter4j/PageableResponseList.java rename twidere.component.twitter4j/src/main/java/twitter4j/internal/json/{PagableResponseListImpl.java => PageableResponseListImpl.java} (50%) create mode 100644 twidere/src/google/java/org/mariotaku/twidere/fragment/support/StatusesMapFragment.java create mode 100644 twidere/src/google/java/org/mariotaku/twidere/model/ClusterStatus.java create mode 100644 twidere/src/google/java/org/mariotaku/twidere/util/StatusClusterRenderer.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivityOld.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/PhotoViewAdapter.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/anim/Animation.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/anim/CanvasAnimation.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/AnimationTime.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapScreenNail.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapTexture.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/DownUpDetector.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeEffect.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeView.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/FlingScroller.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvas.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvasImpl.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLId.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRoot.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRootView.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLView.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GalleryEGLConfigChooser.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GestureRecognizer.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/PositionController.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/ResourceTexture.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/ScreenNail.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/SynchronizedHandler.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/Texture.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/UploadedTexture.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/ApiHelper.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapPool.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/DecodeUtils.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/Future.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/FutureListener.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/IntArray.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/MotionEventHelper.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/PriorityThreadFactory.java delete mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/ThreadPool.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/popup/AccountSelectorPopupWindow.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/StatisticUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ThemedViewFactory.java rename twidere/src/main/java/org/mariotaku/twidere/view/{ActionIconTextView.java => ActionIconThemedTextView.java} (91%) create mode 100755 twidere/src/main/res/drawable-hdpi/ic_activity_action_reply.png create mode 100755 twidere/src/main/res/drawable-mdpi/ic_activity_action_reply.png create mode 100755 twidere/src/main/res/drawable-xhdpi/ic_activity_action_reply.png create mode 100755 twidere/src/main/res/drawable-xxhdpi/ic_activity_action_reply.png create mode 100644 twidere/src/main/res/layout/grid_item_selector_account.xml delete mode 100644 twidere/src/main/res/layout/image_viewer_gl.xml create mode 100644 twidere/src/main/res/layout/popup_account_selector.xml diff --git a/build.gradle b/build.gradle index 6ddf05a02..2fa7532f0 100644 --- a/build.gradle +++ b/build.gradle @@ -7,8 +7,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.github.ben-manes:gradle-versions-plugin:0.6' - classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.7' + classpath 'com.android.tools.build:gradle:1.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java index 68d98d94a..65179b92f 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/TwidereConstants.java @@ -177,6 +177,8 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst public static final int VIRTUAL_TABLE_ID_CACHE_FILES = 107; public static final int VIRTUAL_TABLE_ID_UNREAD_COUNTS = 108; public static final int VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE = 109; + public static final int VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP = 121; + public static final int VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE = 122; public static final int NOTIFICATION_ID_HOME_TIMELINE = 1; public static final int NOTIFICATION_ID_MENTIONS_TIMELINE = 2; diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java index 99ecab8a7..19e0bc2bf 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableAccount.java @@ -195,10 +195,10 @@ public class ParcelableAccount implements Parcelable { return accounts; } - public static ParcelableCredentials getCredentials(final Context context, final long account_id) { + public static ParcelableCredentials getCredentials(final Context context, final long accountId) { if (context == null) return null; final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, - Accounts.COLUMNS, Accounts.ACCOUNT_ID + " = " + account_id, null, null); + Accounts.COLUMNS, Accounts.ACCOUNT_ID + " = " + accountId, null, null); if (cur != null) { try { if (cur.getCount() > 0 && cur.moveToFirst()) { @@ -213,6 +213,36 @@ public class ParcelableAccount implements Parcelable { return null; } + public static List getCredentialsList(final Context context, final boolean activatedOnly) { + return getCredentialsList(context, activatedOnly, false); + } + + public static List getCredentialsList(final Context context, final boolean activatedOnly, + final boolean officialKeyOnly) { + if (context == null) return Collections.emptyList(); + final ArrayList accounts = new ArrayList<>(); + final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), + Accounts.CONTENT_URI, Accounts.COLUMNS, + activatedOnly ? Accounts.IS_ACTIVATED + " = 1" : null, null, Accounts.SORT_POSITION); + if (cur == null) return accounts; + final Indices indices = new Indices(cur); + cur.moveToFirst(); + while (!cur.isAfterLast()) { + if (officialKeyOnly) { + final String consumerKey = cur.getString(indices.consumer_key); + final String consumerSecret = cur.getString(indices.consumer_secret); + if (TwitterContentUtils.isOfficialKey(context, consumerKey, consumerSecret)) { + accounts.add(new ParcelableCredentials(cur, indices)); + } + } else { + accounts.add(new ParcelableCredentials(cur, indices)); + } + cur.moveToNext(); + } + cur.close(); + return accounts; + } + @Override public String toString() { return "Account{screen_name=" + screen_name + ", name=" + name + ", profile_image_url=" + profile_image_url @@ -271,36 +301,6 @@ public class ParcelableAccount implements Parcelable { } - public static List getCredentialsList(final Context context, final boolean activatedOnly) { - return getCredentialsList(context, activatedOnly, false); - } - - public static List getCredentialsList(final Context context, final boolean activatedOnly, - final boolean officialKeyOnly) { - if (context == null) return Collections.emptyList(); - final ArrayList accounts = new ArrayList<>(); - final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), - Accounts.CONTENT_URI, Accounts.COLUMNS, - activatedOnly ? Accounts.IS_ACTIVATED + " = 1" : null, null, Accounts.SORT_POSITION); - if (cur == null) return accounts; - final Indices indices = new Indices(cur); - cur.moveToFirst(); - while (!cur.isAfterLast()) { - if (!officialKeyOnly) { - accounts.add(new ParcelableCredentials(cur, indices)); - } else { - final String consumerKey = cur.getString(indices.consumer_key); - final String consumerSecret = cur.getString(indices.consumer_secret); - if (TwitterContentUtils.isOfficialKey(context, consumerKey, consumerSecret)) { - accounts.add(new ParcelableCredentials(cur, indices)); - } - } - cur.moveToNext(); - } - cur.close(); - return accounts; - } - @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableUser.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableUser.java index 59c42323c..f7d7018e1 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableUser.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableUser.java @@ -31,8 +31,8 @@ import org.mariotaku.jsonserializer.JSONParcelable; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries; import org.mariotaku.twidere.util.HtmlEscapeHelper; -import org.mariotaku.twidere.util.TwitterContentUtils; import org.mariotaku.twidere.util.ParseUtils; +import org.mariotaku.twidere.util.TwitterContentUtils; import twitter4j.URLEntity; import twitter4j.User; @@ -74,7 +74,7 @@ public class ParcelableUser implements TwidereParcelable, Comparable + * + * 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 . + */ + +package org.mariotaku.querybuilder; + +/** + * Created by mariotaku on 15/1/12. + */ +public class Join implements SQLLang { + + private final boolean natural; + private final Operation operation; + private final Selectable source; + private final Expression on; + + public Join(boolean natural, Operation operation, Selectable source, Expression on) { + this.natural = natural; + this.operation = operation; + this.source = source; + this.on = on; + } + + @Override + public String getSQL() { + if (operation == null) throw new IllegalArgumentException("operation can't be null!"); + if (source == null) throw new IllegalArgumentException("source can't be null!"); + final StringBuilder builder = new StringBuilder(); + if (natural) { + builder.append("NATURAL "); + } + builder.append(operation.getSQL()); + builder.append(" JOIN "); + builder.append(source.getSQL()); + if (on != null) { + builder.append(" ON "); + builder.append(on.getSQL()); + } + return builder.toString(); + } + + public enum Operation implements SQLLang { + LEFT("LEFT"), LEFT_OUTER("LEFT OUTER"), INNER("INNER"), CROSS("CROSS"); + private final String op; + + Operation(String op) { + this.op = op; + } + + @Override + public String getSQL() { + return op; + } + + } +} diff --git a/twidere.component.querybuilder/src/main/java/org/mariotaku/querybuilder/query/SQLSelectQuery.java b/twidere.component.querybuilder/src/main/java/org/mariotaku/querybuilder/query/SQLSelectQuery.java index 438355b77..ae4741ea0 100644 --- a/twidere.component.querybuilder/src/main/java/org/mariotaku/querybuilder/query/SQLSelectQuery.java +++ b/twidere.component.querybuilder/src/main/java/org/mariotaku/querybuilder/query/SQLSelectQuery.java @@ -28,6 +28,7 @@ package org.mariotaku.querybuilder.query; import org.mariotaku.querybuilder.Expression; +import org.mariotaku.querybuilder.Join; import org.mariotaku.querybuilder.OrderBy; import org.mariotaku.querybuilder.SQLLang; import org.mariotaku.querybuilder.SQLQuery; @@ -39,7 +40,7 @@ import java.util.List; public class SQLSelectQuery implements SQLQuery, Selectable { - private final List internalQueries = new ArrayList(); + private final List internalQueries = new ArrayList<>(); private InternalQuery currentInternalQuery; private OrderBy orderBy; @@ -59,7 +60,6 @@ public class SQLSelectQuery implements SQLQuery, Selectable { } final InternalQuery query = internalQueries.get(i); sb.append(query.getSQL()); - } if (orderBy != null) { sb.append(String.format("ORDER BY %s ", orderBy.getSQL())); @@ -94,6 +94,10 @@ public class SQLSelectQuery implements SQLQuery, Selectable { currentInternalQuery.setHaving(having); } + void setJoin(final Join join) { + currentInternalQuery.setJoin(join); + } + void setLimit(final int limit) { this.limit = limit; } @@ -151,12 +155,19 @@ public class SQLSelectQuery implements SQLQuery, Selectable { return this; } + public Builder limit(final int limit) { checkNotBuilt(); query.setLimit(limit); return this; } + public Builder join(final Join join) { + checkNotBuilt(); + query.setJoin(join); + return this; + } + public Builder offset(final int offset) { query.setOffset(offset); return this; @@ -204,6 +215,7 @@ public class SQLSelectQuery implements SQLQuery, Selectable { private boolean distinct; private Selectable select, from, groupBy; private Expression where, having; + private Join join; @Override public String getSQL() { @@ -222,6 +234,9 @@ public class SQLSelectQuery implements SQLQuery, Selectable { sb.append(String.format("FROM %s ", from.getSQL())); } } + if (join != null) { + sb.append(String.format("%s ", join.getSQL())); + } if (where != null) { sb.append(String.format("WHERE %s ", where.getSQL())); } @@ -234,6 +249,10 @@ public class SQLSelectQuery implements SQLQuery, Selectable { return sb.toString(); } + void setJoin(final Join join) { + this.join = join; + } + void setDistinct(final boolean distinct) { this.distinct = distinct; } diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/PagableResponseList.java b/twidere.component.twitter4j/src/main/java/twitter4j/PagableResponseList.java deleted file mode 100644 index c5cef97c8..000000000 --- a/twidere.component.twitter4j/src/main/java/twitter4j/PagableResponseList.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2007 Yusuke Yamamoto - * - * Licensed 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. - */ - -package twitter4j; - -/** - * ResponseList with cursor support. - * - * @author Yusuke Yamamoto - yusuke at mac.com - */ -public interface PagableResponseList extends ResponseList, CursorSupport { - -} diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/PageableResponseList.java b/twidere.component.twitter4j/src/main/java/twitter4j/PageableResponseList.java new file mode 100644 index 000000000..8898a0c5c --- /dev/null +++ b/twidere.component.twitter4j/src/main/java/twitter4j/PageableResponseList.java @@ -0,0 +1,29 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package twitter4j; + +/** + * ResponseList with cursor support. + * + * @author Yusuke Yamamoto - yusuke at mac.com + */ +public interface PageableResponseList extends ResponseList, CursorSupport { + +} diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/TwitterImpl.java b/twidere.component.twitter4j/src/main/java/twitter4j/TwitterImpl.java index 0e1d0f419..e6b712a97 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/TwitterImpl.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/TwitterImpl.java @@ -1,18 +1,20 @@ /* - * Copyright (C) 2007 Yusuke Yamamoto - * Copyright (C) 2011 Twitter, Inc. + * Twidere - Twitter client for Android * - * Licensed 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 + * Copyright (C) 2012-2015 Mariotaku Lee * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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 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 . */ package twitter4j; @@ -461,7 +463,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getBlocksList() throws TwitterException { + public PageableResponseList getBlocksList() throws TwitterException { ensureAuthorizationEnabled(); final String url = conf.getRestBaseURL() + ENDPOINT_BLOCKS_LIST; final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_BLOCKS_LIST; @@ -469,7 +471,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getBlocksList(final CursorPaging paging) throws TwitterException { + public PageableResponseList getBlocksList(final CursorPaging paging) throws TwitterException { ensureAuthorizationEnabled(); final String url = conf.getRestBaseURL() + ENDPOINT_BLOCKS_LIST; final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_BLOCKS_LIST; @@ -576,13 +578,13 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getFollowersList(final CursorPaging paging) throws TwitterException { + public PageableResponseList getFollowersList(final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get(conf.getRestBaseURL() + ENDPOINT_FOLLOWERS_LIST, conf.getSigningRestBaseURL() + ENDPOINT_FOLLOWERS_LIST, paging.asPostParameterArray())); } @Override - public PagableResponseList getFollowersList(final long userId, final CursorPaging paging) + public PageableResponseList getFollowersList(final long userId, final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get(conf.getRestBaseURL() + ENDPOINT_FOLLOWERS_LIST, conf.getSigningRestBaseURL() + ENDPOINT_FOLLOWERS_LIST, @@ -590,7 +592,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getFollowersList(final String screenName, final CursorPaging paging) + public PageableResponseList getFollowersList(final String screenName, final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get(conf.getRestBaseURL() + ENDPOINT_FOLLOWERS_LIST, conf.getSigningRestBaseURL() + ENDPOINT_FOLLOWERS_LIST, @@ -618,13 +620,13 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getFriendsList(final CursorPaging paging) throws TwitterException { + public PageableResponseList getFriendsList(final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get(conf.getRestBaseURL() + ENDPOINT_FRIENDS_LIST, conf.getSigningRestBaseURL() + ENDPOINT_FRIENDS_LIST, paging.asPostParameterArray())); } @Override - public PagableResponseList getFriendsList(final long userId, final CursorPaging paging) + public PageableResponseList getFriendsList(final long userId, final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get(conf.getRestBaseURL() + ENDPOINT_FRIENDS_LIST, conf.getSigningRestBaseURL() + ENDPOINT_FRIENDS_LIST, @@ -632,7 +634,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getFriendsList(final String screenName, final CursorPaging paging) + public PageableResponseList getFriendsList(final String screenName, final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get(conf.getRestBaseURL() + ENDPOINT_FRIENDS_LIST, conf.getSigningRestBaseURL() + ENDPOINT_FRIENDS_LIST, @@ -799,7 +801,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getMutesUsersList() throws TwitterException { + public PageableResponseList getMutesUsersList() throws TwitterException { ensureAuthorizationEnabled(); final String url = conf.getRestBaseURL() + ENDPOINT_MUTES_USERS_LIST; final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_MUTES_USERS_LIST; @@ -807,7 +809,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getMutesUsersList(final CursorPaging paging) throws TwitterException { + public PageableResponseList getMutesUsersList(final CursorPaging paging) throws TwitterException { ensureAuthorizationEnabled(); final String url = conf.getRestBaseURL() + ENDPOINT_MUTES_USERS_LIST; final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_MUTES_USERS_LIST; @@ -975,38 +977,36 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListMembers(final long listId, final CursorPaging paging) + public PageableResponseList getUserListMembers(final long listId, final CursorPaging paging) throws TwitterException { - return factory - .createPagableUserList(get( - conf.getRestBaseURL() + ENDPOINT_LISTS_MEMBERS, - conf.getSigningRestBaseURL() + ENDPOINT_LISTS_MEMBERS, - mergeParameters(paging.asPostParameterArray(), new HttpParameter("list_id", listId), - INCLUDE_ENTITIES))); + final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_MEMBERS; + final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_LISTS_MEMBERS; + return factory.createPagableUserList(get(url, signUrl, mergeParameters(paging.asPostParameterArray(), + new HttpParameter("list_id", listId), INCLUDE_ENTITIES))); } @Override - public PagableResponseList getUserListMembers(final String slug, final long ownerId, final CursorPaging paging) - throws TwitterException { - return factory.createPagableUserList(get( - conf.getRestBaseURL() + ENDPOINT_LISTS_MEMBERS, - conf.getSigningRestBaseURL() + ENDPOINT_LISTS_MEMBERS, - mergeParameters(paging.asPostParameterArray(), new HttpParameter("slug", slug), new HttpParameter( - "owner_id", ownerId), INCLUDE_ENTITIES))); + public PageableResponseList getUserListMembers(final String slug, final long ownerId, + final CursorPaging paging) throws TwitterException { + final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_MEMBERS; + final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_LISTS_MEMBERS; + return factory.createPagableUserList(get(url, signUrl, + mergeParameters(paging.asPostParameterArray(), new HttpParameter("slug", slug), + new HttpParameter("owner_id", ownerId), INCLUDE_ENTITIES))); } @Override - public PagableResponseList getUserListMembers(final String slug, final String ownerScreenName, - final CursorPaging paging) throws TwitterException { - return factory.createPagableUserList(get( - conf.getRestBaseURL() + ENDPOINT_LISTS_MEMBERS, - conf.getSigningRestBaseURL() + ENDPOINT_LISTS_MEMBERS, - mergeParameters(paging.asPostParameterArray(), new HttpParameter("slug", slug), new HttpParameter( - "owner_screen_name", ownerScreenName), INCLUDE_ENTITIES))); + public PageableResponseList getUserListMembers(final String slug, final String ownerScreenName, + final CursorPaging paging) throws TwitterException { + final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_MEMBERS; + final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_LISTS_MEMBERS; + return factory.createPagableUserList(get(url, signUrl, + mergeParameters(paging.asPostParameterArray(), new HttpParameter("slug", slug), + new HttpParameter("owner_screen_name", ownerScreenName), INCLUDE_ENTITIES))); } @Override - public PagableResponseList getUserListMemberships(final long cursor) throws TwitterException { + public PageableResponseList getUserListMemberships(final long cursor) throws TwitterException { ensureAuthorizationEnabled(); return factory.createPagableUserListList(get(conf.getRestBaseURL() + ENDPOINT_LISTS_MEMBERSHIPS, conf.getSigningRestBaseURL() + ENDPOINT_LISTS_MEMBERSHIPS, new HttpParameter("cursor", cursor))); @@ -1014,14 +1014,14 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListMemberships(final long listMemberId, final long cursor) + public PageableResponseList getUserListMemberships(final long listMemberId, final long cursor) throws TwitterException { return getUserListMemberships(listMemberId, cursor, false); } @Override - public PagableResponseList getUserListMemberships(final long listMemberId, final long cursor, - final boolean filterToOwnedLists) throws TwitterException { + public PageableResponseList getUserListMemberships(final long listMemberId, final long cursor, + final boolean filterToOwnedLists) throws TwitterException { if (filterToOwnedLists) { ensureAuthorizationEnabled(); } @@ -1031,14 +1031,14 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListMemberships(final String listMemberScreenName, final long cursor) + public PageableResponseList getUserListMemberships(final String listMemberScreenName, final long cursor) throws TwitterException { return getUserListMemberships(listMemberScreenName, cursor, false); } @Override - public PagableResponseList getUserListMemberships(final String listMemberScreenName, final long cursor, - final boolean filterToOwnedLists) throws TwitterException { + public PageableResponseList getUserListMemberships(final String listMemberScreenName, final long cursor, + final boolean filterToOwnedLists) throws TwitterException { if (filterToOwnedLists) { ensureAuthorizationEnabled(); } @@ -1049,7 +1049,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListOwnerships(final long cursor) throws TwitterException { + public PageableResponseList getUserListOwnerships(final long cursor) throws TwitterException { ensureAuthorizationEnabled(); final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_OWNERSHIPS; final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_LISTS_OWNERSHIPS; @@ -1058,7 +1058,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListOwnerships(final long listMemberId, final long cursor) + public PageableResponseList getUserListOwnerships(final long listMemberId, final long cursor) throws TwitterException { ensureAuthorizationEnabled(); final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_OWNERSHIPS; @@ -1068,7 +1068,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListOwnerships(final String listMemberScreenName, final long cursor) + public PageableResponseList getUserListOwnerships(final String listMemberScreenName, final long cursor) throws TwitterException { ensureAuthorizationEnabled(); final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_OWNERSHIPS; @@ -1124,7 +1124,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListSubscribers(final long listId, final CursorPaging paging) + public PageableResponseList getUserListSubscribers(final long listId, final CursorPaging paging) throws TwitterException { return factory .createPagableUserList(get( @@ -1135,8 +1135,8 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListSubscribers(final String slug, final long ownerId, - final CursorPaging paging) throws TwitterException { + public PageableResponseList getUserListSubscribers(final String slug, final long ownerId, + final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get( conf.getRestBaseURL() + ENDPOINT_LISTS_SUBSCRIBERS, conf.getSigningRestBaseURL() + ENDPOINT_LISTS_SUBSCRIBERS, @@ -1145,8 +1145,8 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListSubscribers(final String slug, final String ownerScreenName, - final CursorPaging paging) throws TwitterException { + public PageableResponseList getUserListSubscribers(final String slug, final String ownerScreenName, + final CursorPaging paging) throws TwitterException { return factory.createPagableUserList(get( conf.getRestBaseURL() + ENDPOINT_LISTS_SUBSCRIBERS, conf.getSigningRestBaseURL() + ENDPOINT_LISTS_SUBSCRIBERS, @@ -1155,7 +1155,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public PagableResponseList getUserListSubscriptions(final String listOwnerScreenName, final long cursor) + public PageableResponseList getUserListSubscriptions(final String listOwnerScreenName, final long cursor) throws TwitterException { return factory.createPagableUserListList(get(conf.getRestBaseURL() + ENDPOINT_LISTS_SUBSCRIPTIONS, conf.getSigningRestBaseURL() + ENDPOINT_LISTS_SUBSCRIPTIONS, new HttpParameter("screen_name", diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/api/FriendsFollowersResources.java b/twidere.component.twitter4j/src/main/java/twitter4j/api/FriendsFollowersResources.java index da8591d6b..67aa22b95 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/api/FriendsFollowersResources.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/api/FriendsFollowersResources.java @@ -22,7 +22,7 @@ package twitter4j.api; import twitter4j.CursorPaging; import twitter4j.Friendship; import twitter4j.IDs; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Relationship; import twitter4j.ResponseList; import twitter4j.TwitterException; @@ -153,11 +153,11 @@ public interface FriendsFollowersResources { IDs getFollowersIDs(String screenName, CursorPaging paging) throws TwitterException; - PagableResponseList getFollowersList(CursorPaging paging) throws TwitterException; + PageableResponseList getFollowersList(CursorPaging paging) throws TwitterException; - PagableResponseList getFollowersList(long userId, CursorPaging paging) throws TwitterException; + PageableResponseList getFollowersList(long userId, CursorPaging paging) throws TwitterException; - PagableResponseList getFollowersList(String screenName, CursorPaging paging) throws TwitterException; + PageableResponseList getFollowersList(String screenName, CursorPaging paging) throws TwitterException; IDs getFriendsIDs(CursorPaging paging) throws TwitterException; @@ -165,11 +165,11 @@ public interface FriendsFollowersResources { IDs getFriendsIDs(String screenName, CursorPaging paging) throws TwitterException; - PagableResponseList getFriendsList(CursorPaging paging) throws TwitterException; + PageableResponseList getFriendsList(CursorPaging paging) throws TwitterException; - PagableResponseList getFriendsList(long userId, CursorPaging paging) throws TwitterException; + PageableResponseList getFriendsList(long userId, CursorPaging paging) throws TwitterException; - PagableResponseList getFriendsList(String screenName, CursorPaging paging) throws TwitterException; + PageableResponseList getFriendsList(String screenName, CursorPaging paging) throws TwitterException; /** * Returns an array of numeric IDs for every user who has a pending request diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/api/ListsResources.java b/twidere.component.twitter4j/src/main/java/twitter4j/api/ListsResources.java index ff2dbed68..9b91d310e 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/api/ListsResources.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/api/ListsResources.java @@ -20,7 +20,7 @@ package twitter4j.api; import twitter4j.CursorPaging; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Paging; import twitter4j.ResponseList; import twitter4j.Status; @@ -188,12 +188,12 @@ public interface ListsResources { * lists/members | Twitter Developers * @since Twitter4J 2.2.3 */ - PagableResponseList getUserListMembers(long listId, CursorPaging paging) throws TwitterException; + PageableResponseList getUserListMembers(long listId, CursorPaging paging) throws TwitterException; - PagableResponseList getUserListMembers(String slug, long ownerId, CursorPaging paging) + PageableResponseList getUserListMembers(String slug, long ownerId, CursorPaging paging) throws TwitterException; - PagableResponseList getUserListMembers(String slug, String ownerScreenName, CursorPaging paging) + PageableResponseList getUserListMembers(String slug, String ownerScreenName, CursorPaging paging) throws TwitterException; /** @@ -212,7 +212,7 @@ public interface ListsResources { * lists/memberships | Twitter Developers * @since Twitter4J 2.2.4 */ - PagableResponseList getUserListMemberships(long cursor) throws TwitterException; + PageableResponseList getUserListMemberships(long cursor) throws TwitterException; /** * List the lists the specified user has been added to.
@@ -230,7 +230,7 @@ public interface ListsResources { * lists/memberships | Twitter Developers * @since Twitter4J 2.2.4 */ - PagableResponseList getUserListMemberships(long listMemberId, long cursor) throws TwitterException; + PageableResponseList getUserListMemberships(long listMemberId, long cursor) throws TwitterException; /** * List the lists the specified user has been added to.
@@ -253,7 +253,7 @@ public interface ListsResources { * lists/memberships | Twitter Developers * @since Twitter4J 2.2.4 */ - PagableResponseList getUserListMemberships(long listMemberId, long cursor, boolean filterToOwnedLists) + PageableResponseList getUserListMemberships(long listMemberId, long cursor, boolean filterToOwnedLists) throws TwitterException; /** @@ -272,7 +272,7 @@ public interface ListsResources { * lists/memberships | Twitter Developers * @since Twitter4J 2.1.0 */ - PagableResponseList getUserListMemberships(String listMemberScreenName, long cursor) + PageableResponseList getUserListMemberships(String listMemberScreenName, long cursor) throws TwitterException; /** @@ -296,14 +296,14 @@ public interface ListsResources { * lists/memberships | Twitter Developers * @since Twitter4J 2.2.4 */ - PagableResponseList getUserListMemberships(String listMemberScreenName, long cursor, + PageableResponseList getUserListMemberships(String listMemberScreenName, long cursor, boolean filterToOwnedLists) throws TwitterException; - PagableResponseList getUserListOwnerships(long cursor) throws TwitterException; + PageableResponseList getUserListOwnerships(long cursor) throws TwitterException; - PagableResponseList getUserListOwnerships(long listMemberId, long cursor) throws TwitterException; + PageableResponseList getUserListOwnerships(long listMemberId, long cursor) throws TwitterException; - PagableResponseList getUserListOwnerships(String listMemberScreenName, long cursor) + PageableResponseList getUserListOwnerships(String listMemberScreenName, long cursor) throws TwitterException; /** @@ -374,12 +374,12 @@ public interface ListsResources { * lists/subscribers | Twitter Developers * @since Twitter4J 2.2.3 */ - PagableResponseList getUserListSubscribers(long listId, CursorPaging paging) throws TwitterException; + PageableResponseList getUserListSubscribers(long listId, CursorPaging paging) throws TwitterException; - PagableResponseList getUserListSubscribers(String slug, long ownerId, CursorPaging paging) + PageableResponseList getUserListSubscribers(String slug, long ownerId, CursorPaging paging) throws TwitterException; - PagableResponseList getUserListSubscribers(String slug, String ownerScreenName, CursorPaging paging) + PageableResponseList getUserListSubscribers(String slug, String ownerScreenName, CursorPaging paging) throws TwitterException; /** @@ -398,7 +398,7 @@ public interface ListsResources { * lists/subscriptions | Twitter Developers * @since Twitter4J 2.1.0 */ - PagableResponseList getUserListSubscriptions(String listOwnerScreenName, long cursor) + PageableResponseList getUserListSubscriptions(String listOwnerScreenName, long cursor) throws TwitterException; /** diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/api/UsersResources.java b/twidere.component.twitter4j/src/main/java/twitter4j/api/UsersResources.java index f98051553..ba45501ff 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/api/UsersResources.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/api/UsersResources.java @@ -1,17 +1,20 @@ /* - * Copyright 2007 Yusuke Yamamoto + * Twidere - Twitter client for Android * - * Licensed 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 + * Copyright (C) 2012-2015 Mariotaku Lee * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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 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 . */ package twitter4j.api; @@ -23,7 +26,7 @@ import twitter4j.AccountSettings; import twitter4j.Category; import twitter4j.CursorPaging; import twitter4j.IDs; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.ResponseList; import twitter4j.SettingsUpdate; import twitter4j.TwitterException; @@ -146,9 +149,9 @@ public interface UsersResources { * blocks/blocking | Twitter Developers * @since Twitter4J 2.0.4 */ - PagableResponseList getBlocksList() throws TwitterException; + PageableResponseList getBlocksList() throws TwitterException; - PagableResponseList getBlocksList(CursorPaging paging) throws TwitterException; + PageableResponseList getBlocksList(CursorPaging paging) throws TwitterException; /** * Access the users in a given category of the Twitter suggested user list @@ -172,9 +175,9 @@ public interface UsersResources { IDs getMutesUsersIDs(CursorPaging paging) throws TwitterException; - PagableResponseList getMutesUsersList() throws TwitterException; + PageableResponseList getMutesUsersList() throws TwitterException; - PagableResponseList getMutesUsersList(CursorPaging paging) throws TwitterException; + PageableResponseList getMutesUsersList(CursorPaging paging) throws TwitterException; /** * Access to Twitter's suggested user list. This returns the list of diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactory.java b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactory.java index 382133130..6ea0c74c8 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactory.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactory.java @@ -33,7 +33,7 @@ import twitter4j.IDs; import twitter4j.Location; import twitter4j.MediaUploadResponse; import twitter4j.OEmbed; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Place; import twitter4j.Query; import twitter4j.QueryResult; @@ -90,9 +90,9 @@ public interface InternalJSONFactory { OEmbed createOEmbed(HttpResponse httpResponse) throws TwitterException; - PagableResponseList createPagableUserList(HttpResponse res) throws TwitterException; + PageableResponseList createPagableUserList(HttpResponse res) throws TwitterException; - PagableResponseList createPagableUserListList(HttpResponse res) throws TwitterException; + PageableResponseList createPagableUserListList(HttpResponse res) throws TwitterException; Place createPlace(HttpResponse res) throws TwitterException; diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactoryImpl.java b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactoryImpl.java index ca500ff21..30ac3b521 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactoryImpl.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/InternalJSONFactoryImpl.java @@ -37,7 +37,7 @@ import twitter4j.IDs; import twitter4j.Location; import twitter4j.MediaUploadResponse; import twitter4j.OEmbed; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Place; import twitter4j.Query; import twitter4j.QueryResult; @@ -154,12 +154,12 @@ public class InternalJSONFactoryImpl implements InternalJSONFactory { } @Override - public PagableResponseList createPagableUserList(final HttpResponse res) throws TwitterException { + public PageableResponseList createPagableUserList(final HttpResponse res) throws TwitterException { return UserJSONImpl.createPagableUserList(res, conf); } @Override - public PagableResponseList createPagableUserListList(final HttpResponse res) throws TwitterException { + public PageableResponseList createPagableUserListList(final HttpResponse res) throws TwitterException { return UserListJSONImpl.createPagableUserListList(res, conf); } diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/PagableResponseListImpl.java b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/PageableResponseListImpl.java similarity index 50% rename from twidere.component.twitter4j/src/main/java/twitter4j/internal/json/PagableResponseListImpl.java rename to twidere.component.twitter4j/src/main/java/twitter4j/internal/json/PageableResponseListImpl.java index 260144f66..eb975d514 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/PagableResponseListImpl.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/PageableResponseListImpl.java @@ -1,24 +1,27 @@ /* - * Copyright 2007 Yusuke Yamamoto + * Twidere - Twitter client for Android * - * Licensed 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 + * Copyright (C) 2012-2015 Mariotaku Lee * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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 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 . */ package twitter4j.internal.json; import org.json.JSONObject; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.http.HttpResponse; import twitter4j.internal.util.InternalParseUtil; @@ -27,12 +30,12 @@ import twitter4j.internal.util.InternalParseUtil; * @since Twitter4J 2.1.3 */ @SuppressWarnings("rawtypes") -class PagableResponseListImpl extends ResponseListImpl implements PagableResponseList { +class PageableResponseListImpl extends ResponseListImpl implements PageableResponseList { private static final long serialVersionUID = 9098876089678648404L; private final long previousCursor; private final long nextCursor; - PagableResponseListImpl(final int size, final JSONObject json, final HttpResponse res) { + PageableResponseListImpl(final int size, final JSONObject json, final HttpResponse res) { super(size, res); this.previousCursor = InternalParseUtil.getLong("previous_cursor", json); this.nextCursor = InternalParseUtil.getLong("next_cursor", json); diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserJSONImpl.java b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserJSONImpl.java index 4ef78f8c0..1018c6fad 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserJSONImpl.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserJSONImpl.java @@ -27,7 +27,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Date; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.ResponseList; import twitter4j.Status; import twitter4j.TwitterException; @@ -525,14 +525,14 @@ import static twitter4j.internal.util.InternalParseUtil.getRawString; } /* package */ - static PagableResponseList createPagableUserList(final HttpResponse res, final Configuration conf) + static PageableResponseList createPagableUserList(final HttpResponse res, final Configuration conf) throws TwitterException { try { final JSONObject json = res.asJSONObject(); final JSONArray list = json.getJSONArray("users"); final int size = list.length(); @SuppressWarnings("unchecked") - final PagableResponseList users = new PagableResponseListImpl(size, json, res); + final PageableResponseList users = new PageableResponseListImpl(size, json, res); for (int i = 0; i < size; i++) { final JSONObject userJson = list.getJSONObject(i); final User user = new UserJSONImpl(userJson); diff --git a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserListJSONImpl.java b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserListJSONImpl.java index 0e18e23d5..53657a1b3 100644 --- a/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserListJSONImpl.java +++ b/twidere.component.twitter4j/src/main/java/twitter4j/internal/json/UserListJSONImpl.java @@ -26,7 +26,7 @@ import org.json.JSONObject; import java.net.URI; import java.net.URISyntaxException; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.ResponseList; import twitter4j.TwitterException; import twitter4j.User; @@ -218,14 +218,14 @@ import static twitter4j.internal.util.InternalParseUtil.getRawString; } } - static PagableResponseList createPagableUserListList(final HttpResponse res, final Configuration conf) + static PageableResponseList createPagableUserListList(final HttpResponse res, final Configuration conf) throws TwitterException { try { final JSONObject json = res.asJSONObject(); final JSONArray list = json.getJSONArray("lists"); final int size = list.length(); @SuppressWarnings("unchecked") - final PagableResponseList users = new PagableResponseListImpl(size, json, res); + final PageableResponseList users = new PageableResponseListImpl(size, json, res); for (int i = 0; i < size; i++) { final JSONObject userListJson = list.getJSONObject(i); final UserList userList = new UserListJSONImpl(userListJson); diff --git a/twidere.extension.streaming/build.gradle b/twidere.extension.streaming/build.gradle index b529ee84a..d7111b198 100644 --- a/twidere.extension.streaming/build.gradle +++ b/twidere.extension.streaming/build.gradle @@ -28,7 +28,7 @@ android { applicationId "org.mariotaku.twidere.extension.streaming" minSdkVersion 14 targetSdkVersion 21 - versionCode 12 + versionCode 13 versionName "1.10 (0.3.0-dev)" } buildTypes { diff --git a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java index 7c683b908..f99c6c2f3 100644 --- a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java +++ b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java @@ -10,7 +10,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.ContentObserver; -import android.database.Cursor; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -24,6 +23,7 @@ import org.mariotaku.twidere.extension.streaming.util.TwidereHostAddressResolver import org.mariotaku.twidere.extension.streaming.util.Utils; import org.mariotaku.twidere.library.twitter4j.streaming.BuildConfig; import org.mariotaku.twidere.model.ParcelableAccount; +import org.mariotaku.twidere.model.ParcelableAccount.ParcelableCredentials; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; import org.mariotaku.twidere.provider.TwidereDataStore.Mentions; @@ -163,33 +163,18 @@ public class StreamingService extends Service implements Constants, PrivateConst private boolean setTwitterInstances(final TwidereSharedPreferences prefs) { if (prefs == null) return false; - final String[] cols = new String[]{Accounts.OAUTH_TOKEN, Accounts.OAUTH_TOKEN_SECRET, Accounts.ACCOUNT_ID, - Accounts.CONSUMER_KEY, Accounts.CONSUMER_SECRET}; - final String where = Accounts.IS_ACTIVATED + " = 1" + " AND " + Accounts.AUTH_TYPE + " = " - + Accounts.AUTH_TYPE_OAUTH; - final Cursor cur = mResolver.query(Accounts.CONTENT_URI, cols, where, null, null); - if (cur == null) return false; + final List accountsList = ParcelableAccount.getCredentialsList(this, true); if (BuildConfig.DEBUG) { Log.d(LOGTAG, "Setting up twitter stream instances"); } - final int count = cur.getCount(); - mAccountIds = new long[count]; - if (count == 0) { - cur.close(); - return false; - } - cur.moveToFirst(); + mAccountIds = new long[accountsList.size()]; clearTwitterInstances(); - final int token_idx = cur.getColumnIndex(Accounts.OAUTH_TOKEN); - final int secret_idx = cur.getColumnIndex(Accounts.OAUTH_TOKEN_SECRET); - final int account_id_idx = cur.getColumnIndex(Accounts.ACCOUNT_ID); - final int consumer_key_idx = cur.getColumnIndex(Accounts.CONSUMER_KEY); - final int consumer_secret_idx = cur.getColumnIndex(Accounts.CONSUMER_SECRET); - while (!cur.isAfterLast()) { - final String token = cur.getString(token_idx); - final String secret = cur.getString(secret_idx); - final long account_id = cur.getLong(account_id_idx); - mAccountIds[cur.getPosition()] = account_id; + for (int i = 0, j = accountsList.size(); i < j; i++) { + final ParcelableCredentials account = accountsList.get(i); + final String token = account.oauth_token; + final String secret = account.oauth_token_secret; + final long account_id = account.account_id; + mAccountIds[i] = account_id; final StreamConfigurationBuilder cb = new StreamConfigurationBuilder(); cb.setGZIPEnabled(prefs.getBoolean(KEY_GZIP_COMPRESSING, true)); cb.setIncludeEntitiesEnabled(true); @@ -201,8 +186,7 @@ public class StreamingService extends Service implements Constants, PrivateConst .getNonEmptyString(prefs, KEY_CONSUMER_KEY, TWITTER_CONSUMER_KEY_2); final String default_consumer_secret = Utils.getNonEmptyString(prefs, KEY_CONSUMER_SECRET, TWITTER_CONSUMER_SECRET_2); - final String consumer_key = cur.getString(consumer_key_idx), consumer_secret = cur - .getString(consumer_secret_idx); + final String consumer_key = account.consumer_key, consumer_secret = account.consumer_secret; if (!isEmpty(consumer_key) && !isEmpty(consumer_secret)) { cb.setOAuthConsumerKey(consumer_key); cb.setOAuthConsumerSecret(consumer_secret); @@ -215,9 +199,7 @@ public class StreamingService extends Service implements Constants, PrivateConst twitter.addListener(new UserStreamListenerImpl(this, account_id)); twitter.user(); mTwitterInstances.add(new WeakReference<>(twitter)); - cur.moveToNext(); } - cur.close(); return true; } diff --git a/twidere/build.gradle b/twidere/build.gradle index a2eea9f68..af9b8b367 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -54,18 +54,20 @@ dependencies { compile 'com.android.support:recyclerview-v7:21.0.3' compile 'com.android.support:palette-v7:21.0.3' compile 'com.sothree.slidinguppanel:library:2.0.4' - compile 'it.sephiroth.android.library.imagezoom:imagezoom:2.1.1' compile 'com.twitter:twitter-text:1.9.9' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' compile 'org.apache.httpcomponents:httpclient-android:4.3.5' compile 'org.apache.httpcomponents:httpmime:4.3.5' + compile 'org.apache.commons:commons-csv:1.1' compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0' compile 'com.squareup:otto:1.3.5' compile 'dnsjava:dnsjava:2.1.6' compile 'com.commonsware.cwac:merge:1.1.1' + compile 'com.diegocarloslima:byakugallery:0.1.0' googleCompile 'com.google.android.gms:play-services:6.5.87' - fdroidCompile 'org.osmdroid:osmdroid-android:4.2' - fdroidCompile 'org.slf4j:slf4j-simple:1.7.9' + googleCompile 'com.google.maps.android:android-maps-utils:0.3.4' + fdroidCompile 'org.osmdroid:osmdroid-android:4.3' + fdroidCompile 'org.slf4j:slf4j-simple:1.7.10' compile project(':twidere.component.common') compile project(':twidere.component.nyan') compile project(':twidere.component.viewer.media') diff --git a/twidere/src/google/java/org/mariotaku/twidere/fragment/support/StatusesMapFragment.java b/twidere/src/google/java/org/mariotaku/twidere/fragment/support/StatusesMapFragment.java new file mode 100644 index 000000000..da6df3488 --- /dev/null +++ b/twidere/src/google/java/org/mariotaku/twidere/fragment/support/StatusesMapFragment.java @@ -0,0 +1,49 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.fragment.support; + +import android.content.Context; +import android.os.Bundle; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.maps.android.clustering.ClusterManager; + +import org.mariotaku.twidere.model.ClusterStatus; +import org.mariotaku.twidere.util.StatusClusterRenderer; + +/** + * Created by mariotaku on 15/1/13. + */ +public class StatusesMapFragment extends SupportMapFragment { + + private ClusterManager mClusterManager; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Context context = getActivity(); + final GoogleMap googleMap = getMap(); + mClusterManager = new ClusterManager<>(context, googleMap); + mClusterManager.setRenderer(new StatusClusterRenderer(context, googleMap, mClusterManager)); + } + + +} diff --git a/twidere/src/google/java/org/mariotaku/twidere/model/ClusterStatus.java b/twidere/src/google/java/org/mariotaku/twidere/model/ClusterStatus.java new file mode 100644 index 000000000..e64f7e18e --- /dev/null +++ b/twidere/src/google/java/org/mariotaku/twidere/model/ClusterStatus.java @@ -0,0 +1,44 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.model; + +import com.google.android.gms.maps.model.LatLng; +import com.google.maps.android.clustering.ClusterItem; + +/** + * Created by mariotaku on 15/1/13. + */ +public class ClusterStatus implements ClusterItem { + + private final ParcelableStatus status; + + public ClusterStatus(ParcelableStatus status) { + if (!ParcelableLocation.isValidLocation(status.location)) { + throw new IllegalArgumentException(); + } + this.status = status; + } + + @Override + public LatLng getPosition() { + return new LatLng(status.location.latitude, status.location.longitude); + } + +} diff --git a/twidere/src/google/java/org/mariotaku/twidere/util/StatusClusterRenderer.java b/twidere/src/google/java/org/mariotaku/twidere/util/StatusClusterRenderer.java new file mode 100644 index 000000000..e38e37fd7 --- /dev/null +++ b/twidere/src/google/java/org/mariotaku/twidere/util/StatusClusterRenderer.java @@ -0,0 +1,947 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.OvalShape; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.util.SparseArray; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.Projection; +import com.google.android.gms.maps.model.BitmapDescriptor; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.maps.android.MarkerManager; +import com.google.maps.android.R; +import com.google.maps.android.clustering.Cluster; +import com.google.maps.android.clustering.ClusterManager; +import com.google.maps.android.clustering.view.ClusterRenderer; +import com.google.maps.android.geometry.Point; +import com.google.maps.android.projection.SphericalMercatorProjection; +import com.google.maps.android.ui.IconGenerator; +import com.google.maps.android.ui.SquareTextView; + +import org.mariotaku.twidere.model.ClusterStatus; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm.MAX_DISTANCE_AT_ZOOM; + +/** + * The default view for a ClusterManager. Markers are animated in and out of clusters. + */ +@SuppressWarnings("unused") +public class StatusClusterRenderer implements ClusterRenderer { + private static final boolean SHOULD_ANIMATE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + private final GoogleMap mMap; + private final IconGenerator mIconGenerator; + private final ClusterManager mClusterManager; + private final float mDensity; + + private static final int[] BUCKETS = {10, 20, 50, 100, 200, 500, 1000}; + private ShapeDrawable mColoredCircleBackground; + + /** + * Markers that are currently on the map. + */ + private Set mMarkers = Collections.newSetFromMap( + new ConcurrentHashMap()); + + /** + * Icons for each bucket. + */ + private SparseArray mIcons = new SparseArray<>(); + + /** + * Markers for single ClusterItems. + */ + private MarkerCache mMarkerCache = new MarkerCache<>(); + + /** + * If cluster size is less than this size, display individual markers. + */ + private static final int MIN_CLUSTER_SIZE = 4; + + /** + * The currently displayed set of clusters. + */ + private Set> mClusters; + + /** + * Lookup between markers and the associated cluster. + */ + private Map> mMarkerToCluster = new HashMap<>(); + private Map, Marker> mClusterToMarker = new HashMap<>(); + + /** + * The target zoom level for the current set of clusters. + */ + private float mZoom; + + private final ViewModifier mViewModifier = new ViewModifier(); + + private ClusterManager.OnClusterClickListener mClickListener; + private ClusterManager.OnClusterInfoWindowClickListener mInfoWindowClickListener; + private ClusterManager.OnClusterItemClickListener mItemClickListener; + private ClusterManager.OnClusterItemInfoWindowClickListener mItemInfoWindowClickListener; + + public StatusClusterRenderer(Context context, GoogleMap map, ClusterManager clusterManager) { + mMap = map; + mDensity = context.getResources().getDisplayMetrics().density; + mIconGenerator = new IconGenerator(context); + mIconGenerator.setContentView(makeSquareTextView(context)); + mIconGenerator.setTextAppearance(R.style.ClusterIcon_TextAppearance); + mIconGenerator.setBackground(makeClusterBackground()); + mClusterManager = clusterManager; + } + + @Override + public void onAdd() { + mClusterManager.getMarkerCollection().setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { + @Override + public boolean onMarkerClick(Marker marker) { + return mItemClickListener != null && mItemClickListener.onClusterItemClick(mMarkerCache.get(marker)); + } + }); + + mClusterManager.getMarkerCollection().setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { + @Override + public void onInfoWindowClick(Marker marker) { + if (mItemInfoWindowClickListener != null) { + mItemInfoWindowClickListener.onClusterItemInfoWindowClick(mMarkerCache.get(marker)); + } + } + }); + + mClusterManager.getClusterMarkerCollection().setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { + @Override + public boolean onMarkerClick(Marker marker) { + return mClickListener != null && mClickListener.onClusterClick(mMarkerToCluster.get(marker)); + } + }); + + mClusterManager.getClusterMarkerCollection().setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { + @Override + public void onInfoWindowClick(Marker marker) { + if (mInfoWindowClickListener != null) { + mInfoWindowClickListener.onClusterInfoWindowClick(mMarkerToCluster.get(marker)); + } + } + }); + } + + @Override + public void onRemove() { + mClusterManager.getMarkerCollection().setOnMarkerClickListener(null); + mClusterManager.getClusterMarkerCollection().setOnMarkerClickListener(null); + } + + private LayerDrawable makeClusterBackground() { + mColoredCircleBackground = new ShapeDrawable(new OvalShape()); + ShapeDrawable outline = new ShapeDrawable(new OvalShape()); + outline.getPaint().setColor(0x80ffffff); // Transparent white. + LayerDrawable background = new LayerDrawable(new Drawable[]{outline, mColoredCircleBackground}); + int strokeWidth = (int) (mDensity * 3); + background.setLayerInset(1, strokeWidth, strokeWidth, strokeWidth, strokeWidth); + return background; + } + + private SquareTextView makeSquareTextView(Context context) { + SquareTextView squareTextView = new SquareTextView(context); + ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + squareTextView.setLayoutParams(layoutParams); + squareTextView.setId(R.id.text); + int twelveDpi = (int) (12 * mDensity); + squareTextView.setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi); + return squareTextView; + } + + private int getColor(int clusterSize) { + final float hueRange = 220; + final float sizeRange = 300; + final float size = Math.min(clusterSize, sizeRange); + final float hue = (sizeRange - size) * (sizeRange - size) / (sizeRange * sizeRange) * hueRange; + return Color.HSVToColor(new float[]{ + hue, 1f, .6f + }); + } + + protected String getClusterText(int bucket) { + if (bucket < BUCKETS[0]) { + return String.valueOf(bucket); + } + return String.valueOf(bucket) + "+"; + } + + /** + * Gets the "bucket" for a particular cluster. By default, uses the number of points within the + * cluster, bucketed to some set points. + */ + protected int getBucket(Cluster cluster) { + int size = cluster.getSize(); + if (size <= BUCKETS[0]) { + return size; + } + for (int i = 0; i < BUCKETS.length - 1; i++) { + if (size < BUCKETS[i + 1]) { + return BUCKETS[i]; + } + } + return BUCKETS[BUCKETS.length - 1]; + } + + /** + * ViewModifier ensures only one re-rendering of the view occurs at a time, and schedules + * re-rendering, which is performed by the RenderTask. + */ + @SuppressLint("HandlerLeak") + private class ViewModifier extends Handler { + private static final int RUN_TASK = 0; + private static final int TASK_FINISHED = 1; + private boolean mViewModificationInProgress = false; + private RenderTask mNextClusters = null; + + @Override + public void handleMessage(Message msg) { + if (msg.what == TASK_FINISHED) { + mViewModificationInProgress = false; + if (mNextClusters != null) { + // Run the task that was queued up. + sendEmptyMessage(RUN_TASK); + } + return; + } + removeMessages(RUN_TASK); + + if (mViewModificationInProgress) { + // Busy - wait for the callback. + return; + } + + if (mNextClusters == null) { + // Nothing to do. + return; + } + + RenderTask renderTask; + synchronized (this) { + renderTask = mNextClusters; + mNextClusters = null; + mViewModificationInProgress = true; + } + + renderTask.setCallback(new Runnable() { + @Override + public void run() { + sendEmptyMessage(TASK_FINISHED); + } + }); + renderTask.setProjection(mMap.getProjection()); + renderTask.setMapZoom(mMap.getCameraPosition().zoom); + new Thread(renderTask).start(); + } + + public void queue(Set> clusters) { + synchronized (this) { + // Overwrite any pending cluster tasks - we don't care about intermediate states. + mNextClusters = new RenderTask(clusters); + } + sendEmptyMessage(RUN_TASK); + } + } + + /** + * Determine whether the cluster should be rendered as individual markers or a cluster. + */ + protected boolean shouldRenderAsCluster(Cluster cluster) { + return cluster.getSize() > MIN_CLUSTER_SIZE; + } + + /** + * Transforms the current view (represented by DefaultClusterRenderer.mClusters and DefaultClusterRenderer.mZoom) to a + * new zoom level and set of clusters. + *

+ * This must be run off the UI thread. Work is coordinated in the RenderTask, then queued up to + * be executed by a MarkerModifier. + *

+ * There are three stages for the render: + *

+ * 1. Markers are added to the map + *

+ * 2. Markers are animated to their final position + *

+ * 3. Any old markers are removed from the map + *

+ * When zooming in, markers are animated out from the nearest existing cluster. When zooming + * out, existing clusters are animated to the nearest new cluster. + */ + private class RenderTask implements Runnable { + final Set> clusters; + private Runnable mCallback; + private Projection mProjection; + private SphericalMercatorProjection mSphericalMercatorProjection; + private float mMapZoom; + + private RenderTask(Set> clusters) { + this.clusters = clusters; + } + + /** + * A callback to be run when all work has been completed. + * + * @param callback {@link Runnable} Callback for RenderTask + */ + public void setCallback(Runnable callback) { + mCallback = callback; + } + + public void setProjection(Projection projection) { + this.mProjection = projection; + } + + public void setMapZoom(float zoom) { + this.mMapZoom = zoom; + this.mSphericalMercatorProjection = new SphericalMercatorProjection(256 * Math.pow(2, Math.min(zoom, mZoom))); + } + + @SuppressLint("NewApi") + public void run() { + if (clusters.equals(StatusClusterRenderer.this.mClusters)) { + mCallback.run(); + return; + } + + final MarkerModifier markerModifier = new MarkerModifier(); + + final float zoom = mMapZoom; + final boolean zoomingIn = zoom > mZoom; + final float zoomDelta = zoom - mZoom; + + final Set markersToRemove = mMarkers; + final LatLngBounds visibleBounds = mProjection.getVisibleRegion().latLngBounds; + // TODO: Add some padding, so that markers can animate in from off-screen. + + // Find all of the existing clusters that are on-screen. These are candidates for + // markers to animate from. + List existingClustersOnScreen = null; + if (StatusClusterRenderer.this.mClusters != null && SHOULD_ANIMATE) { + existingClustersOnScreen = new ArrayList<>(); + for (Cluster c : StatusClusterRenderer.this.mClusters) { + if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) { + Point point = mSphericalMercatorProjection.toPoint(c.getPosition()); + existingClustersOnScreen.add(point); + } + } + } + + // Create the new markers and animate them to their new positions. + final Set newMarkers = Collections.newSetFromMap( + new ConcurrentHashMap()); + for (Cluster c : clusters) { + boolean onScreen = visibleBounds.contains(c.getPosition()); + if (zoomingIn && onScreen && SHOULD_ANIMATE) { + Point point = mSphericalMercatorProjection.toPoint(c.getPosition()); + Point closest = findClosestCluster(existingClustersOnScreen, point); + if (closest != null) { + LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest); + markerModifier.add(true, new CreateMarkerTask(c, newMarkers, animateTo)); + } else { + markerModifier.add(true, new CreateMarkerTask(c, newMarkers, null)); + } + } else { + markerModifier.add(onScreen, new CreateMarkerTask(c, newMarkers, null)); + } + } + + // Wait for all markers to be added. + markerModifier.waitUntilFree(); + + // Don't remove any markers that were just added. This is basically anything that had + // a hit in the MarkerCache. + markersToRemove.removeAll(newMarkers); + + // Find all of the new clusters that were added on-screen. These are candidates for + // markers to animate from. + List newClustersOnScreen = null; + if (SHOULD_ANIMATE) { + newClustersOnScreen = new ArrayList<>(); + for (Cluster c : clusters) { + if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) { + Point p = mSphericalMercatorProjection.toPoint(c.getPosition()); + newClustersOnScreen.add(p); + } + } + } + + // Remove the old markers, animating them into clusters if zooming out. + for (final MarkerWithPosition marker : markersToRemove) { + boolean onScreen = visibleBounds.contains(marker.position); + // Don't animate when zooming out more than 3 zoom levels. + // TODO: drop animation based on speed of device & number of markers to animate. + if (!zoomingIn && zoomDelta > -3 && onScreen && SHOULD_ANIMATE) { + final Point point = mSphericalMercatorProjection.toPoint(marker.position); + final Point closest = findClosestCluster(newClustersOnScreen, point); + if (closest != null) { + LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest); + markerModifier.animateThenRemove(marker, marker.position, animateTo); + } else { + markerModifier.remove(true, marker.marker); + } + } else { + markerModifier.remove(onScreen, marker.marker); + } + } + + markerModifier.waitUntilFree(); + + mMarkers = newMarkers; + StatusClusterRenderer.this.mClusters = clusters; + mZoom = zoom; + + mCallback.run(); + } + } + + @Override + public void onClustersChanged(Set> clusters) { + mViewModifier.queue(clusters); + } + + @Override + public void setOnClusterClickListener(ClusterManager.OnClusterClickListener listener) { + mClickListener = listener; + } + + @Override + public void setOnClusterInfoWindowClickListener(ClusterManager.OnClusterInfoWindowClickListener listener) { + mInfoWindowClickListener = listener; + } + + @Override + public void setOnClusterItemClickListener(ClusterManager.OnClusterItemClickListener listener) { + mItemClickListener = listener; + } + + @Override + public void setOnClusterItemInfoWindowClickListener(ClusterManager.OnClusterItemInfoWindowClickListener listener) { + mItemInfoWindowClickListener = listener; + } + + private static double distanceSquared(Point a, Point b) { + return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); + } + + private static Point findClosestCluster(List markers, Point point) { + if (markers == null || markers.isEmpty()) return null; + + // TODO: make this configurable. + double minDistSquared = MAX_DISTANCE_AT_ZOOM * MAX_DISTANCE_AT_ZOOM; + Point closest = null; + for (Point candidate : markers) { + double dist = distanceSquared(candidate, point); + if (dist < minDistSquared) { + closest = candidate; + minDistSquared = dist; + } + } + return closest; + } + + /** + * Handles all markerWithPosition manipulations on the map. Work (such as adding, removing, or + * animating a markerWithPosition) is performed while trying not to block the rest of the app's + * UI. + */ + @SuppressLint("HandlerLeak") + private class MarkerModifier extends Handler implements MessageQueue.IdleHandler { + private static final int BLANK = 0; + + private final Lock lock = new ReentrantLock(); + private final Condition busyCondition = lock.newCondition(); + + private Queue mCreateMarkerTasks = new LinkedList<>(); + private Queue mOnScreenCreateMarkerTasks = new LinkedList<>(); + private Queue mRemoveMarkerTasks = new LinkedList<>(); + private Queue mOnScreenRemoveMarkerTasks = new LinkedList<>(); + private Queue mAnimationTasks = new LinkedList<>(); + + /** + * Whether the idle listener has been added to the UI thread's MessageQueue. + */ + private boolean mListenerAdded; + + private MarkerModifier() { + super(Looper.getMainLooper()); + } + + /** + * Creates markers for a cluster some time in the future. + * + * @param priority whether this operation should have priority. + */ + public void add(boolean priority, CreateMarkerTask c) { + lock.lock(); + sendEmptyMessage(BLANK); + if (priority) { + mOnScreenCreateMarkerTasks.add(c); + } else { + mCreateMarkerTasks.add(c); + } + lock.unlock(); + } + + /** + * Removes a markerWithPosition some time in the future. + * + * @param priority whether this operation should have priority. + * @param m the markerWithPosition to remove. + */ + public void remove(boolean priority, Marker m) { + lock.lock(); + sendEmptyMessage(BLANK); + if (priority) { + mOnScreenRemoveMarkerTasks.add(m); + } else { + mRemoveMarkerTasks.add(m); + } + lock.unlock(); + } + + /** + * Animates a markerWithPosition some time in the future. + * + * @param marker the markerWithPosition to animate. + * @param from the position to animate from. + * @param to the position to animate to. + */ + public void animate(MarkerWithPosition marker, LatLng from, LatLng to) { + lock.lock(); + mAnimationTasks.add(new AnimationTask(marker, from, to)); + lock.unlock(); + } + + /** + * Animates a markerWithPosition some time in the future, and removes it when the animation + * is complete. + * + * @param marker the markerWithPosition to animate. + * @param from the position to animate from. + * @param to the position to animate to. + */ + public void animateThenRemove(MarkerWithPosition marker, LatLng from, LatLng to) { + lock.lock(); + AnimationTask animationTask = new AnimationTask(marker, from, to); + animationTask.removeOnAnimationComplete(mClusterManager.getMarkerManager()); + mAnimationTasks.add(animationTask); + lock.unlock(); + } + + @Override + public void handleMessage(Message msg) { + if (!mListenerAdded) { + Looper.myQueue().addIdleHandler(this); + mListenerAdded = true; + } + removeMessages(BLANK); + + lock.lock(); + try { + + // Perform up to 10 tasks at once. + // Consider only performing 10 remove tasks, not adds and animations. + // Removes are relatively slow and are much better when batched. + for (int i = 0; i < 10; i++) { + performNextTask(); + } + + if (!isBusy()) { + mListenerAdded = false; + Looper.myQueue().removeIdleHandler(this); + // Signal any other threads that are waiting. + busyCondition.signalAll(); + } else { + // Sometimes the idle queue may not be called - schedule up some work regardless + // of whether the UI thread is busy or not. + // TODO: try to remove this. + sendEmptyMessageDelayed(BLANK, 10); + } + } finally { + lock.unlock(); + } + } + + /** + * Perform the next task. Prioritise any on-screen work. + */ + private void performNextTask() { + if (!mOnScreenRemoveMarkerTasks.isEmpty()) { + removeMarker(mOnScreenRemoveMarkerTasks.poll()); + } else if (!mAnimationTasks.isEmpty()) { + mAnimationTasks.poll().perform(); + } else if (!mOnScreenCreateMarkerTasks.isEmpty()) { + mOnScreenCreateMarkerTasks.poll().perform(this); + } else if (!mCreateMarkerTasks.isEmpty()) { + mCreateMarkerTasks.poll().perform(this); + } else if (!mRemoveMarkerTasks.isEmpty()) { + removeMarker(mRemoveMarkerTasks.poll()); + } + } + + private void removeMarker(Marker m) { + Cluster cluster = mMarkerToCluster.get(m); + mClusterToMarker.remove(cluster); + mMarkerCache.remove(m); + mMarkerToCluster.remove(m); + mClusterManager.getMarkerManager().remove(m); + } + + /** + * @return true if there is still work to be processed. + */ + public boolean isBusy() { + try { + lock.lock(); + return !(mCreateMarkerTasks.isEmpty() && mOnScreenCreateMarkerTasks.isEmpty() && + mOnScreenRemoveMarkerTasks.isEmpty() && mRemoveMarkerTasks.isEmpty() && + mAnimationTasks.isEmpty() + ); + } finally { + lock.unlock(); + } + } + + /** + * Blocks the calling thread until all work has been processed. + */ + public void waitUntilFree() { + while (isBusy()) { + // Sometimes the idle queue may not be called - schedule up some work regardless + // of whether the UI thread is busy or not. + // TODO: try to remove this. + sendEmptyMessage(BLANK); + lock.lock(); + try { + if (isBusy()) { + busyCondition.await(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + lock.unlock(); + } + } + } + + @Override + public boolean queueIdle() { + // When the UI is not busy, schedule some work. + sendEmptyMessage(BLANK); + return true; + } + } + + /** + * A cache of markers representing individual ClusterItems. + */ + private static class MarkerCache { + private Map mCache = new HashMap<>(); + private Map mCacheReverse = new HashMap<>(); + + public Marker get(T item) { + return mCache.get(item); + } + + public T get(Marker m) { + return mCacheReverse.get(m); + } + + public void put(T item, Marker m) { + mCache.put(item, m); + mCacheReverse.put(m, item); + } + + public void remove(Marker m) { + T item = mCacheReverse.get(m); + mCacheReverse.remove(m); + mCache.remove(item); + } + } + + /** + * Called before the marker for a ClusterItem is added to the map. + */ + protected void onBeforeClusterItemRendered(ClusterStatus item, MarkerOptions markerOptions) { + } + + /** + * Called before the marker for a Cluster is added to the map. + * The default implementation draws a circle with a rough count of the number of items. + */ + protected void onBeforeClusterRendered(Cluster cluster, MarkerOptions markerOptions) { + int bucket = getBucket(cluster); + BitmapDescriptor descriptor = mIcons.get(bucket); + if (descriptor == null) { + mColoredCircleBackground.getPaint().setColor(getColor(bucket)); + descriptor = BitmapDescriptorFactory.fromBitmap(mIconGenerator.makeIcon(getClusterText(bucket))); + mIcons.put(bucket, descriptor); + } + // TODO: consider adding anchor(.5, .5) (Individual markers will overlap more often) + markerOptions.icon(descriptor); + } + + /** + * Called after the marker for a Cluster has been added to the map. + */ + protected void onClusterRendered(Cluster cluster, Marker marker) { + } + + /** + * Called after the marker for a ClusterItem has been added to the map. + */ + protected void onClusterItemRendered(ClusterStatus clusterItem, Marker marker) { + } + + /** + * Get the marker from a ClusterItem + * + * @param clusterItem ClusterItem which you will obtain its marker + * @return a marker from a ClusterItem or null if it does not exists + */ + public Marker getMarker(ClusterStatus clusterItem) { + return mMarkerCache.get(clusterItem); + } + + /** + * Get the ClusterItem from a marker + * + * @param marker which you will obtain its ClusterItem + * @return a ClusterItem from a marker or null if it does not exists + */ + public ClusterStatus getClusterItem(Marker marker) { + return mMarkerCache.get(marker); + } + + /** + * Get the marker from a Cluster + * + * @param cluster which you will obtain its marker + * @return a marker from a cluster or null if it does not exists + */ + public Marker getMarker(Cluster cluster) { + return mClusterToMarker.get(cluster); + } + + /** + * Get the Cluster from a marker + * + * @param marker which you will obtain its Cluster + * @return a Cluster from a marker or null if it does not exists + */ + public Cluster getCluster(Marker marker) { + return mMarkerToCluster.get(marker); + } + + /** + * Creates markerWithPosition(s) for a particular cluster, animating it if necessary. + */ + private class CreateMarkerTask { + private final Cluster cluster; + private final Set newMarkers; + private final LatLng animateFrom; + + /** + * @param c the cluster to render. + * @param markersAdded a collection of markers to append any created markers. + * @param animateFrom the location to animate the markerWithPosition from, or null if no + * animation is required. + */ + public CreateMarkerTask(Cluster c, Set markersAdded, LatLng animateFrom) { + this.cluster = c; + this.newMarkers = markersAdded; + this.animateFrom = animateFrom; + } + + private void perform(MarkerModifier markerModifier) { + // Don't show small clusters. Render the markers inside, instead. + if (!shouldRenderAsCluster(cluster)) { + for (ClusterStatus item : cluster.getItems()) { + Marker marker = mMarkerCache.get(item); + MarkerWithPosition markerWithPosition; + if (marker == null) { + MarkerOptions markerOptions = new MarkerOptions(); + if (animateFrom != null) { + markerOptions.position(animateFrom); + } else { + markerOptions.position(item.getPosition()); + } + onBeforeClusterItemRendered(item, markerOptions); + marker = mClusterManager.getMarkerCollection().addMarker(markerOptions); + markerWithPosition = new MarkerWithPosition(marker); + mMarkerCache.put(item, marker); + if (animateFrom != null) { + markerModifier.animate(markerWithPosition, animateFrom, item.getPosition()); + } + } else { + markerWithPosition = new MarkerWithPosition(marker); + } + onClusterItemRendered(item, marker); + newMarkers.add(markerWithPosition); + } + return; + } + + MarkerOptions markerOptions = new MarkerOptions(). + position(animateFrom == null ? cluster.getPosition() : animateFrom); + + onBeforeClusterRendered(cluster, markerOptions); + + Marker marker = mClusterManager.getClusterMarkerCollection().addMarker(markerOptions); + mMarkerToCluster.put(marker, cluster); + mClusterToMarker.put(cluster, marker); + MarkerWithPosition markerWithPosition = new MarkerWithPosition(marker); + if (animateFrom != null) { + markerModifier.animate(markerWithPosition, animateFrom, cluster.getPosition()); + } + onClusterRendered(cluster, marker); + newMarkers.add(markerWithPosition); + } + } + + /** + * A Marker and its position. Marker.getPosition() must be called from the UI thread, so this + * object allows lookup from other threads. + */ + private static class MarkerWithPosition { + private final Marker marker; + private LatLng position; + + private MarkerWithPosition(Marker marker) { + this.marker = marker; + position = marker.getPosition(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof MarkerWithPosition) { + return marker.equals(((MarkerWithPosition) other).marker); + } + return false; + } + + @Override + public int hashCode() { + return marker.hashCode(); + } + } + + private static final TimeInterpolator ANIMATION_INTERP = new DecelerateInterpolator(); + + /** + * Animates a markerWithPosition from one position to another. TODO: improve performance for + * slow devices (e.g. Nexus S). + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) + private class AnimationTask extends AnimatorListenerAdapter implements ValueAnimator.AnimatorUpdateListener { + private final MarkerWithPosition markerWithPosition; + private final Marker marker; + private final LatLng from; + private final LatLng to; + private boolean mRemoveOnComplete; + private MarkerManager mMarkerManager; + + private AnimationTask(MarkerWithPosition markerWithPosition, LatLng from, LatLng to) { + this.markerWithPosition = markerWithPosition; + this.marker = markerWithPosition.marker; + this.from = from; + this.to = to; + } + + public void perform() { + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); + valueAnimator.setInterpolator(ANIMATION_INTERP); + valueAnimator.addUpdateListener(this); + valueAnimator.addListener(this); + valueAnimator.start(); + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mRemoveOnComplete) { + Cluster cluster = mMarkerToCluster.get(marker); + mClusterToMarker.remove(cluster); + mMarkerCache.remove(marker); + mMarkerToCluster.remove(marker); + mMarkerManager.remove(marker); + } + markerWithPosition.position = to; + } + + public void removeOnAnimationComplete(MarkerManager markerManager) { + mMarkerManager = markerManager; + mRemoveOnComplete = true; + } + + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + float fraction = valueAnimator.getAnimatedFraction(); + double lat = (to.latitude - from.latitude) * fraction + from.latitude; + double lngDelta = to.longitude - from.longitude; + + // Take the shortest path across the 180th meridian. + if (Math.abs(lngDelta) > 180) { + lngDelta -= Math.signum(lngDelta) * 360; + } + double lng = lngDelta * fraction + from.longitude; + LatLng position = new LatLng(lat, lng); + marker.setPosition(position); + } + } +} \ No newline at end of file diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivityOld.java b/twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivityOld.java deleted file mode 100644 index 1fef8f970..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivityOld.java +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2015 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.gallery3d; - -import android.app.ActionBar; -import android.app.ActionBar.OnMenuVisibilityListener; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.view.Menu; -import android.view.MenuItem; -import android.view.SubMenu; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.TranslateAnimation; -import android.widget.ImageView; -import android.widget.ProgressBar; - -import org.mariotaku.gallery3d.ui.GLRoot; -import org.mariotaku.gallery3d.ui.GLRootView; -import org.mariotaku.gallery3d.ui.GLView; -import org.mariotaku.gallery3d.ui.PhotoView; -import org.mariotaku.gallery3d.ui.SynchronizedHandler; -import org.mariotaku.gallery3d.util.GalleryUtils; -import org.mariotaku.gallery3d.util.ThreadPool; -import org.mariotaku.menucomponent.widget.MenuBar; -import org.mariotaku.menucomponent.widget.MenuBar.MenuBarListener; -import org.mariotaku.twidere.Constants; -import org.mariotaku.twidere.R; -import org.mariotaku.twidere.activity.support.BaseSupportActivity; -import org.mariotaku.twidere.loader.support.TileImageLoader; -import org.mariotaku.twidere.util.SaveImageTask; -import org.mariotaku.twidere.util.ThemeUtils; -import org.mariotaku.twidere.util.Utils; - -import java.io.File; - -public final class ImageViewerGLActivityOld extends BaseSupportActivity implements Constants, PhotoView.Listener, - TileImageLoader.DownloadListener, LoaderManager.LoaderCallbacks, OnMenuVisibilityListener, - MenuBarListener { - - private final GLView mRootPane = new GLView() { - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { - mPhotoView.layout(0, 0, right - left, bottom - top); - } - }; - protected static final int FLAG_HIDE_ACTION_BAR = 1; - protected static final int FLAG_HIDE_STATUS_BAR = 2; - - private static final int MSG_HIDE_BARS = 1; - private static final int MSG_ON_FULL_SCREEN_CHANGED = 4; - private static final int MSG_UPDATE_ACTION_BAR = 5; - private static final int MSG_UNFREEZE_GLROOT = 6; - private static final int MSG_WANT_BARS = 7; - private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8; - private static final int UNFREEZE_GLROOT_TIMEOUT = 250; - - private ActionBar mActionBar; - - private GLView mContentPane; - private GLRootView mGLRootView; - private ProgressBar mProgress; - private ImageView mImageViewer; - private MenuBar mMenuBar; - - private PhotoView mPhotoView; - - private PhotoView.ITileImageAdapter mAdapter; - private Handler mHandler; - protected int mFlags; - - private boolean mShowBars = true; - private boolean mActionBarAllowed = true; - private boolean mLoaderInitialized; - - private long mContentLength; - private ThreadPool mThreadPool; - - private File mImageFile; - - public GLRoot getGLRoot() { - return mGLRootView; - } - - @Override - public int getThemeResourceId() { - return ThemeUtils.getViewerThemeResource(this); - } - - public ThreadPool getThreadPool() { - if (mThreadPool != null) return mThreadPool; - return mThreadPool = new ThreadPool(); - } - - public void hideProgress() { - mProgress.setVisibility(View.GONE); - mProgress.setProgress(0); - } - - @Override - public void onActionBarAllowed(final boolean allowed) { - mActionBarAllowed = allowed; - mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR); - } - - @Override - public void onActionBarWanted() { - mHandler.sendEmptyMessage(MSG_WANT_BARS); - } - - @Override - public void onContentChanged() { - super.onContentChanged(); - mGLRootView = (GLRootView) findViewById(R.id.gl_root_view); - mImageViewer = (ImageView) findViewById(R.id.image_viewer); - mProgress = (ProgressBar) findViewById(R.id.progress); - mMenuBar = (MenuBar) findViewById(R.id.menu_bar); - } - - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - mProgress.setVisibility(View.VISIBLE); - mProgress.setIndeterminate(true); - invalidateOptionsMenu(); - final Uri uri = args.getParcelable(EXTRA_URI); - final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1); - return new TileImageLoader(this, this, accountId, uri); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.menu_image_viewer_action_bar, menu); - return true; - } - - @Override - public void onCurrentImageUpdated() { - mGLRootView.unfreeze(); - } - - @Override - public void onDownloadError(final Throwable t) { - mContentLength = 0; - } - - @Override - public void onDownloadFinished() { - mContentLength = 0; - } - - @Override - public void onDownloadStart(final long total) { - mContentLength = total; - mProgress.setIndeterminate(total <= 0); - mProgress.setMax(total > 0 ? (int) (total / 1024) : 0); - } - - - @Override - public void onLoaderReset(final Loader loader) { - - } - - @Override - public void onLoadFinished(final Loader loader, final TileImageLoader.Result data) { - if (data != null && (data.decoder != null || data.bitmap != null)) { - if (data.decoder != null) { - mGLRootView.setVisibility(View.VISIBLE); - mImageViewer.setVisibility(View.GONE); - mAdapter.setData(data.decoder, data.bitmap, data.orientation); - mImageViewer.setImageBitmap(null); - } else if (data.bitmap != null) { - mGLRootView.setVisibility(View.GONE); - mImageViewer.setVisibility(View.VISIBLE); - mImageViewer.setImageBitmap(data.bitmap); - } - mImageFile = data.file; - } else { - mImageFile = null; - if (data != null) { - Utils.showErrorMessage(this, null, data.exception, true); - } - } - mProgress.setVisibility(View.GONE); - mProgress.setProgress(0); - invalidateOptionsMenu(); - updateShareIntent(); - } - - @Override - public boolean onMenuItemClick(final MenuItem item) { - switch (item.getItemId()) { - case MENU_SAVE: { - if (mImageFile != null) { - new SaveImageTask(this, mImageFile).execute(); - } - break; - } - case MENU_OPEN_IN_BROWSER: { - final Intent intent = getIntent(); - intent.setExtrasClassLoader(getClassLoader()); - final Uri uri = intent.getData(); - final Uri orig = intent.getParcelableExtra(EXTRA_URI_ORIG); - final Uri uriPreferred = orig != null ? orig : uri; - if (uriPreferred == null) return false; - final String scheme = uriPreferred.getScheme(); - if ("http".equals(scheme) || "https".equals(scheme)) { - final Intent open_intent = new Intent(Intent.ACTION_VIEW, uriPreferred); - open_intent.addCategory(Intent.CATEGORY_BROWSABLE); - try { - startActivity(open_intent); - } catch (final ActivityNotFoundException e) { - // Ignore. - } - } - break; - } - default: { - final Intent intent = item.getIntent(); - if (intent != null) { - try { - startActivity(intent); - } catch (final ActivityNotFoundException e) { - // Ignore. - } - return true; - } - return false; - } - } - return true; - } - - @Override - public void onMenuVisibilityChanged(final boolean isVisible) { - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case MENU_HOME: { - onBackPressed(); - break; - } - case MENU_REFRESH: { - loadImage(); - break; - } - } - return true; - } - - @Override - public void onPictureCenter() { - mPhotoView.setWantPictureCenterCallbacks(false); - } - - @Override - public boolean onPrepareOptionsMenu(final Menu menu) { - final LoaderManager lm = getSupportLoaderManager(); - Utils.setMenuItemAvailability(menu, MENU_REFRESH, !lm.hasRunningLoaders()); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public void onProgressUpdate(final long downloaded) { - if (mContentLength == 0) { - mProgress.setIndeterminate(true); - return; - } - mProgress.setIndeterminate(false); - mProgress.setProgress((int) (downloaded / 1024)); - } - - - @Override - public void onSingleTapUp(final int x, final int y) { - toggleBars(); - } - - public void showProgress() { - mProgress.setVisibility(View.VISIBLE); - mProgress.setIndeterminate(true); - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.image_viewer_gl); - mActionBar = getActionBar(); - mActionBar.setDisplayHomeAsUpEnabled(true); - mActionBar.addOnMenuVisibilityListener(this); - mHandler = new MyHandler(this); - mPhotoView = new PhotoView(this); - mPhotoView.setListener(this); - final int bgColor = ThemeUtils.getColorBackgroundCacheHint(this); - final int r = Color.red(bgColor), g = Color.green(bgColor), b = Color.blue(bgColor); - final float[] rootBg = {r / 255f, g / 255f, b / 255f, 1}; - mRootPane.setBackgroundColor(rootBg); - mRootPane.addComponent(mPhotoView); - mAdapter = new PhotoViewAdapter(mPhotoView); - mPhotoView.setModel(mAdapter); - if (savedInstanceState == null) { - loadImage(); - } - mMenuBar.setMenuBarListener(this); - mMenuBar.inflate(R.menu.menu_image_viewer); - mMenuBar.setIsBottomBar(true); - mMenuBar.show(); - } - - @Override - protected void onDestroy() { - mActionBar.removeOnMenuVisibilityListener(this); - super.onDestroy(); - mGLRootView.lockRenderThread(); - try { - // Remove all pending messages. - mHandler.removeCallbacksAndMessages(null); - } finally { - mGLRootView.unlockRenderThread(); - } - } - - @Override - protected void onNewIntent(final Intent intent) { - setIntent(intent); - loadImage(); - } - - @Override - protected void onPause() { - super.onPause(); - mGLRootView.onPause(); - mGLRootView.lockRenderThread(); - try { - mGLRootView.unfreeze(); - mHandler.removeMessages(MSG_UNFREEZE_GLROOT); - - if (mAdapter != null) { - mAdapter.recycleScreenNail(); - } - mPhotoView.pause(); - mHandler.removeMessages(MSG_HIDE_BARS); - mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS); - } finally { - mGLRootView.unlockRenderThread(); - } - - } - - @Override - protected void onResume() { - super.onResume(); - mGLRootView.lockRenderThread(); - try { - if (mAdapter == null) { - finish(); - return; - } - mGLRootView.freeze(); - setContentPane(mRootPane); - - mPhotoView.resume(); - if (!mShowBars) { - hideBars(); - } - mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT); - } finally { - mGLRootView.unlockRenderThread(); - } - mGLRootView.onResume(); - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - mGLRootView.lockRenderThread(); - try { - super.onSaveInstanceState(outState); - } finally { - mGLRootView.unlockRenderThread(); - } - } - - protected void setContentPane(final GLView content) { - mContentPane = content; - mContentPane.setBackgroundColor(GalleryUtils.intColorToFloatARGBArray(Color.BLACK)); - mGLRootView.setContentPane(mContentPane); - } - - private boolean canShowBars() { - // No bars if it's not allowed. - if (!mActionBarAllowed) return false; - return true; - } - - private void hideBars() { - if (!mShowBars) return; - mShowBars = false; - mActionBar.hide(); - final TranslateAnimation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1); - anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime)); - anim.setAnimationListener(new AnimationListener() { - - @Override - public void onAnimationEnd(final Animation animation) { - mMenuBar.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(final Animation animation) { - - } - - @Override - public void onAnimationStart(final Animation animation) { - - } - }); - mMenuBar.startAnimation(anim); - mHandler.removeMessages(MSG_HIDE_BARS); - } - - private void loadImage() { - getSupportLoaderManager().destroyLoader(0); - final Intent intent = getIntent(); - final Uri uri = intent.getData(); - final long accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1); - if (uri == null) { - finish(); - return; - } - final Bundle args = new Bundle(); - args.putParcelable(EXTRA_URI, uri); - args.putLong(EXTRA_ACCOUNT_ID, accountId); - if (!mLoaderInitialized) { - getSupportLoaderManager().initLoader(0, args, this); - mLoaderInitialized = true; - } else { - getSupportLoaderManager().restartLoader(0, args, this); - } - } - - private void showBars() { - if (mShowBars) return; - mShowBars = true; - mActionBar.show(); - mMenuBar.setVisibility(View.VISIBLE); - final TranslateAnimation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0); - anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime)); - mMenuBar.startAnimation(anim); - } - - private void toggleBars() { - if (mShowBars) { - hideBars(); - } else { - if (canShowBars()) { - showBars(); - } - } - } - - private void updateBars() { - if (!canShowBars()) { - hideBars(); - } - } - - private void wantBars() { - if (canShowBars()) { - showBars(); - } - } - - void updateShareIntent() { - final MenuItem item = mMenuBar.getMenu().findItem(MENU_SHARE); - if (item == null || !item.hasSubMenu()) return; - final SubMenu subMenu = item.getSubMenu(); - subMenu.clear(); - final Intent intent = getIntent(); - final Uri uri = intent.getData(); - final Intent shareIntent = new Intent(Intent.ACTION_SEND); - if (mImageFile != null && mImageFile.exists()) { - shareIntent.setType("image/*"); - shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mImageFile)); - } else { - shareIntent.setType("text/plain"); - shareIntent.putExtra(Intent.EXTRA_TEXT, uri.toString()); - } - Utils.addIntentToMenu(this, subMenu, shareIntent); - } - - @Override - public void onPreShowMenu(Menu menu) { - - } - - @Override - public void onPostShowMenu(Menu menu) { - - } - - private static class MyHandler extends SynchronizedHandler { - ImageViewerGLActivityOld activity; - - private MyHandler(final ImageViewerGLActivityOld activity) { - super(activity.getGLRoot()); - this.activity = activity; - } - - @Override - public void handleMessage(final Message message) { - switch (message.what) { - case MSG_HIDE_BARS: { - activity.hideBars(); - break; - } - case MSG_REFRESH_BOTTOM_CONTROLS: { - break; - } - case MSG_ON_FULL_SCREEN_CHANGED: { - break; - } - case MSG_UPDATE_ACTION_BAR: { - activity.updateBars(); - break; - } - case MSG_WANT_BARS: { - activity.wantBars(); - break; - } - case MSG_UNFREEZE_GLROOT: { - mGLRoot.unfreeze(); - break; - } - } - } - } - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/PhotoViewAdapter.java b/twidere/src/main/java/org/mariotaku/gallery3d/PhotoViewAdapter.java deleted file mode 100644 index e09fbb4ae..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/PhotoViewAdapter.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.util.Log; - -import org.mariotaku.gallery3d.ui.BitmapScreenNail; -import org.mariotaku.gallery3d.ui.PhotoView; -import org.mariotaku.gallery3d.ui.ScreenNail; -import org.mariotaku.gallery3d.util.ApiHelper; -import org.mariotaku.gallery3d.util.BitmapPool; -import org.mariotaku.gallery3d.util.GalleryUtils; - -public class PhotoViewAdapter implements PhotoView.ITileImageAdapter { - - private static final String TAG = "PhotoViewAdapter"; - - protected ScreenNail mScreenNail; - protected BitmapRegionDecoder mRegionDecoder; - protected int mImageWidth; - protected int mImageHeight; - protected int mLevelCount; - - private final PhotoView mPhotoView; - private BitmapScreenNail mBitmapScreenNail; - - private int mImageRotation; - - public PhotoViewAdapter(final PhotoView view) { - mPhotoView = view; - } - - @Override - public int getImageHeight() { - return mImageHeight; - } - - @Override - public int getImageRotation() { - return mImageRotation; - } - - @Override - public int getImageWidth() { - return mImageWidth; - } - - @Override - public int getLevelCount() { - return mLevelCount; - } - - @Override - public ScreenNail getScreenNail() { - return mScreenNail; - } - - // Gets a sub image on a rectangle of the current photo. For example, - // getTile(1, 50, 50, 100, 3, pool) means to get the region located - // at (50, 50) with sample level 1 (ie, down sampled by 2^1) and the - // target tile size (after sampling) 100 with border 3. - // - // From this spec, we can infer the actual tile size to be - // 100 + 3x2 = 106, and the size of the region to be extracted from the - // photo to be 200 with border 6. - // - // As a result, we should decode region (50-6, 50-6, 250+6, 250+6) or - // (44, 44, 256, 256) from the original photo and down sample it to 106. - @Override - public Bitmap getTile(final int level, final int x, final int y, final int tileSize, final int borderSize, - final BitmapPool pool) { - if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) - return getTileWithoutReusingBitmap(level, x, y, tileSize, borderSize); - - final int b = borderSize << level; - final int t = tileSize << level; - - final Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b); - - boolean needClear; - BitmapRegionDecoder regionDecoder = null; - - synchronized (this) { - regionDecoder = mRegionDecoder; - if (regionDecoder == null) return null; - - // We need to clear a reused bitmap, if wantRegion is not fully - // within the image. - needClear = !new Rect(0, 0, mImageWidth, mImageHeight).contains(wantRegion); - } - - Bitmap bitmap = pool == null ? null : pool.getBitmap(); - if (bitmap != null) { - if (needClear) { - bitmap.eraseColor(0); - } - } else { - final int s = tileSize + 2 * borderSize; - bitmap = Bitmap.createBitmap(s, s, Config.RGB_565); - } - - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Config.RGB_565; - options.inPreferQualityOverSpeed = true; - options.inSampleSize = 1 << level; - options.inBitmap = bitmap; - - try { - // In CropImage, we may call the decodeRegion() concurrently. - synchronized (regionDecoder) { - bitmap = regionDecoder.decodeRegion(wantRegion, options); - } - } finally { - if (options.inBitmap != bitmap && options.inBitmap != null) { - if (pool != null) { - pool.recycle(options.inBitmap); - } - options.inBitmap = null; - } - } - - if (bitmap == null) { - Log.w(TAG, "fail in decoding region"); - } - return bitmap; - } - - @Override - public void recycleScreenNail() { - if (mBitmapScreenNail != null) { - mBitmapScreenNail.recycle(); - mBitmapScreenNail = null; - } - } - - @Override - public boolean setData(final BitmapRegionDecoder decoder, final Bitmap bitmap, final int oroentation) { - try { - if (decoder != null) { - setScreenNail(bitmap, decoder.getWidth(), decoder.getHeight()); - } else { - if (bitmap == null) return false; - setScreenNail(bitmap, bitmap.getWidth(), bitmap.getHeight()); - } - setRegionDecoder(decoder); - mPhotoView.notifyImageChange(); - return true; - } catch (final Throwable t) { - Log.w(TAG, "fail to decode large", t); - return false; - } - } - - private int calculateLevelCount() { - return Math.max(0, GalleryUtils.ceilLog2((float) mImageWidth / mScreenNail.getWidth())); - } - - private Bitmap getTileWithoutReusingBitmap(final int level, final int x, final int y, final int tileSize, - final int borderSize) { - final int b = borderSize << level; - final int t = tileSize << level; - final Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b); - - BitmapRegionDecoder regionDecoder; - Rect overlapRegion; - - synchronized (this) { - regionDecoder = mRegionDecoder; - if (regionDecoder == null) return null; - overlapRegion = new Rect(0, 0, mImageWidth, mImageHeight); - GalleryUtils.assertTrue(overlapRegion.intersect(wantRegion)); - } - - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Config.RGB_565; - options.inPreferQualityOverSpeed = true; - options.inSampleSize = 1 << level; - Bitmap bitmap = null; - - // In CropImage, we may call the decodeRegion() concurrently. - synchronized (regionDecoder) { - bitmap = regionDecoder.decodeRegion(overlapRegion, options); - } - - if (bitmap == null) { - Log.w(TAG, "fail in decoding region"); - } - - if (wantRegion.equals(overlapRegion)) return bitmap; - - final int s = tileSize + 2 * borderSize; - final Bitmap result = Bitmap.createBitmap(s, s, Config.RGB_565); - final Canvas canvas = new Canvas(result); - canvas.drawBitmap(bitmap, overlapRegion.left - wantRegion.left >> level, - overlapRegion.top - wantRegion.top >> level, null); - return result; - } - - private synchronized void setRegionDecoder(final BitmapRegionDecoder decoder) { - mRegionDecoder = decoder; - if (decoder == null) return; - mImageWidth = decoder.getWidth(); - mImageHeight = decoder.getHeight(); - mLevelCount = calculateLevelCount(); - } - - private void setScreenNail(final Bitmap bitmap, final int width, final int height) { - mBitmapScreenNail = new BitmapScreenNail(bitmap); - setScreenNail(mBitmapScreenNail, width, height); - } - - // Caller is responsible to recycle the ScreenNail - private synchronized void setScreenNail(final ScreenNail screenNail, final int width, final int height) { - mScreenNail = GalleryUtils.checkNotNull(screenNail); - mImageWidth = width; - mImageHeight = height; - mRegionDecoder = null; - mLevelCount = 0; - } - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/anim/Animation.java b/twidere/src/main/java/org/mariotaku/gallery3d/anim/Animation.java deleted file mode 100644 index 5dbf6fcfc..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/anim/Animation.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.anim; - -import android.view.animation.Interpolator; - -import org.mariotaku.gallery3d.util.GalleryUtils; - -// Animation calculates a value according to the current input time. -// -// 1. First we need to use setDuration(int) to set the duration of the -// animation. The duration is in milliseconds. -// 2. Then we should call start(). The actual start time is the first value -// passed to calculate(long). -// 3. Each time we want to get an animation value, we call -// calculate(long currentTimeMillis) to ask the Animation to calculate it. -// The parameter passed to calculate(long) should be nonnegative. -// 4. Use get() to get that value. -// -// In step 3, onCalculate(float progress) is called so subclasses can calculate -// the value according to progress (progress is a value in [0,1]). -// -// Before onCalculate(float) is called, There is an optional interpolator which -// can change the progress value. The interpolator can be set by -// setInterpolator(Interpolator). If the interpolator is used, the value passed -// to onCalculate may be (for example, the overshoot effect). -// -// The isActive() method returns true after the animation start() is called and -// before calculate is passed a value which reaches the duration of the -// animation. -// -// The start() method can be called again to restart the Animation. -// -abstract public class Animation { - private static final long ANIMATION_START = -1; - private static final long NO_ANIMATION = -2; - - private long mStartTime = NO_ANIMATION; - private int mDuration; - private Interpolator mInterpolator; - - public boolean calculate(final long currentTimeMillis) { - if (mStartTime == NO_ANIMATION) return false; - if (mStartTime == ANIMATION_START) { - mStartTime = currentTimeMillis; - } - final int elapse = (int) (currentTimeMillis - mStartTime); - final float x = GalleryUtils.clamp((float) elapse / mDuration, 0f, 1f); - final Interpolator i = mInterpolator; - onCalculate(i != null ? i.getInterpolation(x) : x); - if (elapse >= mDuration) { - mStartTime = NO_ANIMATION; - } - return mStartTime != NO_ANIMATION; - } - - public boolean isActive() { - return mStartTime != NO_ANIMATION; - } - - public void setDuration(final int duration) { - mDuration = duration; - } - - public void setInterpolator(final Interpolator interpolator) { - mInterpolator = interpolator; - } - - public void setStartTime(final long time) { - mStartTime = time; - } - - abstract protected void onCalculate(float progress); -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/anim/CanvasAnimation.java b/twidere/src/main/java/org/mariotaku/gallery3d/anim/CanvasAnimation.java deleted file mode 100644 index 1e421ac62..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/anim/CanvasAnimation.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.anim; - -import org.mariotaku.gallery3d.ui.GLCanvas; - -public abstract class CanvasAnimation extends Animation { - - public abstract void apply(GLCanvas canvas); - - public abstract int getCanvasSaveFlags(); -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/AnimationTime.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/AnimationTime.java deleted file mode 100644 index 5d5fe68fa..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/AnimationTime.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.os.SystemClock; - -// -// The animation time should ideally be the vsync time the frame will be -// displayed, but that is an unknown time in the future. So we use the system -// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called) -// as a approximation. -// -public class AnimationTime { - private static volatile long sTime; - - // Returns the animation time. - public static long get() { - return sTime; - } - - public static long startTime() { - sTime = SystemClock.uptimeMillis(); - return sTime; - } - - // Sets current time as the animation time. - public static void update() { - sTime = SystemClock.uptimeMillis(); - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java deleted file mode 100644 index 55e796ed6..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.util.Log; - -import org.mariotaku.twidere.util.MathUtils; - -import java.util.WeakHashMap; - -// BasicTexture is a Texture corresponds to a real GL texture. -// The state of a BasicTexture indicates whether its data is loaded to GL memory. -// If a BasicTexture is loaded into GL memory, it has a GL texture id. -abstract class BasicTexture implements Texture { - - private static final String TAG = "BasicTexture"; - protected static final int UNSPECIFIED = -1; - - protected static final int STATE_UNLOADED = 0; - protected static final int STATE_LOADED = 1; - protected static final int STATE_ERROR = -1; - - // Log a warning if a texture is larger along a dimension - private static final int MAX_TEXTURE_SIZE = 4096; - - protected int mId; - protected int mState; - - protected int mWidth = UNSPECIFIED; - protected int mHeight = UNSPECIFIED; - - protected int mTextureWidth; - protected int mTextureHeight; - - private boolean mHasBorder; - - protected GLCanvas mCanvasRef = null; - private static WeakHashMap sAllTextures = new WeakHashMap(); - private static ThreadLocal> sInFinalizer = new ThreadLocal>(); - - protected BasicTexture() { - this(null, 0, STATE_UNLOADED); - } - - protected BasicTexture(final GLCanvas canvas, final int id, final int state) { - setAssociatedCanvas(canvas); - mId = id; - mState = state; - synchronized (sAllTextures) { - sAllTextures.put(this, null); - } - } - - @Override - public void draw(final GLCanvas canvas, final int x, final int y) { - canvas.drawTexture(this, x, y, getWidth(), getHeight()); - } - - @Override - public void draw(final GLCanvas canvas, final int x, final int y, final int w, final int h) { - canvas.drawTexture(this, x, y, w, h); - } - - @Override - public int getHeight() { - return mHeight; - } - - public int getId() { - return mId; - } - - // Returns the height rounded to the next power of 2. - public int getTextureHeight() { - return mTextureHeight; - } - - // Returns the width rounded to the next power of 2. - public int getTextureWidth() { - return mTextureWidth; - } - - @Override - public int getWidth() { - return mWidth; - } - - // Returns true if the texture has one pixel transparent border around the - // actual content. This is used to avoid jigged edges. - // - // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap - // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially - // covered by the texture will use the color of the edge texel. If we add - // the transparent border, the color of the edge texel will be mixed with - // appropriate amount of transparent. - // - // Currently our background is black, so we can draw the thumbnails without - // enabling blending. - public boolean hasBorder() { - return mHasBorder; - } - - public boolean isLoaded() { - return mState == STATE_LOADED; - } - - // recycle() is called when the texture will never be used again, - // so it can free all resources. - public void recycle() { - freeResource(); - } - - @Override - protected void finalize() { - sInFinalizer.set(BasicTexture.class); - recycle(); - sInFinalizer.set(null); - } - - // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D). - abstract protected int getTarget(); - - // onBind is called before GLCanvas binds this texture. - // It should make sure the data is uploaded to GL memory. - abstract protected boolean onBind(GLCanvas canvas); - - protected void setAssociatedCanvas(final GLCanvas canvas) { - mCanvasRef = canvas; - } - - protected void setBorder(final boolean hasBorder) { - mHasBorder = hasBorder; - } - - /** - * Sets the content size of this texture. In OpenGL, the actual texture size - * must be of power of 2, the size of the content may be smaller. - */ - protected void setSize(final int width, final int height) { - mWidth = width; - mHeight = height; - mTextureWidth = MathUtils.nextPowerOf2(width); - mTextureHeight = MathUtils.nextPowerOf2(height); - if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) { - Log.w(TAG, String.format("texture is too large: %d x %d", mTextureWidth, mTextureHeight), new Exception()); - } - } - - private void freeResource() { - final GLCanvas canvas = mCanvasRef; - if (canvas != null && isLoaded()) { - canvas.unloadTexture(this); - } - mState = STATE_UNLOADED; - setAssociatedCanvas(null); - } - - // yield() is called when the texture will not be used temporarily, - // so it can free some resources. - // The default implementation unloads the texture from GL memory, so - // the subclass should make sure it can reload the texture to GL memory - // later, or it will have to override this method. - private void yield() { - freeResource(); - } - - // This is for deciding if we can call Bitmap's recycle(). - // We cannot call Bitmap's recycle() in finalizer because at that point - // the finalizer of Bitmap may already be called so recycle() will crash. - public static boolean inFinalizer() { - return sInFinalizer.get() != null; - } - - public static void invalidateAllTextures() { - synchronized (sAllTextures) { - for (final BasicTexture t : sAllTextures.keySet()) { - t.mState = STATE_UNLOADED; - t.setAssociatedCanvas(null); - } - } - } - - public static void yieldAllTextures() { - synchronized (sAllTextures) { - for (final BasicTexture t : sAllTextures.keySet()) { - t.yield(); - } - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapScreenNail.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapScreenNail.java deleted file mode 100644 index d5dcbf4e9..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapScreenNail.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.Bitmap; -import android.graphics.RectF; - -public class BitmapScreenNail implements ScreenNail { - private final BitmapTexture mBitmapTexture; - - public BitmapScreenNail(final Bitmap bitmap) { - mBitmapTexture = new BitmapTexture(bitmap); - } - - @Override - public void draw(final GLCanvas canvas, final int x, final int y, final int width, final int height) { - mBitmapTexture.draw(canvas, x, y, width, height); - } - - @Override - public void draw(final GLCanvas canvas, final RectF source, final RectF dest) { - canvas.drawTexture(mBitmapTexture, source, dest); - } - - @Override - public int getHeight() { - return mBitmapTexture.getHeight(); - } - - @Override - public int getWidth() { - return mBitmapTexture.getWidth(); - } - - @Override - public void noDraw() { - // do nothing - } - - @Override - public void recycle() { - mBitmapTexture.recycle(); - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapTexture.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapTexture.java deleted file mode 100644 index d3d000aa4..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapTexture.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.Bitmap; - -import org.mariotaku.gallery3d.util.GalleryUtils; - -// BitmapTexture is a texture whose content is specified by a fixed Bitmap. -// -// The texture does not own the Bitmap. The user should make sure the Bitmap -// is valid during the texture's lifetime. When the texture is recycled, it -// does not free the Bitmap. -public class BitmapTexture extends UploadedTexture { - protected Bitmap mContentBitmap; - - public BitmapTexture(final Bitmap bitmap) { - this(bitmap, false); - } - - private BitmapTexture(final Bitmap bitmap, final boolean hasBorder) { - super(hasBorder); - GalleryUtils.assertTrue(bitmap != null && !bitmap.isRecycled()); - mContentBitmap = bitmap; - } - - public Bitmap getBitmap() { - return mContentBitmap; - } - - @Override - protected void onFreeBitmap(final Bitmap bitmap) { - // Do nothing. - } - - @Override - protected Bitmap onGetBitmap() { - return mContentBitmap; - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/DownUpDetector.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/DownUpDetector.java deleted file mode 100644 index e3096b4de..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/DownUpDetector.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.view.MotionEvent; - -public class DownUpDetector { - private boolean mStillDown; - - private final DownUpListener mListener; - - public DownUpDetector(final DownUpListener listener) { - mListener = listener; - } - - public boolean isDown() { - return mStillDown; - } - - public void onTouchEvent(final MotionEvent ev) { - switch (ev.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: - setState(true, ev); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_POINTER_DOWN: // Multitouch event - abort. - setState(false, ev); - break; - } - } - - private void setState(final boolean down, final MotionEvent e) { - if (down == mStillDown) return; - mStillDown = down; - if (down) { - mListener.onDown(e); - } else { - mListener.onUp(e); - } - } - - public interface DownUpListener { - void onDown(MotionEvent e); - - void onUp(MotionEvent e); - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeEffect.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeEffect.java deleted file mode 100644 index 5f9a6d15f..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeEffect.java +++ /dev/null @@ -1,451 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; - -import org.mariotaku.twidere.R; -import org.mariotaku.twidere.util.ThemeUtils; - -// This is copied from android.widget.EdgeEffect with some small modifications: -// (1) Copy the images (overscroll_{edge|glow}.png) to local resources. -// (2) Use "GLCanvas" instead of "Canvas" for draw()'s parameter. -// (3) Use a private Drawable class (which inherits from ResourceTexture) -// instead of android.graphics.drawable.Drawable to hold the images. -// The private Drawable class is used to translate original Canvas calls to -// corresponding GLCanvas calls. - -/** - * This class performs the graphical effect used at the edges of scrollable - * widgets when the user scrolls beyond the content bounds in 2D space. - *

- * EdgeEffect is stateful. Custom widgets using EdgeEffect should create an - * instance for each edge that should show the effect, feed it input data using - * the methods {@link #onAbsorb(int)}, {@link #onPull(float)}, and - * {@link #onRelease()}, and draw the effect using {@link #draw(Canvas)} in the - * widget's overridden {@link android.view.View#draw(Canvas)} method. If - * {@link #isFinished()} returns false after drawing, the edge effect's - * animation is not yet complete and the widget should schedule another drawing - * pass to continue the animation. - *

- *

- * When drawing, widgets should draw their main content and child views first, - * usually by invoking super.draw(canvas) from an overridden - * draw method. (This will invoke onDraw and dispatch drawing to - * child views as needed.) The edge effect may then be drawn on top of the - * view's content using the {@link #draw(Canvas)} method. - *

- */ -public class EdgeEffect { - @SuppressWarnings("unused") - private static final String TAG = "EdgeEffect"; - - // Time it will take the effect to fully recede in ms - private static final int RECEDE_TIME = 1000; - - // Time it will take before a pulled glow begins receding in ms - private static final int PULL_TIME = 167; - - // Time it will take in ms for a pulled glow to decay to partial strength - // before release - private static final int PULL_DECAY_TIME = 1000; - - private static final float MAX_ALPHA = 0.8f; - private static final float HELD_EDGE_SCALE_Y = 0.5f; - private static final float MAX_GLOW_HEIGHT = 4.f; - - private static final float PULL_GLOW_BEGIN = 1.f; - private static final float PULL_EDGE_BEGIN = 0.6f; - - // Minimum velocity that will be absorbed - private static final int MIN_VELOCITY = 100; - - private static final float EPSILON = 0.001f; - - private final Drawable mEdge; - private final Drawable mGlow; - private int mWidth; - private final int MIN_WIDTH = 300; - private final int mMinWidth; - - private float mEdgeAlpha; - private float mEdgeScaleY; - private float mGlowAlpha; - private float mGlowScaleY; - - private float mEdgeAlphaStart; - private float mEdgeAlphaFinish; - private float mEdgeScaleYStart; - private float mEdgeScaleYFinish; - private float mGlowAlphaStart; - private float mGlowAlphaFinish; - private float mGlowScaleYStart; - private float mGlowScaleYFinish; - - private long mStartTime; - private float mDuration; - - private final Interpolator mInterpolator; - - private static final int STATE_IDLE = 0; - private static final int STATE_PULL = 1; - private static final int STATE_ABSORB = 2; - private static final int STATE_RECEDE = 3; - private static final int STATE_PULL_DECAY = 4; - - // How much dragging should effect the height of the edge image. - // Number determined by user testing. - private static final int PULL_DISTANCE_EDGE_FACTOR = 7; - - // How much dragging should effect the height of the glow image. - // Number determined by user testing. - private static final int PULL_DISTANCE_GLOW_FACTOR = 7; - private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 1.1f; - - private static final int VELOCITY_EDGE_FACTOR = 8; - private static final int VELOCITY_GLOW_FACTOR = 16; - - private int mState = STATE_IDLE; - - private float mPullDistance; - - /** - * Construct a new EdgeEffect with a theme appropriate for the provided - * context. - * - * @param context Context used to provide theming and resource information - * for the EdgeEffect - */ - public EdgeEffect(final Context context) { - final int theme_color = ThemeUtils.getUserAccentColor(context); - mEdge = new Drawable(context, R.drawable.overscroll_edge); - mEdge.setColorFilter(theme_color, PorterDuff.Mode.SRC_ATOP); - mGlow = new Drawable(context, R.drawable.overscroll_glow); - mGlow.setColorFilter(theme_color, PorterDuff.Mode.SRC_ATOP); - mMinWidth = (int) (context.getResources().getDisplayMetrics().density * MIN_WIDTH + 0.5f); - mInterpolator = new DecelerateInterpolator(); - } - - /** - * Draw into the provided canvas. Assumes that the canvas has been rotated - * accordingly and the size has been set. The effect will be drawn the full - * width of X=0 to X=width, beginning from Y=0 and extending to some factor - * < 1.f of height. - * - * @param canvas Canvas to draw into - * @return true if drawing should continue beyond this frame to continue the - * animation - */ - public boolean draw(final GLCanvas canvas) { - update(); - - final int edgeHeight = mEdge.getIntrinsicHeight(); - mEdge.getIntrinsicWidth(); - final int glowHeight = mGlow.getIntrinsicHeight(); - final int glowWidth = mGlow.getIntrinsicWidth(); - - mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255)); - - final int glowBottom = (int) Math.min(glowHeight * mGlowScaleY * glowHeight / glowWidth * 0.6f, glowHeight - * MAX_GLOW_HEIGHT); - if (mWidth < mMinWidth) { - // Center the glow and clip it. - final int glowLeft = (mWidth - mMinWidth) / 2; - mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, glowBottom); - } else { - // Stretch the glow to fit. - mGlow.setBounds(0, 0, mWidth, glowBottom); - } - - mGlow.draw(canvas); - - mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255)); - - final int edgeBottom = (int) (edgeHeight * mEdgeScaleY); - if (mWidth < mMinWidth) { - // Center the edge and clip it. - final int edgeLeft = (mWidth - mMinWidth) / 2; - mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, edgeBottom); - } else { - // Stretch the edge to fit. - mEdge.setBounds(0, 0, mWidth, edgeBottom); - } - mEdge.draw(canvas); - - return mState != STATE_IDLE; - } - - /** - * Reports if this EdgeEffect's animation is finished. If this method - * returns false after a call to {@link #draw(Canvas)} the host widget - * should schedule another drawing pass to continue the animation. - * - * @return true if animation is finished, false if drawing should continue - * on the next frame. - */ - public boolean isFinished() { - return mState == STATE_IDLE; - } - - /** - * Call when the effect absorbs an impact at the given velocity. Used when a - * fling reaches the scroll boundary. - *

- * When using a {@link android.widget.Scroller} or - * {@link android.widget.OverScroller}, the method - * getCurrVelocity will provide a reasonable approximation to - * use here. - *

- * - * @param velocity Velocity at impact in pixels per second. - */ - public void onAbsorb(int velocity) { - mState = STATE_ABSORB; - velocity = Math.max(MIN_VELOCITY, Math.abs(velocity)); - - mStartTime = AnimationTime.get(); - mDuration = 0.1f + velocity * 0.03f; - - // The edge should always be at least partially visible, regardless - // of velocity. - mEdgeAlphaStart = 0.f; - mEdgeScaleY = mEdgeScaleYStart = 0.f; - // The glow depends more on the velocity, and therefore starts out - // nearly invisible. - mGlowAlphaStart = 0.5f; - mGlowScaleYStart = 0.f; - - // Factor the velocity by 8. Testing on device shows this works best to - // reflect the strength of the user's scrolling. - mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1)); - // Edge should never get larger than the size of its asset. - mEdgeScaleYFinish = Math.max(HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f)); - - // Growth for the size of the glow should be quadratic to properly - // respond - // to a user's scrolling speed. The faster the scrolling speed, the more - // intense the effect should be for both the size and the saturation. - mGlowScaleYFinish = Math.min(0.025f + velocity * (velocity / 100) * 0.00015f, 1.75f); - // Alpha should change for the glow as well as size. - mGlowAlphaFinish = Math.max(mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); - } - - /** - * A view should call this when content is pulled away from an edge by the - * user. This will update the state of the current visual effect and its - * associated animation. The host view should always - * {@link android.view.View#invalidate()} after this and draw the results - * accordingly. - * - * @param deltaDistance Change in distance since the last call. Values may - * be 0 (no change) to 1.f (full length of the view) or negative - * values to express change back toward the edge reached to - * initiate the effect. - */ - public void onPull(final float deltaDistance) { - final long now = AnimationTime.get(); - if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) return; - if (mState != STATE_PULL) { - mGlowScaleY = PULL_GLOW_BEGIN; - } - mState = STATE_PULL; - - mStartTime = now; - mDuration = PULL_TIME; - - mPullDistance += deltaDistance; - final float distance = Math.abs(mPullDistance); - - mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA)); - mEdgeScaleY = mEdgeScaleYStart = Math.max(HELD_EDGE_SCALE_Y, - Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f)); - - mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, mGlowAlpha + Math.abs(deltaDistance) - * PULL_DISTANCE_ALPHA_GLOW_FACTOR); - - float glowChange = Math.abs(deltaDistance); - if (deltaDistance > 0 && mPullDistance < 0) { - glowChange = -glowChange; - } - if (mPullDistance == 0) { - mGlowScaleY = 0; - } - - // Do not allow glow to get larger than MAX_GLOW_HEIGHT. - mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, - Math.max(0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR)); - - mEdgeAlphaFinish = mEdgeAlpha; - mEdgeScaleYFinish = mEdgeScaleY; - mGlowAlphaFinish = mGlowAlpha; - mGlowScaleYFinish = mGlowScaleY; - } - - /** - * Call when the object is released after being pulled. This will begin the - * "decay" phase of the effect. After calling this method the host view - * should {@link android.view.View#invalidate()} and thereby draw the - * results accordingly. - */ - public void onRelease() { - mPullDistance = 0; - - if (mState != STATE_PULL && mState != STATE_PULL_DECAY) return; - - mState = STATE_RECEDE; - mEdgeAlphaStart = mEdgeAlpha; - mEdgeScaleYStart = mEdgeScaleY; - mGlowAlphaStart = mGlowAlpha; - mGlowScaleYStart = mGlowScaleY; - - mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.f; - mGlowAlphaFinish = 0.f; - mGlowScaleYFinish = 0.f; - - mStartTime = AnimationTime.get(); - mDuration = RECEDE_TIME; - } - - /** - * Set the size of this edge effect in pixels. - * - * @param width Effect width in pixels - * @param height Effect height in pixels - */ - public void setSize(final int width, final int height) { - mWidth = width; - } - - private void update() { - final long time = AnimationTime.get(); - final float t = Math.min((time - mStartTime) / mDuration, 1.f); - - final float interp = mInterpolator.getInterpolation(t); - - mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp; - mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp; - mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; - mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; - - if (t >= 1.f - EPSILON) { - switch (mState) { - case STATE_ABSORB: - mState = STATE_RECEDE; - mStartTime = AnimationTime.get(); - mDuration = RECEDE_TIME; - - mEdgeAlphaStart = mEdgeAlpha; - mEdgeScaleYStart = mEdgeScaleY; - mGlowAlphaStart = mGlowAlpha; - mGlowScaleYStart = mGlowScaleY; - - // After absorb, the glow and edge should fade to nothing. - mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.f; - mGlowAlphaFinish = 0.f; - mGlowScaleYFinish = 0.f; - break; - case STATE_PULL: - mState = STATE_PULL_DECAY; - mStartTime = AnimationTime.get(); - mDuration = PULL_DECAY_TIME; - - mEdgeAlphaStart = mEdgeAlpha; - mEdgeScaleYStart = mEdgeScaleY; - mGlowAlphaStart = mGlowAlpha; - mGlowScaleYStart = mGlowScaleY; - - // After pull, the glow and edge should fade to nothing. - mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.f; - mGlowAlphaFinish = 0.f; - mGlowScaleYFinish = 0.f; - break; - case STATE_PULL_DECAY: - // When receding, we want edge to decrease more slowly - // than the glow. - final float factor = mGlowScaleYFinish != 0 ? 1 / (mGlowScaleYFinish * mGlowScaleYFinish) - : Float.MAX_VALUE; - mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp * factor; - mState = STATE_RECEDE; - break; - case STATE_RECEDE: - mState = STATE_IDLE; - break; - } - } - } - - private static class Drawable extends ResourceTexture { - private final Rect mBounds = new Rect(); - private int mAlpha = 255; - - private final Paint mPaint = new Paint(); - - public Drawable(final Context context, final int resId) { - super(context, resId); - } - - public void draw(final GLCanvas canvas) { - canvas.save(GLCanvas.SAVE_FLAG_ALPHA); - canvas.multiplyAlpha(mAlpha / 255.0f); - final Rect b = mBounds; - draw(canvas, b.left, b.top, b.width(), b.height()); - canvas.restore(); - } - - public int getIntrinsicHeight() { - return getHeight(); - } - - public int getIntrinsicWidth() { - return getWidth(); - } - - public void setAlpha(final int alpha) { - mAlpha = alpha; - } - - public void setBounds(final int left, final int top, final int right, final int bottom) { - mBounds.set(left, top, right, bottom); - } - - public void setColorFilter(final int color, final PorterDuff.Mode mode) { - mPaint.setColorFilter(new PorterDuffColorFilter(color, mode)); - } - - @Override - protected Bitmap onGetBitmap() { - final Bitmap orig = super.onGetBitmap(); - if (mPaint.getColorFilter() == null) return orig; - final Bitmap bitmap = Bitmap.createBitmap(orig.getWidth(), orig.getHeight(), orig.getConfig()); - final Canvas c = new Canvas(bitmap); - c.drawBitmap(orig, 0, 0, mPaint); - orig.recycle(); - return bitmap; - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeView.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeView.java deleted file mode 100644 index 4ca2f2291..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeView.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.content.Context; -import android.opengl.Matrix; - -// EdgeView draws EdgeEffect (blue glow) at four sides of the view. -public class EdgeView extends GLView { - @SuppressWarnings("unused") - private static final String TAG = "EdgeView"; - - public static final int TOP = 0; - public static final int LEFT = 1; - public static final int BOTTOM = 2; - public static final int RIGHT = 3; - - // Each edge effect has a transform matrix, and each matrix has 16 elements. - // We put all the elements in one array. These constants specify the - // starting index of each matrix. - private static final int TOP_M = TOP * 16; - private static final int LEFT_M = LEFT * 16; - private static final int BOTTOM_M = BOTTOM * 16; - private static final int RIGHT_M = RIGHT * 16; - - private final EdgeEffect[] mEffect = new EdgeEffect[4]; - private final float[] mMatrix = new float[4 * 16]; - - public EdgeView(final Context context) { - for (int i = 0; i < 4; i++) { - mEffect[i] = new EdgeEffect(context); - } - } - - // Call when the effect absorbs an impact at the given velocity. - // Used when a fling reaches the scroll boundary. velocity is in pixels - // per second. direction is one of {TOP, LEFT, BOTTOM, RIGHT}. - public void onAbsorb(final int velocity, final int direction) { - mEffect[direction].onAbsorb(velocity); - if (!mEffect[direction].isFinished()) { - invalidate(); - } - } - - // Called when the content is pulled away from the edge. - // offset is in pixels. direction is one of {TOP, LEFT, BOTTOM, RIGHT}. - public void onPull(final int offset, final int direction) { - final int fullLength = (direction & 1) == 0 ? getWidth() : getHeight(); - mEffect[direction].onPull((float) offset / fullLength); - if (!mEffect[direction].isFinished()) { - invalidate(); - } - } - - // Call when the object is released after being pulled. - public void onRelease() { - boolean more = false; - for (int i = 0; i < 4; i++) { - mEffect[i].onRelease(); - more |= !mEffect[i].isFinished(); - } - if (more) { - invalidate(); - } - } - - @Override - protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { - if (!changeSize) return; - - final int w = right - left; - final int h = bottom - top; - for (int i = 0; i < 4; i++) { - if ((i & 1) == 0) { // top or bottom - mEffect[i].setSize(w, h); - } else { // left or right - mEffect[i].setSize(h, w); - } - } - - // Set up transforms for the four edges. Without transforms an - // EdgeEffect draws the TOP edge from (0, 0) to (w, Y * h) where Y - // is some factor < 1. For other edges we need to move, rotate, and - // flip the effects into proper places. - Matrix.setIdentityM(mMatrix, TOP_M); - Matrix.setIdentityM(mMatrix, LEFT_M); - Matrix.setIdentityM(mMatrix, BOTTOM_M); - Matrix.setIdentityM(mMatrix, RIGHT_M); - - Matrix.rotateM(mMatrix, LEFT_M, 90, 0, 0, 1); - Matrix.scaleM(mMatrix, LEFT_M, 1, -1, 1); - - Matrix.translateM(mMatrix, BOTTOM_M, 0, h, 0); - Matrix.scaleM(mMatrix, BOTTOM_M, 1, -1, 1); - - Matrix.translateM(mMatrix, RIGHT_M, w, 0, 0); - Matrix.rotateM(mMatrix, RIGHT_M, 90, 0, 0, 1); - } - - @Override - protected void render(final GLCanvas canvas) { - super.render(canvas); - boolean more = false; - for (int i = 0; i < 4; i++) { - canvas.save(GLCanvas.SAVE_FLAG_MATRIX); - canvas.multiplyMatrix(mMatrix, i * 16); - more |= mEffect[i].draw(canvas); - canvas.restore(); - } - if (more) { - invalidate(); - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/FlingScroller.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/FlingScroller.java deleted file mode 100644 index 3ffd3c39e..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/FlingScroller.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -// This is a customized version of Scroller, with a interface similar to -// android.widget.Scroller. It does fling only, not scroll. -// -// The differences between the this Scroller and the system one are: -// -// (1) The velocity does not change because of min/max limit. -// (2) The duration is different. -// (3) The deceleration curve is different. -class FlingScroller { - @SuppressWarnings("unused") - private static final String TAG = "FlingController"; - - // The fling duration (in milliseconds) when velocity is 1 pixel/second - private static final float FLING_DURATION_PARAM = 50f; - private static final int DECELERATED_FACTOR = 4; - - private int mStartX, mStartY; - private int mMinX, mMinY, mMaxX, mMaxY; - private double mSinAngle; - private double mCosAngle; - private int mDuration; - private int mDistance; - private int mFinalX, mFinalY; - - private int mCurrX, mCurrY; - private double mCurrV; - - public void computeScrollOffset(float progress) { - progress = Math.min(progress, 1); - float f = 1 - progress; - f = 1 - (float) Math.pow(f, DECELERATED_FACTOR); - mCurrX = getX(f); - mCurrY = getY(f); - mCurrV = getV(progress); - } - - public void fling(final int startX, final int startY, final int velocityX, final int velocityY, final int minX, - final int maxX, final int minY, final int maxY) { - mStartX = startX; - mStartY = startY; - mMinX = minX; - mMinY = minY; - mMaxX = maxX; - mMaxY = maxY; - - final double velocity = Math.hypot(velocityX, velocityY); - mSinAngle = velocityY / velocity; - mCosAngle = velocityX / velocity; - // - // The position formula: x(t) = s + (e - s) * (1 - (1 - t / T) ^ d) - // velocity formula: v(t) = d * (e - s) * (1 - t / T) ^ (d - 1) / T - // Thus, - // v0 = d * (e - s) / T => (e - s) = v0 * T / d - // - - // Ta = T_ref * (Va / V_ref) ^ (1 / (d - 1)); V_ref = 1 pixel/second; - mDuration = (int) Math.round(FLING_DURATION_PARAM - * Math.pow(Math.abs(velocity), 1.0 / (DECELERATED_FACTOR - 1))); - - // (e - s) = v0 * T / d - mDistance = (int) Math.round(velocity * mDuration / DECELERATED_FACTOR / 1000); - - mFinalX = getX(1.0f); - mFinalY = getY(1.0f); - } - - public int getCurrVelocityX() { - return (int) Math.round(mCurrV * mCosAngle); - } - - public int getCurrVelocityY() { - return (int) Math.round(mCurrV * mSinAngle); - } - - public int getCurrX() { - return mCurrX; - - } - - public int getCurrY() { - return mCurrY; - } - - public int getDuration() { - return mDuration; - } - - public int getFinalX() { - return mFinalX; - } - - public int getFinalY() { - return mFinalY; - } - - private double getV(final float progress) { - // velocity formula: v(t) = d * (e - s) * (1 - t / T) ^ (d - 1) / T - return DECELERATED_FACTOR * mDistance * 1000 * Math.pow(1 - progress, DECELERATED_FACTOR - 1) / mDuration; - } - - private int getX(final float f) { - int r = (int) Math.round(mStartX + f * mDistance * mCosAngle); - if (mCosAngle > 0 && mStartX <= mMaxX) { - r = Math.min(r, mMaxX); - } else if (mCosAngle < 0 && mStartX >= mMinX) { - r = Math.max(r, mMinX); - } - return r; - } - - private int getY(final float f) { - int r = (int) Math.round(mStartY + f * mDistance * mSinAngle); - if (mSinAngle > 0 && mStartY <= mMaxY) { - r = Math.min(r, mMaxY); - } else if (mSinAngle < 0 && mStartY >= mMinY) { - r = Math.max(r, mMinY); - } - return r; - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvas.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvas.java deleted file mode 100644 index e008a16ac..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvas.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.RectF; - -import javax.microedition.khronos.opengles.GL11; - -// -// GLCanvas gives a convenient interface to draw using OpenGL. -// -// When a rectangle is specified in this interface, it means the region -// [x, x+width) * [y, y+height) -// -public interface GLCanvas { - public static final int SAVE_FLAG_ALL = 0xFFFFFFFF; - - public static final int SAVE_FLAG_ALPHA = 0x01; - public static final int SAVE_FLAG_MATRIX = 0x02; - - public void clearBuffer(float[] argb); - - // Delete the textures and buffers in GL side. This function should only be - // called in the GL thread. - public void deleteRecycledResources(); - - // Draws a texture to the specified rectangle. - public void drawTexture(BasicTexture texture, int x, int y, int width, int height); - - // Draws the source rectangle part of the texture to the target rectangle. - public void drawTexture(BasicTexture texture, RectF source, RectF target); - - // Dump statistics information and clear the counters. For debug only. - public void dumpStatisticsAndClear(); - - // Fills the specified rectangle with the specified color. - public void fillRect(float x, float y, float width, float height, int color); - - public float getAlpha(); - - // Gets the underlying GL instance. This is used only when direct access to - // GL is needed. - public GL11 getGLInstance(); - - // (current alpha) = (current alpha) * alpha - public void multiplyAlpha(float alpha); - - public void multiplyMatrix(float[] mMatrix, int offset); - - // Pops from the top of the stack as current configuration state (matrix, - // alpha, and clip). This call balances a previous call to save(), and is - // used to remove all modifications to the configuration state since the - // last save call. - public void restore(); - - public void rotate(float angle, float x, float y, float z); - - // Pushes the configuration state (matrix, and alpha) onto - // a private stack. - public void save(); - - // Same as save(), but only save those specified in saveFlags. - public void save(int saveFlags); - - public void scale(float sx, float sy, float sz); - - // Sets and gets the current alpha, alpha must be in [0, 1]. - public void setAlpha(float alpha); - - // Tells GLCanvas the size of the underlying GL surface. This should be - // called before first drawing and when the size of GL surface is changed. - // This is called by GLRoot and should not be called by the clients - // who only want to draw on the GLCanvas. Both width and height must be - // nonnegative. - public void setSize(int width, int height); - - public void translate(float x, float y); - - // Unloads the specified texture from the canvas. The resource allocated - // to draw the texture will be released. The specified texture will return - // to the unloaded state. This function should be called only from - // BasicTexture or its descendant - public boolean unloadTexture(BasicTexture texture); -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvasImpl.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvasImpl.java deleted file mode 100644 index f595a5493..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvasImpl.java +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.RectF; -import android.opengl.GLU; -import android.opengl.Matrix; -import android.util.Log; - -import org.mariotaku.gallery3d.util.GalleryUtils; -import org.mariotaku.gallery3d.util.IntArray; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.Locale; - -import javax.microedition.khronos.opengles.GL10; -import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; - -public class GLCanvasImpl implements GLCanvas { - private static final String TAG = "GLCanvasImp"; - - private static final float OPAQUE_ALPHA = 0.95f; - - private static final int OFFSET_FILL_RECT = 0; - private static final float[] BOX_COORDINATES = { 0, 0, 1, 0, 0, 1, 1, 1, // used - // for - // filling - // a - // rectangle - 0, 0, 1, 1, // used for drawing a line - 0, 0, 0, 1, 1, 1, 1, 0 }; // used for drawing the outline of a - // rectangle - - private final GL11 mGL; - - private final float mMatrixValues[] = new float[16]; - private final float mTextureMatrixValues[] = new float[16]; - - // The results of mapPoints are stored in this buffer, and the order is - // x1, y1, x2, y2. - private final float mMapPointsBuffer[] = new float[4]; - - private int mBoxCoords; - - private final GLState mGLState; - - private float mAlpha; - private final ArrayList mRestoreStack = new ArrayList(); - private ConfigState mRecycledRestoreAction; - - private final RectF mDrawTextureSourceRect = new RectF(); - private final RectF mDrawTextureTargetRect = new RectF(); - private final float[] mTempMatrix = new float[32]; - private final IntArray mUnboundTextures = new IntArray(); - private final IntArray mDeleteBuffers = new IntArray(); - private final boolean mBlendEnabled = true; - - // Drawing statistics - int mCountDrawLine; - int mCountFillRect; - int mCountDrawMesh; - int mCountTextureRect; - int mCountTextureOES; - - // TODO: the code only work for 2D should get fixed for 3D or removed - private static final int MSKEW_X = 4; - - private static final int MSKEW_Y = 1; - - private static final int MSCALE_X = 0; - - private static final int MSCALE_Y = 5; - - GLCanvasImpl(final GL11 gl) { - mGL = gl; - mGLState = new GLState(gl); - initialize(); - } - - @Override - public void clearBuffer(final float[] argb) { - if (argb != null && argb.length == 4) { - mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]); - } else { - mGL.glClearColor(0, 0, 0, 1); - } - mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); - } - - @Override - public void deleteRecycledResources() { - synchronized (mUnboundTextures) { - IntArray ids = mUnboundTextures; - if (ids.size() > 0) { - GLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); - ids.clear(); - } - - ids = mDeleteBuffers; - if (ids.size() > 0) { - GLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); - ids.clear(); - } - } - } - - @Override - public void drawTexture(final BasicTexture texture, final int x, final int y, final int width, final int height) { - drawTexture(texture, x, y, width, height, mAlpha); - } - - @Override - public void drawTexture(final BasicTexture texture, RectF source, RectF target) { - if (target.width() <= 0 || target.height() <= 0) return; - - // Copy the input to avoid changing it. - mDrawTextureSourceRect.set(source); - mDrawTextureTargetRect.set(target); - source = mDrawTextureSourceRect; - target = mDrawTextureTargetRect; - - mGLState.setBlendEnabled(mBlendEnabled && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA)); - if (!bindTexture(texture)) return; - convertCoordinate(source, target, texture); - setTextureCoords(source); - mGLState.setTextureAlpha(mAlpha); - textureRect(target.left, target.top, target.width(), target.height()); - } - - @Override - public void dumpStatisticsAndClear() { - final String line = String.format(Locale.US, "MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", - mCountDrawMesh, mCountTextureRect, mCountTextureOES, mCountFillRect, mCountDrawLine); - mCountDrawMesh = 0; - mCountTextureRect = 0; - mCountTextureOES = 0; - mCountFillRect = 0; - mCountDrawLine = 0; - Log.d(TAG, line); - } - - @Override - public void fillRect(final float x, final float y, final float width, final float height, final int color) { - mGLState.setColorMode(color, mAlpha); - final GL11 gl = mGL; - - saveTransform(); - translate(x, y); - scale(width, height, 1); - - gl.glLoadMatrixf(mMatrixValues, 0); - gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4); - - restoreTransform(); - mCountFillRect++; - } - - @Override - public float getAlpha() { - return mAlpha; - } - - @Override - public GL11 getGLInstance() { - return mGL; - } - - @Override - public void multiplyAlpha(final float alpha) { - GalleryUtils.assertTrue(alpha >= 0 && alpha <= 1); - mAlpha *= alpha; - } - - @Override - public void multiplyMatrix(final float matrix[], final int offset) { - final float[] temp = mTempMatrix; - Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset); - System.arraycopy(temp, 0, mMatrixValues, 0, 16); - } - - @Override - public void restore() { - if (mRestoreStack.isEmpty()) throw new IllegalStateException(); - final ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1); - config.restore(this); - freeRestoreConfig(config); - } - - @Override - public void rotate(final float angle, final float x, final float y, final float z) { - if (angle == 0) return; - final float[] temp = mTempMatrix; - Matrix.setRotateM(temp, 0, angle, x, y, z); - Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0); - System.arraycopy(temp, 16, mMatrixValues, 0, 16); - } - - @Override - public void save() { - save(SAVE_FLAG_ALL); - } - - @Override - public void save(final int saveFlags) { - final ConfigState config = obtainRestoreConfig(); - - if ((saveFlags & SAVE_FLAG_ALPHA) != 0) { - config.mAlpha = mAlpha; - } else { - config.mAlpha = -1; - } - - if ((saveFlags & SAVE_FLAG_MATRIX) != 0) { - System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16); - } else { - config.mMatrix[0] = Float.NEGATIVE_INFINITY; - } - - mRestoreStack.add(config); - } - - @Override - public void scale(final float sx, final float sy, final float sz) { - Matrix.scaleM(mMatrixValues, 0, sx, sy, sz); - } - - @Override - public void setAlpha(final float alpha) { - GalleryUtils.assertTrue(alpha >= 0 && alpha <= 1); - mAlpha = alpha; - } - - @Override - public void setSize(final int width, final int height) { - GalleryUtils.assertTrue(width >= 0 && height >= 0); - - mAlpha = 1.0f; - - final GL11 gl = mGL; - gl.glViewport(0, 0, width, height); - gl.glMatrixMode(GL11.GL_PROJECTION); - gl.glLoadIdentity(); - GLU.gluOrtho2D(gl, 0, width, 0, height); - - gl.glMatrixMode(GL11.GL_MODELVIEW); - gl.glLoadIdentity(); - - final float matrix[] = mMatrixValues; - Matrix.setIdentityM(matrix, 0); - // to match the graphic coordinate system in android, we flip it - // vertically. - Matrix.translateM(matrix, 0, 0, height, 0); - Matrix.scaleM(matrix, 0, 1, -1, 1); - } - - // This is a faster version of translate(x, y, z) because - // (1) we knows z = 0, (2) we inline the Matrix.translateM call, - // (3) we unroll the loop - @Override - public void translate(final float x, final float y) { - final float[] m = mMatrixValues; - m[12] += m[0] * x + m[4] * y; - m[13] += m[1] * x + m[5] * y; - m[14] += m[2] * x + m[6] * y; - m[15] += m[3] * x + m[7] * y; - } - - // unloadTexture and deleteBuffer can be called from the finalizer thread, - // so we synchronized on the mUnboundTextures object. - @Override - public boolean unloadTexture(final BasicTexture t) { - synchronized (mUnboundTextures) { - if (!t.isLoaded()) return false; - mUnboundTextures.add(t.mId); - return true; - } - } - - private boolean bindTexture(final BasicTexture texture) { - if (!texture.onBind(this)) return false; - final int target = texture.getTarget(); - mGLState.setTextureTarget(target); - mGL.glBindTexture(target, texture.getId()); - return true; - } - - private void drawBoundTexture(final BasicTexture texture, int x, int y, int width, int height) { - // Test whether it has been rotated or flipped, if so, glDrawTexiOES - // won't work - if (isMatrixRotatedOrFlipped(mMatrixValues)) { - if (texture.hasBorder()) { - setTextureCoords(1.0f / texture.getTextureWidth(), 1.0f / texture.getTextureHeight(), - (texture.getWidth() - 1.0f) / texture.getTextureWidth(), - (texture.getHeight() - 1.0f) / texture.getTextureHeight()); - } else { - setTextureCoords(0, 0, (float) texture.getWidth() / texture.getTextureWidth(), - (float) texture.getHeight() / texture.getTextureHeight()); - } - textureRect(x, y, width, height); - } else { - // draw the rect from bottom-left to top-right - final float points[] = mapPoints(mMatrixValues, x, y + height, x + width, y); - x = (int) (points[0] + 0.5f); - y = (int) (points[1] + 0.5f); - width = (int) (points[2] + 0.5f) - x; - height = (int) (points[3] + 0.5f) - y; - if (width > 0 && height > 0) { - ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height); - mCountTextureOES++; - } - } - } - - private void drawTexture(final BasicTexture texture, final int x, final int y, final int width, final int height, - final float alpha) { - if (width <= 0 || height <= 0) return; - - mGLState.setBlendEnabled(mBlendEnabled && (!texture.isOpaque() || alpha < OPAQUE_ALPHA)); - if (!bindTexture(texture)) return; - mGLState.setTextureAlpha(alpha); - drawBoundTexture(texture, x, y, width, height); - } - - private void freeRestoreConfig(final ConfigState action) { - action.mNextFree = mRecycledRestoreAction; - mRecycledRestoreAction = action; - } - - private void initialize() { - final GL11 gl = mGL; - - // First create an nio buffer, then create a VBO from it. - final int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE; - final FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer(); - xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0); - - final int[] name = new int[1]; - GLId.glGenBuffers(1, name, 0); - mBoxCoords = name[0]; - - gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords); - gl.glBufferData(GL11.GL_ARRAY_BUFFER, xyBuffer.capacity() * (Float.SIZE / Byte.SIZE), xyBuffer, - GL11.GL_STATIC_DRAW); - - gl.glVertexPointer(2, GL11.GL_FLOAT, 0, 0); - gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); - - // Enable the texture coordinate array for Texture 1 - gl.glClientActiveTexture(GL11.GL_TEXTURE1); - gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); - gl.glClientActiveTexture(GL11.GL_TEXTURE0); - gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); - - // mMatrixValues and mAlpha will be initialized in setSize() - } - - // Transforms two points by the given matrix m. The result - // {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned. - private float[] mapPoints(final float m[], final int x1, final int y1, final int x2, final int y2) { - final float[] r = mMapPointsBuffer; - - // Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused. - final float x3 = m[0] * x1 + m[4] * y1 + m[12]; - final float y3 = m[1] * x1 + m[5] * y1 + m[13]; - final float w3 = m[3] * x1 + m[7] * y1 + m[15]; - r[0] = x3 / w3; - r[1] = y3 / w3; - - // Same for x2 y2. - final float x4 = m[0] * x2 + m[4] * y2 + m[12]; - final float y4 = m[1] * x2 + m[5] * y2 + m[13]; - final float w4 = m[3] * x2 + m[7] * y2 + m[15]; - r[2] = x4 / w4; - r[3] = y4 / w4; - - return r; - } - - private ConfigState obtainRestoreConfig() { - if (mRecycledRestoreAction != null) { - final ConfigState result = mRecycledRestoreAction; - mRecycledRestoreAction = result.mNextFree; - return result; - } - return new ConfigState(); - } - - private void restoreTransform() { - System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16); - } - - private void saveTransform() { - System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16); - } - - private void setTextureCoords(final float left, final float top, final float right, final float bottom) { - mGL.glMatrixMode(GL11.GL_TEXTURE); - mTextureMatrixValues[0] = right - left; - mTextureMatrixValues[5] = bottom - top; - mTextureMatrixValues[10] = 1; - mTextureMatrixValues[12] = left; - mTextureMatrixValues[13] = top; - mTextureMatrixValues[15] = 1; - mGL.glLoadMatrixf(mTextureMatrixValues, 0); - mGL.glMatrixMode(GL11.GL_MODELVIEW); - } - - private void setTextureCoords(final RectF source) { - setTextureCoords(source.left, source.top, source.right, source.bottom); - } - - private void textureRect(final float x, final float y, final float width, final float height) { - final GL11 gl = mGL; - - saveTransform(); - translate(x, y); - scale(width, height, 1); - - gl.glLoadMatrixf(mMatrixValues, 0); - gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4); - - restoreTransform(); - mCountTextureRect++; - } - - private static ByteBuffer allocateDirectNativeOrderBuffer(final int size) { - return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); - } - - // This function changes the source coordinate to the texture coordinates. - // It also clips the source and target coordinates if it is beyond the - // bound of the texture. - private static void convertCoordinate(final RectF source, final RectF target, final BasicTexture texture) { - - final int width = texture.getWidth(); - final int height = texture.getHeight(); - final int texWidth = texture.getTextureWidth(); - final int texHeight = texture.getTextureHeight(); - // Convert to texture coordinates - source.left /= texWidth; - source.right /= texWidth; - source.top /= texHeight; - source.bottom /= texHeight; - - // Clip if the rendering range is beyond the bound of the texture. - final float xBound = (float) width / texWidth; - if (source.right > xBound) { - target.right = target.left + target.width() * (xBound - source.left) / source.width(); - source.right = xBound; - } - final float yBound = (float) height / texHeight; - if (source.bottom > yBound) { - target.bottom = target.top + target.height() * (yBound - source.top) / source.height(); - source.bottom = yBound; - } - } - - private static boolean isMatrixRotatedOrFlipped(final float matrix[]) { - final float eps = 1e-5f; - return Math.abs(matrix[MSKEW_X]) > eps || Math.abs(matrix[MSKEW_Y]) > eps || matrix[MSCALE_X] < -eps - || matrix[MSCALE_Y] > eps; - } - - private static class ConfigState { - float mAlpha; - float mMatrix[] = new float[16]; - ConfigState mNextFree; - - public void restore(final GLCanvasImpl canvas) { - if (mAlpha >= 0) { - canvas.setAlpha(mAlpha); - } - if (mMatrix[0] != Float.NEGATIVE_INFINITY) { - System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16); - } - } - } - - private static class GLState { - - private final GL11 mGL; - - private int mTexEnvMode = GL11.GL_REPLACE; - private float mTextureAlpha = 1.0f; - private int mTextureTarget = GL11.GL_TEXTURE_2D; - private boolean mBlendEnabled = true; - - public GLState(final GL11 gl) { - mGL = gl; - - // Disable unused state - gl.glDisable(GL11.GL_LIGHTING); - - // Enable used features - gl.glEnable(GL11.GL_DITHER); - - gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); - gl.glEnable(GL11.GL_TEXTURE_2D); - - gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE); - - // Set the background color - gl.glClearColor(0f, 0f, 0f, 0f); - gl.glClearStencil(0); - - gl.glEnable(GL11.GL_BLEND); - gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); - - // We use 565 or 8888 format, so set the alignment to 2 bytes/pixel. - gl.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2); - } - - public void setBlendEnabled(final boolean enabled) { - if (mBlendEnabled == enabled) return; - mBlendEnabled = enabled; - if (enabled) { - mGL.glEnable(GL11.GL_BLEND); - } else { - mGL.glDisable(GL11.GL_BLEND); - } - } - - public void setColorMode(final int color, final float alpha) { - setBlendEnabled(!GalleryUtils.isOpaque(color) || alpha < OPAQUE_ALPHA); - - // Set mTextureAlpha to an invalid value, so that it will reset - // again in setTextureAlpha(float) later. - mTextureAlpha = -1.0f; - - setTextureTarget(0); - - final float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f; - mGL.glColor4x(Math.round((color >> 16 & 0xFF) * prealpha), Math.round((color >> 8 & 0xFF) * prealpha), - Math.round((color & 0xFF) * prealpha), Math.round(255 * prealpha)); - } - - public void setTexEnvMode(final int mode) { - if (mTexEnvMode == mode) return; - mTexEnvMode = mode; - mGL.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode); - } - - public void setTextureAlpha(final float alpha) { - if (mTextureAlpha == alpha) return; - mTextureAlpha = alpha; - if (alpha >= OPAQUE_ALPHA) { - // The alpha is need for those texture without alpha channel - mGL.glColor4f(1, 1, 1, 1); - setTexEnvMode(GL11.GL_REPLACE); - } else { - mGL.glColor4f(alpha, alpha, alpha, alpha); - setTexEnvMode(GL11.GL_MODULATE); - } - } - - // target is a value like GL_TEXTURE_2D. If target = 0, texturing is - // disabled. - public void setTextureTarget(final int target) { - if (mTextureTarget == target) return; - if (mTextureTarget != 0) { - mGL.glDisable(mTextureTarget); - } - mTextureTarget = target; - if (mTextureTarget != 0) { - mGL.glEnable(mTextureTarget); - } - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLId.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLId.java deleted file mode 100644 index 5ccd44631..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLId.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import javax.microedition.khronos.opengles.GL11; - -// This mimics corresponding GL functions. -public class GLId { - static int sNextId = 1; - - public synchronized static void glDeleteBuffers(final GL11 gl, final int n, final int[] buffers, final int offset) { - gl.glDeleteBuffers(n, buffers, offset); - } - - public synchronized static void glDeleteTextures(final GL11 gl, final int n, final int[] textures, final int offset) { - gl.glDeleteTextures(n, textures, offset); - } - - public synchronized static void glGenBuffers(int n, final int[] buffers, final int offset) { - while (n-- > 0) { - buffers[offset + n] = sNextId++; - } - } - - public synchronized static void glGenTextures(int n, final int[] textures, final int offset) { - while (n-- > 0) { - textures[offset + n] = sNextId++; - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRoot.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRoot.java deleted file mode 100644 index d2862b610..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRoot.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.content.Context; -import android.graphics.Matrix; - -public interface GLRoot { - - public void addOnGLIdleListener(OnGLIdleListener listener); - - public void freeze(); - - public int getCompensation(); - - public Matrix getCompensationMatrix(); - - public Context getContext(); - - public int getDisplayRotation(); - - public void lockRenderThread(); - - public void requestLayoutContentPane(); - - public void requestRender(); - - public void setContentPane(GLView content); - - public void unfreeze(); - - public void unlockRenderThread(); - - // Listener will be called when GL is idle AND before each frame. - // Mainly used for uploading textures. - public static interface OnGLIdleListener { - public boolean onGLIdle(GLCanvas canvas, boolean renderRequested); - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRootView.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRootView.java deleted file mode 100644 index 8786331c4..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRootView.java +++ /dev/null @@ -1,541 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Matrix; -import android.graphics.PixelFormat; -import android.opengl.GLSurfaceView; -import android.os.Build; -import android.os.Process; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.SurfaceHolder; -import android.view.View; - -import org.mariotaku.gallery3d.anim.CanvasAnimation; -import org.mariotaku.gallery3d.util.ApiHelper; -import org.mariotaku.gallery3d.util.GalleryUtils; -import org.mariotaku.gallery3d.util.MotionEventHelper; -import org.mariotaku.twidere.R; -import org.mariotaku.twidere.util.accessor.ViewAccessor; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; -import javax.microedition.khronos.opengles.GL11; - -// The root component of all GLViews. The rendering is done in GL -// thread while the event handling is done in the main thread. To synchronize -// the two threads, the entry points of this package need to synchronize on the -// GLRootView instance unless it can be proved that the rendering -// thread won't access the same thing as the method. The entry points include: -// (1) The public methods of HeadUpDisplay -// (2) The public methods of CameraHeadUpDisplay -// (3) The overridden methods in GLRootView. -@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) -public class GLRootView extends GLSurfaceView implements GLSurfaceView.Renderer, GLRoot { - private static final String TAG = "GLRootView"; - - private static final boolean DEBUG_FPS = false; - private int mFrameCount = 0; - private long mFrameCountingStart = 0; - - private static final boolean DEBUG_INVALIDATE = false; - private int mInvalidateColor = 0; - - private static final boolean DEBUG_DRAWING_STAT = false; - - private static final boolean false_SLOW_ONLY = false; - - private static final int FLAG_INITIALIZED = 1; - private static final int FLAG_NEED_LAYOUT = 2; - - private GL11 mGL; - private GLCanvas mCanvas; - private GLView mContentView; - - // mCompensation is the difference between the UI orientation on GLCanvas - // and the framework orientation. See OrientationManager for details. - private int mCompensation; - // mCompensationMatrix maps the coordinates of touch events. It is kept sync - // with mCompensation. - private final Matrix mCompensationMatrix = new Matrix(); - private int mDisplayRotation; - - private int mFlags = FLAG_NEED_LAYOUT; - private volatile boolean mRenderRequested = false; - - private final GalleryEGLConfigChooser mEglConfigChooser = new GalleryEGLConfigChooser(); - - private final ArrayList mAnimations = new ArrayList(); - - private final ArrayDeque mIdleListeners = new ArrayDeque(); - - private final IdleRunner mIdleRunner = new IdleRunner(); - - private final ReentrantLock mRenderLock = new ReentrantLock(); - private final Condition mFreezeCondition = mRenderLock.newCondition(); - private boolean mFreeze; - - private long mLastDrawFinishTime; - private boolean mInDownState = false; - private boolean mFirstDraw = true; - - public GLRootView(final Context context) { - this(context, null); - } - - public GLRootView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mFlags |= FLAG_INITIALIZED; - ViewAccessor.setBackground(this, null); - setEGLConfigChooser(mEglConfigChooser); - setRenderer(this); - if (ApiHelper.USE_888_PIXEL_FORMAT) { - getHolder().setFormat(PixelFormat.RGB_888); - } else { - getHolder().setFormat(PixelFormat.RGB_565); - } - - // Uncomment this to enable gl error check. - // setDebugFlags(DEBUG_CHECK_GL_ERROR); - } - - @Override - public void addOnGLIdleListener(final OnGLIdleListener listener) { - synchronized (mIdleListeners) { - mIdleListeners.addLast(listener); - mIdleRunner.enable(); - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - if (!isEnabled()) return false; - - final int action = event.getAction(); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mInDownState = false; - } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) return false; - - if (mCompensation != 0) { - event = MotionEventHelper.transformEvent(event, mCompensationMatrix); - } - - mRenderLock.lock(); - try { - // If this has been detached from root, we don't need to handle - // event - final boolean handled = mContentView != null && mContentView.dispatchTouchEvent(event); - if (action == MotionEvent.ACTION_DOWN && handled) { - mInDownState = true; - } - return handled; - } finally { - mRenderLock.unlock(); - } - } - - @Override - public void freeze() { - mRenderLock.lock(); - mFreeze = true; - mRenderLock.unlock(); - } - - @Override - public int getCompensation() { - return mCompensation; - } - - @Override - public Matrix getCompensationMatrix() { - return mCompensationMatrix; - } - - @Override - public int getDisplayRotation() { - return mDisplayRotation; - } - - @Override - public void lockRenderThread() { - mRenderLock.lock(); - } - - @Override - public void onDrawFrame(final GL10 gl) { - AnimationTime.update(); - long t0; - if (false_SLOW_ONLY) { - t0 = System.nanoTime(); - } - mRenderLock.lock(); - - while (mFreeze) { - mFreezeCondition.awaitUninterruptibly(); - } - - try { - onDrawFrameLocked(gl); - } finally { - mRenderLock.unlock(); - } - - // We put a black cover View in front of the SurfaceView and hide it - // after the first draw. This prevents the SurfaceView being transparent - // before the first draw. - if (mFirstDraw) { - mFirstDraw = false; - post(new Runnable() { - @Override - public void run() { - final View root = getRootView(); - final View cover = root.findViewById(R.id.gl_root_cover); - cover.setVisibility(GONE); - } - }); - } - - if (false_SLOW_ONLY) { - final long t = System.nanoTime(); - final long durationInMs = (t - mLastDrawFinishTime) / 1000000; - final long durationDrawInMs = (t - t0) / 1000000; - mLastDrawFinishTime = t; - - if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames - Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" + durationInMs + ") -----"); - } - } - } - - @Override - public void onPause() { - unfreeze(); - super.onPause(); - } - - /** - * Called when the OpenGL surface is recreated without destroying the - * context. - */ - // This is a GLSurfaceView.Renderer callback - @Override - public void onSurfaceChanged(final GL10 gl1, final int width, final int height) { - Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString()); - Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); - final GL11 gl = (GL11) gl1; - GalleryUtils.assertTrue(mGL == gl); - - mCanvas.setSize(width, height); - } - - /** - * Called when the context is created, possibly after automatic destruction. - */ - // This is a GLSurfaceView.Renderer callback - @Override - public void onSurfaceCreated(final GL10 gl1, final EGLConfig config) { - final GL11 gl = (GL11) gl1; - if (mGL != null) { - // The GL Object has changed - Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl); - } - mRenderLock.lock(); - try { - mGL = gl; - mCanvas = new GLCanvasImpl(gl); - BasicTexture.invalidateAllTextures(); - } finally { - mRenderLock.unlock(); - } - - if (DEBUG_FPS) { - setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); - } else { - setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); - } - } - - @Override - public void requestLayoutContentPane() { - mRenderLock.lock(); - try { - if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return; - - // "View" system will invoke onLayout() for initialization(bug ?), - // we - // have to ignore it since the GLThread is not ready yet. - if ((mFlags & FLAG_INITIALIZED) == 0) return; - - mFlags |= FLAG_NEED_LAYOUT; - requestRender(); - } finally { - mRenderLock.unlock(); - } - } - - @Override - public void requestRender() { - if (DEBUG_INVALIDATE) { - final StackTraceElement e = Thread.currentThread().getStackTrace()[4]; - final String caller = e.getFileName() + ":" + e.getLineNumber() + " "; - Log.d(TAG, "invalidate: " + caller); - } - if (mRenderRequested) return; - mRenderRequested = true; - super.requestRender(); - } - - @Override - public void setContentPane(final GLView content) { - if (mContentView == content) return; - if (mContentView != null) { - if (mInDownState) { - final long now = SystemClock.uptimeMillis(); - final MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); - mContentView.dispatchTouchEvent(cancelEvent); - cancelEvent.recycle(); - mInDownState = false; - } - mContentView.detachFromRoot(); - BasicTexture.yieldAllTextures(); - } - mContentView = content; - if (content != null) { - content.attachToRoot(this); - requestLayoutContentPane(); - } - } - - // We need to unfreeze in the following methods and in onPause(). - // These methods will wait on GLThread. If we have freezed the GLRootView, - // the GLThread will wait on main thread to call unfreeze and cause dead - // lock. - @Override - public void surfaceChanged(final SurfaceHolder holder, final int format, final int w, final int h) { - unfreeze(); - super.surfaceChanged(holder, format, w, h); - } - - @Override - public void surfaceCreated(final SurfaceHolder holder) { - unfreeze(); - super.surfaceCreated(holder); - } - - @Override - public void surfaceDestroyed(final SurfaceHolder holder) { - unfreeze(); - super.surfaceDestroyed(holder); - } - - @Override - public void unfreeze() { - mRenderLock.lock(); - mFreeze = false; - mFreezeCondition.signalAll(); - mRenderLock.unlock(); - } - - @Override - public void unlockRenderThread() { - mRenderLock.unlock(); - } - - @Override - protected void finalize() throws Throwable { - try { - unfreeze(); - } finally { - super.finalize(); - } - } - - @Override - protected void onDetachedFromWindow() { - unfreeze(); - super.onDetachedFromWindow(); - } - - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { - if (changed) { - requestLayoutContentPane(); - } - } - - private void layoutContentPane() { - mFlags &= ~FLAG_NEED_LAYOUT; - - int w = getWidth(); - int h = getHeight(); - final int displayRotation = 0; - final int compensation = 0; - - if (mCompensation != compensation) { - mCompensation = compensation; - if (mCompensation % 180 != 0) { - mCompensationMatrix.setRotate(mCompensation); - // move center to origin before rotation - mCompensationMatrix.preTranslate(-w / 2, -h / 2); - // align with the new origin after rotation - mCompensationMatrix.postTranslate(h / 2, w / 2); - } else { - mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2); - } - } - mDisplayRotation = displayRotation; - - // Do the actual layout. - if (mCompensation % 180 != 0) { - final int tmp = w; - w = h; - h = tmp; - } - Log.i(TAG, "layout content pane " + w + "x" + h + " (compensation " + mCompensation + ")"); - if (mContentView != null && w != 0 && h != 0) { - mContentView.layout(0, 0, w, h); - } - // Uncomment this to dump the view hierarchy. - // mContentView.dumpTree(""); - } - - private void onDrawFrameLocked(final GL10 gl) { - if (DEBUG_FPS) { - outputFps(); - } - - // release the unbound textures and deleted buffers. - mCanvas.deleteRecycledResources(); - - // reset texture upload limit - UploadedTexture.resetUploadLimit(); - - mRenderRequested = false; - - if ((mFlags & FLAG_NEED_LAYOUT) != 0) { - layoutContentPane(); - } - - mCanvas.save(GLCanvas.SAVE_FLAG_ALL); - rotateCanvas(-mCompensation); - if (mContentView != null) { - mContentView.render(mCanvas); - } - mCanvas.restore(); - - if (!mAnimations.isEmpty()) { - final long now = AnimationTime.get(); - for (int i = 0, n = mAnimations.size(); i < n; i++) { - mAnimations.get(i).setStartTime(now); - } - mAnimations.clear(); - } - - if (UploadedTexture.uploadLimitReached()) { - requestRender(); - } - - synchronized (mIdleListeners) { - if (!mIdleListeners.isEmpty()) { - mIdleRunner.enable(); - } - } - - if (DEBUG_INVALIDATE) { - mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor); - mInvalidateColor = ~mInvalidateColor; - } - - if (DEBUG_DRAWING_STAT) { - mCanvas.dumpStatisticsAndClear(); - } - } - - private void outputFps() { - final long now = System.nanoTime(); - if (mFrameCountingStart == 0) { - mFrameCountingStart = now; - } else if (now - mFrameCountingStart > 1000000000) { - Log.d(TAG, "fps: " + (double) mFrameCount * 1000000000 / (now - mFrameCountingStart)); - mFrameCountingStart = now; - mFrameCount = 0; - } - ++mFrameCount; - } - - private void rotateCanvas(final int degrees) { - if (degrees == 0) return; - final int w = getWidth(); - final int h = getHeight(); - final int cx = w / 2; - final int cy = h / 2; - mCanvas.translate(cx, cy); - mCanvas.rotate(degrees, 0, 0, 1); - if (degrees % 180 != 0) { - mCanvas.translate(-cy, -cx); - } else { - mCanvas.translate(-cx, -cy); - } - } - - private class IdleRunner implements Runnable { - // true if the idle runner is in the queue - private boolean mActive = false; - - public void enable() { - // Who gets the flag can add it to the queue - if (mActive) return; - mActive = true; - queueEvent(this); - } - - @Override - public void run() { - OnGLIdleListener listener; - synchronized (mIdleListeners) { - mActive = false; - if (mIdleListeners.isEmpty()) return; - listener = mIdleListeners.removeFirst(); - } - mRenderLock.lock(); - boolean keepInQueue; - try { - keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested); - } finally { - mRenderLock.unlock(); - } - synchronized (mIdleListeners) { - if (keepInQueue) { - mIdleListeners.addLast(listener); - } - if (!mRenderRequested && !mIdleListeners.isEmpty()) { - enable(); - } - } - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLView.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLView.java deleted file mode 100644 index 33beaeb0a..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GLView.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.annotation.SuppressLint; -import android.graphics.Rect; -import android.view.MotionEvent; - -import org.mariotaku.gallery3d.anim.CanvasAnimation; -import org.mariotaku.gallery3d.util.GalleryUtils; - -import java.util.ArrayList; - -// GLView is a UI component. It can render to a GLCanvas and accept touch -// events. A GLView may have zero or more child GLView and they form a tree -// structure. The rendering and event handling will pass through the tree -// structure. -// -// A GLView tree should be attached to a GLRoot before event dispatching and -// rendering happens. GLView asks GLRoot to re-render or re-layout the -// GLView hierarchy using requestRender() and requestLayoutContentPane(). -// -// The render() method is called in a separate thread. Before calling -// dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the -// rendering thread running at the same time. If there are other entry points -// from main thread (like a Handler) in your GLView, you need to call -// lockRendering() if the rendering thread should not run at the same time. -// -public class GLView { - public static final int VISIBLE = 0; - public static final int INVISIBLE = 1; - - private static final int FLAG_INVISIBLE = 1; - private static final int FLAG_LAYOUT_REQUESTED = 4; - - protected final Rect mBounds = new Rect(); - - protected final Rect mPaddings = new Rect(); - private GLRoot mRoot; - - protected GLView mParent; - private ArrayList mComponents; - private GLView mMotionTarget; - private CanvasAnimation mAnimation; - - private int mViewFlags = 0; - - protected int mMeasuredWidth = 0; - - protected int mMeasuredHeight = 0; - protected int mScrollY = 0; - - protected int mScrollX = 0; - private float[] mBackgroundColor; - - // Adds a child to this GLView. - public void addComponent(final GLView component) { - // Make sure the component doesn't have a parent currently. - if (component.mParent != null) throw new IllegalStateException(); - - // Build parent-child links - if (mComponents == null) { - mComponents = new ArrayList(); - } - mComponents.add(component); - component.mParent = this; - - // If this is added after we have a root, tell the component. - if (mRoot != null) { - component.onAttachToRoot(mRoot); - } - } - - // This should only be called on the content pane (the topmost GLView). - public void attachToRoot(final GLRoot root) { - GalleryUtils.assertTrue(mParent == null && mRoot == null); - onAttachToRoot(root); - } - - // This should only be called on the content pane (the topmost GLView). - public void detachFromRoot() { - GalleryUtils.assertTrue(mParent == null && mRoot != null); - onDetachFromRoot(); - } - - public float[] getBackgroundColor() { - return mBackgroundColor; - } - - // Returns the number of children of the GLView. - public int getComponentCount() { - return mComponents == null ? 0 : mComponents.size(); - } - - public GLRoot getGLRoot() { - return mRoot; - } - - public int getHeight() { - return mBounds.bottom - mBounds.top; - } - - public int getMeasuredHeight() { - return mMeasuredHeight; - } - - public int getMeasuredWidth() { - return mMeasuredWidth; - } - - public Rect getPaddings() { - return mPaddings; - } - - // Returns GLView.VISIBLE or GLView.INVISIBLE - public int getVisibility() { - return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE; - } - - public int getWidth() { - return mBounds.right - mBounds.left; - } - - // Request re-rendering of the view hierarchy. - // This is used for animation or when the contents changed. - public void invalidate() { - final GLRoot root = getGLRoot(); - if (root != null) { - root.requestRender(); - } - } - - @SuppressLint("WrongCall") - public void layout(final int left, final int top, final int right, final int bottom) { - final boolean sizeChanged = setBounds(left, top, right, bottom); - mViewFlags &= ~FLAG_LAYOUT_REQUESTED; - // We call onLayout no matter sizeChanged is true or not because the - // orientation may change without changing the size of the View (for - // example, rotate the device by 180 degrees), and we want to handle - // orientation change in onLayout. - onLayout(sizeChanged, left, top, right, bottom); - } - - public void setBackgroundColor(final float[] color) { - mBackgroundColor = color; - } - - // Sets the visiblity of this GLView (either GLView.VISIBLE or - // GLView.INVISIBLE). - public void setVisibility(final int visibility) { - if (visibility == getVisibility()) return; - if (visibility == VISIBLE) { - mViewFlags &= ~FLAG_INVISIBLE; - } else { - mViewFlags |= FLAG_INVISIBLE; - } - onVisibilityChanged(visibility); - invalidate(); - } - - protected boolean dispatchTouchEvent(final MotionEvent event) { - final int x = (int) event.getX(); - final int y = (int) event.getY(); - final int action = event.getAction(); - if (mMotionTarget != null) { - if (action == MotionEvent.ACTION_DOWN) { - final MotionEvent cancel = MotionEvent.obtain(event); - cancel.setAction(MotionEvent.ACTION_CANCEL); - dispatchTouchEvent(cancel, x, y, mMotionTarget, false); - mMotionTarget = null; - } else { - dispatchTouchEvent(event, x, y, mMotionTarget, false); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mMotionTarget = null; - } - return true; - } - } - if (action == MotionEvent.ACTION_DOWN) { - // in the reverse rendering order - for (int i = getComponentCount() - 1; i >= 0; --i) { - final GLView component = getComponent(i); - if (component.getVisibility() != GLView.VISIBLE) { - continue; - } - if (dispatchTouchEvent(event, x, y, component, true)) { - mMotionTarget = component; - return true; - } - } - } - return onTouch(event); - } - - protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { - } - - protected boolean onTouch(final MotionEvent event) { - return false; - } - - protected void render(final GLCanvas canvas) { - renderBackground(canvas); - canvas.save(); - for (int i = 0, n = getComponentCount(); i < n; ++i) { - renderChild(canvas, getComponent(i)); - } - canvas.restore(); - } - - protected void renderChild(final GLCanvas canvas, final GLView component) { - if (component.getVisibility() != GLView.VISIBLE && component.mAnimation == null) return; - - final int xoffset = component.mBounds.left - mScrollX; - final int yoffset = component.mBounds.top - mScrollY; - - canvas.translate(xoffset, yoffset); - - final CanvasAnimation anim = component.mAnimation; - if (anim != null) { - canvas.save(anim.getCanvasSaveFlags()); - if (anim.calculate(AnimationTime.get())) { - invalidate(); - } else { - component.mAnimation = null; - } - anim.apply(canvas); - } - component.render(canvas); - if (anim != null) { - canvas.restore(); - } - canvas.translate(-xoffset, -yoffset); - } - - private boolean dispatchTouchEvent(final MotionEvent event, final int x, final int y, final GLView component, - final boolean checkBounds) { - final Rect rect = component.mBounds; - final int left = rect.left; - final int top = rect.top; - if (!checkBounds || rect.contains(x, y)) { - event.offsetLocation(-left, -top); - if (component.dispatchTouchEvent(event)) { - event.offsetLocation(left, top); - return true; - } - event.offsetLocation(left, top); - } - return false; - } - - // Returns the children for the given index. - private GLView getComponent(final int index) { - if (mComponents == null) throw new ArrayIndexOutOfBoundsException(index); - return mComponents.get(index); - } - - private void onAttachToRoot(final GLRoot root) { - mRoot = root; - for (int i = 0, n = getComponentCount(); i < n; ++i) { - getComponent(i).onAttachToRoot(root); - } - } - - private void onDetachFromRoot() { - for (int i = 0, n = getComponentCount(); i < n; ++i) { - getComponent(i).onDetachFromRoot(); - } - mRoot = null; - } - - private void onVisibilityChanged(final int visibility) { - for (int i = 0, n = getComponentCount(); i < n; ++i) { - final GLView child = getComponent(i); - if (child.getVisibility() == GLView.VISIBLE) { - child.onVisibilityChanged(visibility); - } - } - } - - private void renderBackground(final GLCanvas view) { - if (mBackgroundColor != null) { - view.clearBuffer(mBackgroundColor); - } - } - - private boolean setBounds(final int left, final int top, final int right, final int bottom) { - final boolean sizeChanged = right - left != mBounds.right - mBounds.left - || bottom - top != mBounds.bottom - mBounds.top; - mBounds.set(left, top, right, bottom); - return sizeChanged; - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GalleryEGLConfigChooser.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GalleryEGLConfigChooser.java deleted file mode 100644 index a3df80ef7..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GalleryEGLConfigChooser.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.opengl.GLSurfaceView.EGLConfigChooser; -import android.util.Log; - -import org.mariotaku.gallery3d.util.ApiHelper; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLDisplay; - -/* - * The code is copied/adapted from - * android.opengl.GLSurfaceView.BaseConfigChooser. Here we try to - * choose a configuration that support RGBA_8888 format and if possible, - * with stencil buffer, but is not required. - */ -class GalleryEGLConfigChooser implements EGLConfigChooser { - - private static final String TAG = "GalleryEGLConfigChooser"; - - private final int mConfigSpec565[] = new int[] { EGL10.EGL_RED_SIZE, 5, EGL10.EGL_GREEN_SIZE, 6, - EGL10.EGL_BLUE_SIZE, 5, EGL10.EGL_ALPHA_SIZE, 0, EGL10.EGL_NONE }; - - private final int mConfigSpec888[] = new int[] { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_ALPHA_SIZE, 0, EGL10.EGL_NONE }; - - private static final int[] ATTR_ID = { EGL10.EGL_RED_SIZE, EGL10.EGL_GREEN_SIZE, EGL10.EGL_BLUE_SIZE, - EGL10.EGL_ALPHA_SIZE, EGL10.EGL_DEPTH_SIZE, EGL10.EGL_STENCIL_SIZE, EGL10.EGL_CONFIG_ID, - EGL10.EGL_CONFIG_CAVEAT }; - - private static final String[] ATTR_NAME = { "R", "G", "B", "A", "D", "S", "ID", "CAVEAT" }; - - @Override - public EGLConfig chooseConfig(final EGL10 egl, final EGLDisplay display) { - final int[] numConfig = new int[1]; - final int mConfigSpec[] = ApiHelper.USE_888_PIXEL_FORMAT ? mConfigSpec888 : mConfigSpec565; - if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, numConfig)) - throw new RuntimeException("eglChooseConfig failed"); - - if (numConfig[0] <= 0) throw new RuntimeException("No configs match configSpec"); - - final EGLConfig[] configs = new EGLConfig[numConfig[0]]; - if (!egl.eglChooseConfig(display, mConfigSpec, configs, configs.length, numConfig)) - throw new RuntimeException(); - - return chooseConfig(egl, display, configs); - } - - private EGLConfig chooseConfig(final EGL10 egl, final EGLDisplay display, final EGLConfig configs[]) { - - EGLConfig result = null; - int minStencil = Integer.MAX_VALUE; - final int value[] = new int[1]; - - // Because we need only one bit of stencil, try to choose a config that - // has stencil support but with smallest number of stencil bits. If - // none is found, choose any one. - for (final EGLConfig config : configs) { - if (!ApiHelper.USE_888_PIXEL_FORMAT) { - if (egl.eglGetConfigAttrib(display, config, EGL10.EGL_RED_SIZE, value)) { - // Filter out ARGB 8888 configs. - if (value[0] == 8) { - continue; - } - } - } - if (egl.eglGetConfigAttrib(display, config, EGL10.EGL_STENCIL_SIZE, value)) { - if (value[0] == 0) { - continue; - } - if (value[0] < minStencil) { - minStencil = value[0]; - result = config; - } - } else - throw new RuntimeException("eglGetConfigAttrib error: " + egl.eglGetError()); - } - if (result == null) { - result = configs[0]; - } - egl.eglGetConfigAttrib(display, result, EGL10.EGL_STENCIL_SIZE, value); - logConfig(egl, display, result); - return result; - } - - private void logConfig(final EGL10 egl, final EGLDisplay display, final EGLConfig config) { - final int value[] = new int[1]; - final StringBuilder sb = new StringBuilder(); - for (int j = 0, k = ATTR_ID.length; j < k; j++) { - egl.eglGetConfigAttrib(display, config, ATTR_ID[j], value); - sb.append(ATTR_NAME[j] + value[0] + " "); - } - Log.i(TAG, "Config chosen: " + sb.toString()); - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GestureRecognizer.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/GestureRecognizer.java deleted file mode 100644 index 37082cb24..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/GestureRecognizer.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.os.SystemClock; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; - -// This class aggregates three gesture detectors: GestureDetector, -// ScaleGestureDetector, and DownUpDetector. -@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) -public class GestureRecognizer { - @SuppressWarnings("unused") - private static final String TAG = "GestureRecognizer"; - - private final GestureDetector mGestureDetector; - - private final ScaleGestureDetector mScaleDetector; - private final DownUpDetector mDownUpDetector; - private final Listener mListener; - - public GestureRecognizer(final Context context, final Listener listener) { - mListener = listener; - mGestureDetector = new GestureDetector(context, new MyGestureListener(), null, true /* ignoreMultitouch */); - mScaleDetector = new ScaleGestureDetector(context, new MyScaleListener()); - mDownUpDetector = new DownUpDetector(new MyDownUpListener()); - } - - public void cancelScale() { - final long now = SystemClock.uptimeMillis(); - final MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); - mScaleDetector.onTouchEvent(cancelEvent); - cancelEvent.recycle(); - } - - public boolean isDown() { - return mDownUpDetector.isDown(); - } - - public void onTouchEvent(final MotionEvent event) { - mGestureDetector.onTouchEvent(event); - mScaleDetector.onTouchEvent(event); - mDownUpDetector.onTouchEvent(event); - } - - public interface Listener { - boolean onDoubleTap(float x, float y); - - void onDown(float x, float y); - - boolean onFling(float velocityX, float velocityY); - - boolean onScale(float focusX, float focusY, float scale); - - boolean onScaleBegin(float focusX, float focusY); - - void onScaleEnd(); - - boolean onScroll(float dx, float dy, float totalX, float totalY); - - boolean onSingleTapUp(float x, float y); - - void onUp(); - } - - private class MyDownUpListener implements DownUpDetector.DownUpListener { - @Override - public void onDown(final MotionEvent e) { - mListener.onDown(e.getX(), e.getY()); - } - - @Override - public void onUp(final MotionEvent e) { - mListener.onUp(); - } - } - - private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onDoubleTap(final MotionEvent e) { - return mListener.onDoubleTap(e.getX(), e.getY()); - } - - @Override - public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { - return mListener.onFling(velocityX, velocityY); - } - - @Override - public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float dx, final float dy) { - return mListener.onScroll(dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY()); - } - - @Override - public boolean onSingleTapUp(final MotionEvent e) { - return mListener.onSingleTapUp(e.getX(), e.getY()); - } - } - - private class MyScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { - @Override - public boolean onScale(final ScaleGestureDetector detector) { - return mListener.onScale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor()); - } - - @Override - public boolean onScaleBegin(final ScaleGestureDetector detector) { - return mListener.onScaleBegin(detector.getFocusX(), detector.getFocusY()); - } - - @Override - public void onScaleEnd(final ScaleGestureDetector detector) { - mListener.onScaleEnd(); - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java deleted file mode 100644 index ff17798cc..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.os.Build; -import android.os.Message; -import android.view.MotionEvent; - -import org.mariotaku.gallery3d.ImageViewerGLActivityOld; -import org.mariotaku.gallery3d.util.BitmapPool; - -public class PhotoView extends GLView { - - private static final int MSG_CANCEL_EXTRA_SCALING = 2; - private static final int MSG_CAPTURE_ANIMATION_DONE = 4; - - private static final int HOLD_TOUCH_DOWN = 1; - private static final int HOLD_CAPTURE_ANIMATION = 2; - - private final GestureListener mGestureListener; - - private final GestureRecognizer mGestureRecognizer; - private final PositionController mPositionController; - - private Listener mListener; - private ITileImageAdapter mModel; - - private final TileImageView mTileView; - private final EdgeView mEdgeView; - private final SynchronizedHandler mHandler; - private boolean mCancelExtraScalingPending; - private boolean mWantPictureCenterCallbacks = false; - - private int mDisplayRotation = 0; - - private int mCompensation = 0; - // This variable prevents us doing snapback until its values goes to 0. This - // happens if the user gesture is still in progress or we are in a capture - // animation. - private int mHolding; - - // This is the index of the last deleted item. This is only used as a hint - // to hide the undo button when we are too far away from the deleted - // item. The value Integer.MAX_VALUE means there is no such hint. - private final Context mContext; - private final FullPicture mPicture; - - public PhotoView(final ImageViewerGLActivityOld activity) { - mTileView = new TileImageView(activity); - addComponent(mTileView); - mContext = activity; - mEdgeView = new EdgeView(mContext); - addComponent(mEdgeView); - mHandler = new MyHandler(activity); - mPicture = new FullPicture(); - mGestureListener = new GestureListener(); - mGestureRecognizer = new GestureRecognizer(mContext, mGestureListener); - mPositionController = new PositionController(new EventListener()); - } - - public Rect getPhotoRect() { - return mPositionController.getPosition(); - } - - public void notifyImageChange() { - mListener.onCurrentImageUpdated(); - mPicture.reload(); - setPictureSize(); - invalidate(); - } - - public void pause() { - mPositionController.skipAnimation(); - mTileView.freeTextures(); - for (int i = -0; i <= 0; i++) { - mPicture.setScreenNail(null); - } - } - - public void resume() { - mTileView.prepareTextures(); - mPositionController.skipToFinalPosition(); - } - - public void setListener(final Listener listener) { - mListener = listener; - } - - public void setModel(final ITileImageAdapter model) { - mModel = model; - mTileView.setModel(mModel); - } - - public void setOpenAnimationRect(final Rect rect) { - mPositionController.setOpenAnimationRect(rect); - } - - public void setWantPictureCenterCallbacks(final boolean wanted) { - mWantPictureCenterCallbacks = wanted; - } - - @Override - protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { - final int w = right - left; - final int h = bottom - top; - mTileView.layout(0, 0, w, h); - mEdgeView.layout(0, 0, w, h); - - final GLRoot root = getGLRoot(); - final int displayRotation = root.getDisplayRotation(); - final int compensation = root.getCompensation(); - if (mDisplayRotation != displayRotation || mCompensation != compensation) { - mDisplayRotation = displayRotation; - mCompensation = compensation; - } - - if (changeSize) { - mPositionController.setViewSize(getWidth(), getHeight()); - } - } - - // ////////////////////////////////////////////////////////////////////////// - // Pictures - // ////////////////////////////////////////////////////////////////////////// - - @Override - protected boolean onTouch(final MotionEvent event) { - mGestureRecognizer.onTouchEvent(event); - return true; - } - - @Override - protected void render(final GLCanvas canvas) { - - // Draw photos from back to front - final Rect r = mPositionController.getPosition(); - mPicture.draw(canvas, r); - - renderChild(canvas, mEdgeView); - - mPositionController.advanceAnimation(); - } - - private void captureAnimationDone(final int offset) { - mHolding &= ~HOLD_CAPTURE_ANIMATION; - if (offset == 1) { - // Now the capture animation is done, enable the action bar. - mListener.onActionBarAllowed(true); - mListener.onActionBarWanted(); - } - snapback(); - } - - private void setPictureSize() { - mPositionController.setImageSize(mPicture.getSize(), null); - } - - private void snapback() { - mPositionController.snapback(); - } - - // ////////////////////////////////////////////////////////////////////////// - // Film mode focus switching - // ////////////////////////////////////////////////////////////////////////// - - private static int getRotated(final int degree, final int original, final int theother) { - return degree % 180 == 0 ? original : theother; - } - - public interface ITileImageAdapter { - - public int getImageHeight(); - - // Returns the rotation for the specified picture. - public int getImageRotation(); - - public int getImageWidth(); - - public int getLevelCount(); - - public ScreenNail getScreenNail(); - - /** - * The tile returned by this method can be specified this way: Assuming - * the image size is (width, height), first take the intersection of (0, - * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). Then - * extend this intersection region by borderSize pixels on each side. If - * in extending the region, we found some part of the region are outside - * the image, those pixels are filled with black.
- *
- * If level > 0, it does the same operation on a down-scaled version of - * the original image (down-scaled by a factor of 2^level), but (x, y) - * still refers to the coordinate on the original image.
- *
- * The method would be called in another thread. - */ - public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize, BitmapPool pool); - - public void recycleScreenNail(); - - public boolean setData(BitmapRegionDecoder decoder, Bitmap bitmap, int orientation); - - } - - public interface Listener { - public void onActionBarAllowed(boolean allowed); - - public void onActionBarWanted(); - - public void onCurrentImageUpdated(); - - public void onPictureCenter(); - - public void onSingleTapUp(int x, int y); - } - - public static class Size { - public int width; - public int height; - } - - private class GestureListener implements GestureRecognizer.Listener { - private boolean mIgnoreUpEvent = false; - // If we can change mode for this scale gesture. - private boolean mCanChangeMode; - // If we have changed the film mode in this scaling gesture. - private boolean mModeChanged; - // If a scrolling has happened after a down gesture. - private boolean mScrolledAfterDown; - // The accumulated scaling change from a scaling gesture. - private float mAccScale; - - @Override - public boolean onDoubleTap(final float x, final float y) { - final PositionController controller = mPositionController; - final float scale = controller.getImageScale(); - // onDoubleTap happened on the second ACTION_DOWN. - // We need to ignore the next UP event. - mIgnoreUpEvent = true; - if (scale <= .75f || controller.isAtMinimalScale()) { - controller.zoomIn(x, y, Math.max(1.0f, scale * 1.5f)); - } else { - controller.resetToFullView(); - } - return true; - } - - @Override - public void onDown(final float x, final float y) { - - mModeChanged = false; - - mHolding |= HOLD_TOUCH_DOWN; - - mScrolledAfterDown = false; - } - - @Override - public boolean onFling(final float velocityX, final float velocityY) { - if (mModeChanged) return true; - flingImages(velocityX, velocityY); - return true; - } - - @Override - public boolean onScale(final float focusX, final float focusY, final float scale) { - if (mModeChanged) return true; - if (Float.isNaN(scale) || Float.isInfinite(scale)) return false; - - final int outOfRange = mPositionController.scaleBy(scale, focusX, focusY); - - // We wait for a large enough scale change before changing mode. - // Otherwise we may mistakenly treat a zoom-in gesture as zoom-out - // or vice versa. - mAccScale *= scale; - final boolean largeEnough = mAccScale < 0.97f || mAccScale > 1.03f; - - // If mode changes, we treat this scaling gesture has ended. - if (mCanChangeMode && largeEnough) { - if (outOfRange < 0 || outOfRange > 0) { - stopExtraScalingIfNeeded(); - - // Removing the touch down flag allows snapback to happen - // for film mode change. - mHolding &= ~HOLD_TOUCH_DOWN; - - // We need to call onScaleEnd() before setting mModeChanged - // to true. - onScaleEnd(); - mModeChanged = true; - return true; - } - } - - if (outOfRange != 0) { - startExtraScalingIfNeeded(); - } else { - stopExtraScalingIfNeeded(); - } - return true; - } - - @Override - public boolean onScaleBegin(final float focusX, final float focusY) { - // We ignore the scaling gesture if it is a camera preview. - mPositionController.beginScale(focusX, focusY); - // We can change mode if we are in film mode, or we are in page - // mode and at minimal scale. - mCanChangeMode = mPositionController.isAtMinimalScale(); - mAccScale = 1f; - return true; - } - - @Override - public void onScaleEnd() { - if (mModeChanged) return; - mPositionController.endScale(); - } - - @Override - public boolean onScroll(final float dx, final float dy, final float totalX, final float totalY) { - if (!mScrolledAfterDown) { - mScrolledAfterDown = true; - } - - final int dxi = (int) (-dx + 0.5f); - final int dyi = (int) (-dy + 0.5f); - mPositionController.scrollPage(dxi, dyi); - return true; - } - - @Override - public boolean onSingleTapUp(final float x, final float y) { - // On crespo running Android 2.3.6 (gingerbread), a pinch out - // gesture results in the - // following call sequence: onDown(), onUp() and then - // onSingleTapUp(). The correct - // sequence for a single-tap-up gesture should be: onDown(), - // onSingleTapUp() and onUp(). - // The call sequence for a pinch out gesture in JB is: onDown(), - // then onUp() and there's - // no onSingleTapUp(). Base on these observations, the following - // condition is added to - // filter out the false alarm where onSingleTapUp() is called within - // a pinch out - // gesture. The framework fix went into ICS. Refer to b/4588114. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - if ((mHolding & HOLD_TOUCH_DOWN) == 0) return true; - } - - // We do this in addition to onUp() because we want the snapback of - // setFilmMode to happen. - mHolding &= ~HOLD_TOUCH_DOWN; - - if (mListener != null) { - // Do the inverse transform of the touch coordinates. - final Matrix m = getGLRoot().getCompensationMatrix(); - final Matrix inv = new Matrix(); - m.invert(inv); - final float[] pts = new float[]{x, y}; - inv.mapPoints(pts); - mListener.onSingleTapUp((int) (pts[0] + 0.5f), (int) (pts[1] + 0.5f)); - } - return true; - } - - @Override - public void onUp() { - mHolding &= ~HOLD_TOUCH_DOWN; - mEdgeView.onRelease(); - - if (mIgnoreUpEvent) { - mIgnoreUpEvent = false; - return; - } - - snapback(); - } - - private boolean flingImages(final float velocityX, final float velocityY) { - final int vx = (int) (velocityX + 0.5f); - final int vy = (int) (velocityY + 0.5f); - return mPositionController.flingPage(vx, vy); - } - - private void startExtraScalingIfNeeded() { - if (!mCancelExtraScalingPending) { - mHandler.sendEmptyMessageDelayed(MSG_CANCEL_EXTRA_SCALING, 700); - mPositionController.setExtraScalingRange(true); - mCancelExtraScalingPending = true; - } - } - - private void stopExtraScalingIfNeeded() { - if (mCancelExtraScalingPending) { - mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING); - mPositionController.setExtraScalingRange(false); - mCancelExtraScalingPending = false; - } - } - } - - private class MyHandler extends SynchronizedHandler { - - private MyHandler(final ImageViewerGLActivityOld activity) { - super(activity.getGLRoot()); - } - - @Override - public void handleMessage(final Message message) { - switch (message.what) { - case MSG_CANCEL_EXTRA_SCALING: { - mGestureRecognizer.cancelScale(); - mPositionController.setExtraScalingRange(false); - mCancelExtraScalingPending = false; - break; - } - case MSG_CAPTURE_ANIMATION_DONE: { - // message.arg1 is the offset parameter passed to - // switchWithCaptureAnimation(). - captureAnimationDone(message.arg1); - break; - } - default: - throw new AssertionError(message.what); - } - } - } - - private interface Picture { - void draw(GLCanvas canvas, Rect r); - - void forceSize(); // called when mCompensation changes - - Size getSize(); - - void reload(); - - void setScreenNail(ScreenNail s); - } - - class EventListener implements PositionController.Listener { - - @Override - public boolean isHoldingDown() { - return (mHolding & HOLD_TOUCH_DOWN) != 0; - } - - @Override - public void onAbsorb(final int velocity, final int direction) { - mEdgeView.onAbsorb(velocity, direction); - } - - @Override - public void onInvalidate() { - invalidate(); - } - - @Override - public void onPull(final int offset, final int direction) { - mEdgeView.onPull(offset, direction); - } - } - - class FullPicture implements Picture { - private int mRotation; - private final Size mSize = new Size(); - - @Override - public void draw(final GLCanvas canvas, final Rect r) { - drawTileView(canvas, r); - - // We want to have the following transitions: - // (1) Move camera preview out of its place: switch to film mode - // (2) Move camera preview into its place: switch to page mode - // The extra mWasCenter check makes sure (1) does not apply if in - // page mode, we move _to_ the camera preview from another picture. - - // Holdings except touch-down prevent the transitions. - if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return; - - if (mWantPictureCenterCallbacks && mPositionController.isCenter()) { - mListener.onPictureCenter(); - } - } - - @Override - public void forceSize() { - updateSize(); - mPositionController.forceImageSize(mSize); - } - - @Override - public Size getSize() { - return mSize; - } - - @Override - public void reload() { - // mImageWidth and mImageHeight will get updated - mTileView.notifyModelInvalidated(); - setScreenNail(mModel.getScreenNail()); - updateSize(); - } - - @Override - public void setScreenNail(final ScreenNail s) { - mTileView.setScreenNail(s); - } - - private void drawTileView(final GLCanvas canvas, final Rect r) { - final float imageScale = mPositionController.getImageScale(); - final int viewW = getWidth(); - final int viewH = getHeight(); - final float cx = r.exactCenterX(); - final float cy = r.exactCenterY(); - - canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA); - // Draw the tile view. - setTileViewPosition(cx, cy, viewW, viewH, imageScale); - renderChild(canvas, mTileView); - - // Draw the play video icon and the message. - canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f)); - - canvas.restore(); - } - - // Set the position of the tile view - private void setTileViewPosition(final float cx, final float cy, final int viewW, final int viewH, - final float scale) { - // Find out the bitmap coordinates of the center of the view - final int imageW = mPositionController.getImageWidth(); - final int imageH = mPositionController.getImageHeight(); - final int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f); - final int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f); - - final int inverseX = imageW - centerX; - final int inverseY = imageH - centerY; - int x, y; - switch (mRotation) { - case 0: - x = centerX; - y = centerY; - break; - case 90: - x = centerY; - y = inverseX; - break; - case 180: - x = inverseX; - y = inverseY; - break; - case 270: - x = inverseY; - y = centerX; - break; - default: - throw new RuntimeException(String.valueOf(mRotation)); - } - mTileView.setPosition(x, y, scale, mRotation); - } - - private void updateSize() { - mRotation = mModel.getImageRotation(); - - final int w = mTileView.mImageWidth; - final int h = mTileView.mImageHeight; - mSize.width = getRotated(mRotation, w, h); - mSize.height = getRotated(mRotation, h, w); - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/PositionController.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/PositionController.java deleted file mode 100644 index ed150e67c..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/PositionController.java +++ /dev/null @@ -1,1019 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.Rect; - -import org.mariotaku.gallery3d.ui.PhotoView.Size; -import org.mariotaku.gallery3d.util.GalleryUtils; - -class PositionController { - - public static final int IMAGE_AT_LEFT_EDGE = 1; - public static final int IMAGE_AT_RIGHT_EDGE = 2; - public static final int IMAGE_AT_TOP_EDGE = 4; - public static final int IMAGE_AT_BOTTOM_EDGE = 8; - - public static final int CAPTURE_ANIMATION_TIME = 700; - public static final int SNAPBACK_ANIMATION_TIME = 600; - - // Special values for animation time. - private static final long NO_ANIMATION = -1; - private static final long LAST_ANIMATION = -2; - - private static final int ANIM_KIND_NONE = -1; - private static final int ANIM_KIND_SCROLL = 0; - private static final int ANIM_KIND_SCALE = 1; - private static final int ANIM_KIND_SNAPBACK = 2; - private static final int ANIM_KIND_SLIDE = 3; - private static final int ANIM_KIND_ZOOM = 4; - private static final int ANIM_KIND_OPENING = 5; - private static final int ANIM_KIND_FLING = 6; - - // Animation time in milliseconds. The order must match ANIM_KIND_* above. - // - // The values for ANIM_KIND_FLING_X does't matter because we use - // mFilmScroller.isFinished() to decide when to stop. We set it to 0 so it's - // faster for Animatable.advanceAnimation() to calculate the progress - // (always 1). - private static final int ANIM_TIME[] = { 0, // ANIM_KIND_SCROLL - 0, // ANIM_KIND_SCALE - SNAPBACK_ANIMATION_TIME, // ANIM_KIND_SNAPBACK - 400, // ANIM_KIND_SLIDE - 300, // ANIM_KIND_ZOOM - 300, // ANIM_KIND_OPENING - 0, // ANIM_KIND_FLING (the duration is calculated dynamically) - 0, // ANIM_KIND_FLING_X (see the comment above) - 0, // ANIM_KIND_DELETE (the duration is calculated dynamically) - CAPTURE_ANIMATION_TIME, // ANIM_KIND_CAPTURE - }; - - // We try to scale up the image to fill the screen. But in order not to - // scale too much for small icons, we limit the max up-scaling factor here. - private static final float SCALE_LIMIT = 4; - - // For user's gestures, we give a temporary extra scaling range which goes - // above or below the usual scaling limits. - private static final float SCALE_MIN_EXTRA = 0.7f; - private static final float SCALE_MAX_EXTRA = 1.4f; - - // Setting this true makes the extra scaling range permanent (until this is - // set to false again). - private boolean mExtraScalingRange = false; - - private static final int HORIZONTAL_SLACK = GalleryUtils.dpToPixel(12); - - private final Listener mListener; - private volatile Rect mOpenAnimationRect; - - // Use a large enough value, so we won't see the gray shadow in the - // beginning. - private int mViewW = 1200; - private int mViewH = 1200; - - // A scaling gesture is in progress. - private boolean mInScale; - // The focus point of the scaling gesture, relative to the center of the - // picture in bitmap pixels. - private float mFocusX, mFocusY; - - // This is used by the fling animation (page mode). - private final FlingScroller mPageScroller; - - // The bound of the stable region that the focused box can stay, see the - // comments above calculateStableBound() for details. - private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom; - - // Constrained frame is a rectangle that the focused box should fit into if - // it is constrained. It has two effects: - // - // (1) In page mode, if the focused box is constrained, scaling for the - // focused box is adjusted to fit into the constrained frame, instead of the - // whole view. - // - // (2) In page mode, if the focused box is constrained, the mPlatform's - // default center (mDefaultX/Y) is moved to the center of the constrained - // frame, instead of the view center. - // - private final Rect mConstrainedFrame = new Rect(); - - // Whether the focused box is constrained. - // - // Our current program's first call to moveBox() sets constrained = true, so - // we set the initial value of this variable to true, and we will not see - // see unwanted transition animation. - private final boolean mConstrained = true; - - private final Platform mPlatform = new Platform(); - private final Box mBox; - - // The output of the PositionController. Available through getPosition(). - private final Rect mRect; - - public PositionController(final Listener listener) { - mListener = listener; - mPageScroller = new FlingScroller(); - - // Initialize the areas. - initPlatform(); - mBox = new Box(); - initBox(); - mRect = new Rect(); - } - - public void advanceAnimation() { - boolean changed = false; - changed |= mPlatform.advanceAnimation(); - changed |= mBox.advanceAnimation(); - if (changed) { - redraw(); - } - } - - public void beginScale(float focusX, float focusY) { - focusX -= mViewW / 2; - focusY -= mViewH / 2; - final Platform p = mPlatform; - mInScale = true; - mFocusX = (int) ((focusX - p.mCurrentX) / mBox.mCurrentScale + 0.5f); - mFocusY = (int) ((focusY - mBox.mCurrentY) / mBox.mCurrentScale + 0.5f); - } - - public void endScale() { - mInScale = false; - snapAndRedraw(); - } - - public boolean flingPage(int velocityX, int velocityY) { - - final Platform p = mPlatform; - - // We only want to do fling when the picture is zoomed-in. - if (viewWiderThanScaledImage(mBox.mCurrentScale) && viewTallerThanScaledImage(mBox.mCurrentScale)) - return false; - - // We only allow flinging in the directions where it won't go over the - // picture. - final int edges = getImageAtEdges(); - if (velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0 || velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0) { - velocityX = 0; - } - if (velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0 || velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0) { - velocityY = 0; - } - - if (velocityX == 0 && velocityY == 0) return false; - - mPageScroller.fling(p.mCurrentX, mBox.mCurrentY, velocityX, velocityY, mBoundLeft, mBoundRight, mBoundTop, - mBoundBottom); - final int targetX = mPageScroller.getFinalX(); - final int targetY = mPageScroller.getFinalY(); - ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration(); - return startAnimation(targetX, targetY, mBox.mCurrentScale, ANIM_KIND_FLING); - } - - public void forceImageSize(final Size s) { - if (s.width == 0 || s.height == 0) return; - mBox.mImageW = s.width; - mBox.mImageH = s.height; - return; - } - - public int getImageAtEdges() { - - final Platform p = mPlatform; - calculateStableBound(mBox.mCurrentScale); - int edges = 0; - if (p.mCurrentX <= mBoundLeft) { - edges |= IMAGE_AT_RIGHT_EDGE; - } - if (p.mCurrentX >= mBoundRight) { - edges |= IMAGE_AT_LEFT_EDGE; - } - if (mBox.mCurrentY <= mBoundTop) { - edges |= IMAGE_AT_BOTTOM_EDGE; - } - if (mBox.mCurrentY >= mBoundBottom) { - edges |= IMAGE_AT_TOP_EDGE; - } - return edges; - } - - public int getImageHeight() { - - return mBox.mImageH; - } - - public float getImageScale() { - - return mBox.mCurrentScale; - } - - public int getImageWidth() { - - return mBox.mImageW; - } - - // Returns the position of a box. - public Rect getPosition() { - return mRect; - } - - // Returns the index of the box which contains the given point (x, y) - // Returns Integer.MAX_VALUE if there is no hit. There may be more than - // one box contains the given point, and we want to give priority to the - // one closer to the focused index (0). - public int hitTest(final int x, final int y) { - final Rect r = mRect; - if (r.contains(x, y)) return 0; - - return Integer.MAX_VALUE; - } - - public boolean isAtMinimalScale() { - - return isAlmostEqual(mBox.mCurrentScale, mBox.mScaleMin); - } - - // ////////////////////////////////////////////////////////////////////////// - // Start an animations for the focused box - // ////////////////////////////////////////////////////////////////////////// - - public boolean isCenter() { - - return mPlatform.mCurrentX == mPlatform.mDefaultX && mBox.mCurrentY == 0; - } - - public boolean isScrolling() { - return mPlatform.mAnimationStartTime != NO_ANIMATION && mPlatform.mCurrentX != mPlatform.mToX; - } - - public void resetToFullView() { - - startAnimation(mPlatform.mDefaultX, 0, mBox.mScaleMin, ANIM_KIND_ZOOM); - } - - // Scales the image by the given factor. - // Returns an out-of-range indicator: - // 1 if the intended scale is too large for the stable range. - // 0 if the intended scale is in the stable range. - // -1 if the intended scale is too small for the stable range. - public int scaleBy(float s, float focusX, float focusY) { - focusX -= mViewW / 2; - focusY -= mViewH / 2; - - // We want to keep the focus point (on the bitmap) the same as when we - // begin the scale gesture, that is, - // - // (focusX' - currentX') / scale' = (focusX - currentX) / scale - // - s = mBox.clampScale(s * getTargetScale(mBox)); - final int x = (int) (focusX - s * mFocusX + 0.5f); - final int y = (int) (focusY - s * mFocusY + 0.5f); - startAnimation(x, y, s, ANIM_KIND_SCALE); - if (s < mBox.mScaleMin) return -1; - if (s > mBox.mScaleMax) return 1; - return 0; - } - - public void scrollPage(final int dx, final int dy) { - if (!canScroll()) return; - - calculateStableBound(mBox.mCurrentScale); - - int x = mPlatform.mCurrentX + dx; - int y = mBox.mCurrentY + dy; - - // Vertical direction: If we have space to move in the vertical - // direction, we show the edge effect when scrolling reaches the edge. - if (mBoundTop != mBoundBottom) { - if (y < mBoundTop) { - mListener.onPull(mBoundTop - y, EdgeView.BOTTOM); - } else if (y > mBoundBottom) { - mListener.onPull(y - mBoundBottom, EdgeView.TOP); - } - } - - y = GalleryUtils.clamp(y, mBoundTop, mBoundBottom); - - // Horizontal direction: we show the edge effect when the scrolling - // tries to go left of the first image or go right of the last image. - if (mBoundLeft != mBoundRight) { - if (x > mBoundRight) { - mListener.onPull(x - mBoundRight, EdgeView.LEFT); - } else if (x < mBoundLeft) { - mListener.onPull(mBoundLeft - x, EdgeView.RIGHT); - } - } - - x = GalleryUtils.clamp(x, mBoundLeft, mBoundRight); - - startAnimation(x, y, mBox.mCurrentScale, ANIM_KIND_SCROLL); - } - - public void setConstrainedFrame(final Rect cFrame) { - if (mConstrainedFrame.equals(cFrame)) return; - mConstrainedFrame.set(cFrame); - mPlatform.updateDefaultXY(); - updateScaleAndGapLimit(); - snapAndRedraw(); - } - - public void setExtraScalingRange(final boolean enabled) { - if (mExtraScalingRange == enabled) return; - mExtraScalingRange = enabled; - if (!enabled) { - snapAndRedraw(); - } - } - - public void setFilmMode(final boolean enabled) { - mPlatform.updateDefaultXY(); - updateScaleAndGapLimit(); - stopAnimation(); - snapAndRedraw(); - } - - public void setImageSize(final Size s, final Rect cFrame) { - if (s.width == 0 || s.height == 0) return; - - boolean needUpdate = false; - if (cFrame != null && !mConstrainedFrame.equals(cFrame)) { - mConstrainedFrame.set(cFrame); - mPlatform.updateDefaultXY(); - needUpdate = true; - } - needUpdate |= setBoxSize(s.width, s.height, false); - - if (!needUpdate) return; - updateScaleAndGapLimit(); - snapAndRedraw(); - } - - public void setOpenAnimationRect(final Rect r) { - mOpenAnimationRect = r; - } - - public void setViewSize(final int viewW, final int viewH) { - if (viewW == mViewW && viewH == mViewH) return; - - final boolean wasMinimal = isAtMinimalScale(); - - mViewW = viewW; - mViewH = viewH; - initPlatform(); - - setBoxSize(viewW, viewH, true); - - updateScaleAndGapLimit(); - - // If the focused box was at minimal scale, we try to make it the - // minimal scale under the new view size. - if (wasMinimal) { - - mBox.mCurrentScale = mBox.mScaleMin; - } - - // If we have the opening animation, do it. Otherwise go directly to the - // right position. - if (!startOpeningAnimationIfNeeded()) { - skipToFinalPosition(); - } - } - - public void skipAnimation() { - if (mPlatform.mAnimationStartTime != NO_ANIMATION) { - mPlatform.mCurrentX = mPlatform.mToX; - mPlatform.mCurrentY = mPlatform.mToY; - mPlatform.mAnimationStartTime = NO_ANIMATION; - } - - if (mBox.mAnimationStartTime != NO_ANIMATION) { - mBox.mCurrentY = mBox.mToY; - mBox.mCurrentScale = mBox.mToScale; - mBox.mAnimationStartTime = NO_ANIMATION; - } - - redraw(); - } - - public void skipToFinalPosition() { - stopAnimation(); - snapAndRedraw(); - skipAnimation(); - } - - public void snapback() { - snapAndRedraw(); - } - - // ////////////////////////////////////////////////////////////////////////// - // Layout - // ////////////////////////////////////////////////////////////////////////// - - public void zoomIn(float tapX, float tapY, float targetScale) { - tapX -= mViewW / 2; - tapY -= mViewH / 2; - - // Convert the tap position to distance to center in bitmap coordinates - final float tempX = (tapX - mPlatform.mCurrentX) / mBox.mCurrentScale; - final float tempY = (tapY - mBox.mCurrentY) / mBox.mCurrentScale; - - final int x = (int) (-tempX * targetScale + 0.5f); - final int y = (int) (-tempY * targetScale + 0.5f); - - calculateStableBound(targetScale); - final int targetX = GalleryUtils.clamp(x, mBoundLeft, mBoundRight); - final int targetY = GalleryUtils.clamp(y, mBoundTop, mBoundBottom); - targetScale = GalleryUtils.clamp(targetScale, mBox.mScaleMin, mBox.mScaleMax); - - startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM); - } - - private void calculateStableBound(final float scale) { - calculateStableBound(scale, 0); - } - - // Calculates the stable region of mPlatform.mCurrentX and - // mBox.mCurrentY, where "stable" means - // - // (1) If the dimension of scaled image >= view dimension, we will not - // see black region outside the image (at that dimension). - // (2) If the dimension of scaled image < view dimension, we will center - // the scaled image. - // - // We might temporarily go out of this stable during user interaction, - // but will "snap back" after user stops interaction. - // - // The results are stored in mBound{Left/Right/Top/Bottom}. - // - // An extra parameter "horizontalSlack" (which has the value of 0 usually) - // is used to extend the stable region by some pixels on each side - // horizontally. - private void calculateStableBound(final float scale, final int horizontalSlack) { - - // The width and height of the box in number of view pixels - final int w = widthOf(mBox, scale); - final int h = heightOf(mBox, scale); - - // When the edge of the view is aligned with the edge of the box - mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack; - mBoundRight = w / 2 - mViewW / 2 + horizontalSlack; - mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2; - mBoundBottom = h / 2 - mViewH / 2; - - // If the scaled height is smaller than the view height, - // force it to be in the center. - if (viewTallerThanScaledImage(scale)) { - mBoundTop = mBoundBottom = 0; - } - - // Same for width - if (viewWiderThanScaledImage(scale)) { - mBoundLeft = mBoundRight = mPlatform.mDefaultX; - } - } - - // Only allow scrolling when we are not currently in an animation or we - // are in some animation with can be interrupted. - private boolean canScroll() { - - if (mBox.mAnimationStartTime == NO_ANIMATION) return true; - switch (mBox.mAnimationKind) { - case ANIM_KIND_SCROLL: - case ANIM_KIND_FLING: - return true; - } - return false; - } - - private void convertBoxToRect() { - - final Rect r = mRect; - final int y = mBox.mCurrentY + mPlatform.mCurrentY + mViewH / 2; - final int w = widthOf(mBox); - final int h = heightOf(mBox); - final int x = mPlatform.mCurrentX + mViewW / 2; - r.left = x - w / 2; - r.right = r.left + w; - r.top = y - h / 2; - r.bottom = r.top + h; - } - - private float getMaximalScale(final Box b) { - if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b); - return SCALE_LIMIT; - } - - private float getMinimalScale(final Box b) { - final float wFactor = 1.0f; - final float hFactor = 1.0f; - int viewW, viewH; - - if (mConstrained && !mConstrainedFrame.isEmpty() && b == mBox) { - viewW = mConstrainedFrame.width(); - viewH = mConstrainedFrame.height(); - } else { - viewW = mViewW; - viewH = mViewH; - } - - final float s = Math.min(wFactor * viewW / b.mImageW, hFactor * viewH / b.mImageH); - return Math.min(SCALE_LIMIT, s); - } - - private float getTargetScale(final Box b) { - return b.mAnimationStartTime == NO_ANIMATION ? b.mCurrentScale : b.mToScale; - } - - // Returns the display height of this box. - private int heightOf(final Box b) { - return (int) (b.mImageH * b.mCurrentScale + 0.5f); - } - - // Returns the display height of this box, using the given scale. - private int heightOf(final Box b, final float scale) { - return (int) (b.mImageH * scale + 0.5f); - } - - // ////////////////////////////////////////////////////////////////////////// - // Public utilities - // ////////////////////////////////////////////////////////////////////////// - - // Initialize a box to have the size of the view. - private void initBox() { - mBox.mImageW = mViewW; - mBox.mImageH = mViewH; - mBox.mUseViewSize = true; - mBox.mScaleMin = getMinimalScale(mBox); - mBox.mScaleMax = getMaximalScale(mBox); - mBox.mCurrentY = 0; - mBox.mCurrentScale = mBox.mScaleMin; - mBox.mAnimationStartTime = NO_ANIMATION; - mBox.mAnimationKind = ANIM_KIND_NONE; - } - - // Initialize the platform to be at the view center. - private void initPlatform() { - mPlatform.updateDefaultXY(); - mPlatform.mCurrentX = mPlatform.mDefaultX; - mPlatform.mCurrentY = mPlatform.mDefaultY; - mPlatform.mAnimationStartTime = NO_ANIMATION; - } - - // Convert the information in mPlatform and mBoxes to mRects, so the user - // can get the position of each box by getPosition(). - // - // Note we go from center-out because each box's X coordinate - // is relative to its anchor box (except the focused box). - private void layoutAndSetPosition() { - convertBoxToRect(); - } - - // ////////////////////////////////////////////////////////////////////////// - // Redraw - // - // If a method changes box positions directly, redraw() - // should be called. - // - // If a method may also cause a snapback to happen, snapAndRedraw() should - // be called. - // - // If a method starts an animation to change the position of focused box, - // startAnimation() should be called. - // - // If time advances to change the box position, advanceAnimation() should - // be called. - // ////////////////////////////////////////////////////////////////////////// - private void redraw() { - layoutAndSetPosition(); - mListener.onInvalidate(); - } - - // Returns false if the box size doesn't change. - private boolean setBoxSize(final int width, final int height, final boolean isViewSize) { - - final boolean wasViewSize = mBox.mUseViewSize; - - // If we already have an image size, we don't want to use the view size. - if (!wasViewSize && isViewSize) return false; - - mBox.mUseViewSize = isViewSize; - - if (width == mBox.mImageW && height == mBox.mImageH) return false; - - // The ratio of the old size and the new size. - // - // If the aspect ratio changes, we don't know if it is because one side - // grows or the other side shrinks. Currently we just assume the view - // angle of the longer side doesn't change (so the aspect ratio change - // is because the view angle of the shorter side changes). This matches - // what camera preview does. - final float ratio = width > height ? (float) mBox.mImageW / width : (float) mBox.mImageH / height; - - mBox.mImageW = width; - mBox.mImageH = height; - - mBox.mCurrentScale = getMinimalScale(mBox); - mBox.mAnimationStartTime = NO_ANIMATION; - - mFocusX /= ratio; - mFocusY /= ratio; - - return true; - } - - private void snapAndRedraw() { - mPlatform.startSnapback(); - mBox.startSnapback(); - redraw(); - } - - private boolean startAnimation(final int targetX, final int targetY, final float targetScale, final int kind) { - boolean changed = false; - changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind); - changed |= mBox.doAnimation(targetY, targetScale, kind); - if (changed) { - redraw(); - } - return changed; - } - - private boolean startOpeningAnimationIfNeeded() { - if (mOpenAnimationRect == null) return false; - - if (mBox.mUseViewSize) return false; - - // Start animation from the saved rectangle if we have one. - final Rect r = mOpenAnimationRect; - mOpenAnimationRect = null; - - mPlatform.mCurrentX = r.centerX() - mViewW / 2; - mBox.mCurrentY = r.centerY() - mViewH / 2; - mBox.mCurrentScale = Math.max(r.width() / (float) mBox.mImageW, r.height() / (float) mBox.mImageH); - startAnimation(mPlatform.mDefaultX, 0, mBox.mScaleMin, ANIM_KIND_OPENING); - - return true; - } - - // ////////////////////////////////////////////////////////////////////////// - // Private utilities - // ////////////////////////////////////////////////////////////////////////// - - // Stop all animations at where they are now. - private void stopAnimation() { - mPlatform.mAnimationStartTime = NO_ANIMATION; - mBox.mAnimationStartTime = NO_ANIMATION; - } - - // This should be called whenever the scale range of boxes or the default - // gap size may change. Currently this can happen due to change of view - // size, image size, mFilmMode, mConstrained, and mConstrainedFrame. - private void updateScaleAndGapLimit() { - - mBox.mScaleMin = getMinimalScale(mBox); - mBox.mScaleMax = getMaximalScale(mBox); - - } - - private boolean viewTallerThanScaledImage(final float scale) { - return mViewH >= heightOf(mBox, scale); - } - - private boolean viewWiderThanScaledImage(final float scale) { - return mViewW >= widthOf(mBox, scale); - } - - // Returns the display width of this box. - private int widthOf(final Box b) { - return (int) (b.mImageW * b.mCurrentScale + 0.5f); - } - - // Returns the display width of this box, using the given scale. - private int widthOf(final Box b, final float scale) { - return (int) (b.mImageW * scale + 0.5f); - } - - private static boolean isAlmostEqual(final float a, final float b) { - final float diff = a - b; - return (diff < 0 ? -diff : diff) < 0.02f; - } - - public interface Listener { - boolean isHoldingDown(); - - void onAbsorb(int velocity, int direction); - - void onInvalidate(); - - // EdgeView - void onPull(int offset, int direction); - - } - - // ////////////////////////////////////////////////////////////////////////// - // Animatable: an thing which can do animation. - // ////////////////////////////////////////////////////////////////////////// - private abstract static class Animatable { - public long mAnimationStartTime; - public int mAnimationKind; - public int mAnimationDuration; - - // Returns true if the animation values changes, so things need to be - // redrawn. - public boolean advanceAnimation() { - if (mAnimationStartTime == NO_ANIMATION) return false; - if (mAnimationStartTime == LAST_ANIMATION) { - mAnimationStartTime = NO_ANIMATION; - return startSnapback(); - } - - float progress; - if (mAnimationDuration == 0) { - progress = 1; - } else { - final long now = AnimationTime.get(); - progress = (float) (now - mAnimationStartTime) / mAnimationDuration; - } - - if (progress >= 1) { - progress = 1; - } else { - progress = applyInterpolationCurve(mAnimationKind, progress); - } - - final boolean done = interpolate(progress); - - if (done) { - mAnimationStartTime = LAST_ANIMATION; - } - - return true; - } - - public abstract boolean startSnapback(); - - // This should be overridden in subclass to change the animation values - // give the progress value in [0, 1]. - protected abstract boolean interpolate(float progress); - - private static float applyInterpolationCurve(final int kind, float progress) { - final float f = 1 - progress; - switch (kind) { - case ANIM_KIND_SCROLL: - case ANIM_KIND_FLING: - progress = 1 - f; // linear - break; - case ANIM_KIND_OPENING: - case ANIM_KIND_SCALE: - progress = 1 - f * f; // quadratic - break; - case ANIM_KIND_SNAPBACK: - case ANIM_KIND_ZOOM: - case ANIM_KIND_SLIDE: - progress = 1 - f * f * f * f * f; // x^5 - break; - } - return progress; - } - } - - // ////////////////////////////////////////////////////////////////////////// - // Box: represents a rectangular area which shows a picture. - // ////////////////////////////////////////////////////////////////////////// - private class Box extends Animatable { - // Size of the bitmap - public int mImageW, mImageH; - - // This is true if we assume the image size is the same as view size - // until we know the actual size of image. This is also used to - // determine if there is an image ready to show. - public boolean mUseViewSize; - - // The minimum and maximum scale we allow for this box. - public float mScaleMin, mScaleMax; - - // The X/Y value indicates where the center of the box is on the view - // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the - // actual values used currently. Note that the X values are implicitly - // defined by Platform and Gaps. - public int mCurrentY, mFromY, mToY; - public float mCurrentScale, mFromScale, mToScale; - - // Clamps the input scale to the range that doAnimation() can reach. - public float clampScale(final float s) { - return GalleryUtils.clamp(s, SCALE_MIN_EXTRA * mScaleMin, SCALE_MAX_EXTRA * mScaleMax); - } - - @Override - public boolean startSnapback() { - if (mAnimationStartTime != NO_ANIMATION) return false; - if (mAnimationKind == ANIM_KIND_SCROLL && mListener.isHoldingDown()) return false; - if (mInScale && this == mBox) return false; - - int y = mCurrentY; - float scale; - - if (this == mBox) { - final float scaleMin = mExtraScalingRange ? mScaleMin * SCALE_MIN_EXTRA : mScaleMin; - final float scaleMax = mExtraScalingRange ? mScaleMax * SCALE_MAX_EXTRA : mScaleMax; - scale = GalleryUtils.clamp(mCurrentScale, scaleMin, scaleMax); - calculateStableBound(scale, HORIZONTAL_SLACK); - // If the picture is zoomed-in, we want to keep the focus - // point stay in the same position on screen. See the - // comment in Platform.startSnapback for details. - if (!viewTallerThanScaledImage(scale)) { - final float scaleDiff = mCurrentScale - scale; - y += (int) (mFocusY * scaleDiff + 0.5f); - } - y = GalleryUtils.clamp(y, mBoundTop, mBoundBottom); - } else { - y = 0; - scale = mScaleMin; - } - - if (mCurrentY != y || mCurrentScale != scale) return doAnimation(y, scale, ANIM_KIND_SNAPBACK); - return false; - } - - @Override - protected boolean interpolate(final float progress) { - if (mAnimationKind == ANIM_KIND_FLING) - return interpolateFlingPage(progress); - else - return interpolateLinear(progress); - } - - private boolean doAnimation(final int targetY, float targetScale, final int kind) { - targetScale = clampScale(targetScale); - - if (mCurrentY == targetY && mCurrentScale == targetScale) return false; - - // Now starts an animation for the box. - mAnimationKind = kind; - mFromY = mCurrentY; - mFromScale = mCurrentScale; - mToY = targetY; - mToScale = targetScale; - mAnimationStartTime = AnimationTime.startTime(); - mAnimationDuration = ANIM_TIME[kind]; - advanceAnimation(); - return true; - } - - private boolean interpolateFlingPage(final float progress) { - mPageScroller.computeScrollOffset(progress); - calculateStableBound(mCurrentScale); - - final int oldY = mCurrentY; - mCurrentY = mPageScroller.getCurrY(); - - // Check if we hit the edges; show edge effects if we do. - if (oldY > mBoundTop && mCurrentY == mBoundTop) { - final int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f); - mListener.onAbsorb(v, EdgeView.BOTTOM); - } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) { - final int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f); - mListener.onAbsorb(v, EdgeView.TOP); - } - - return progress >= 1; - } - - private boolean interpolateLinear(final float progress) { - if (progress >= 1) { - mCurrentY = mToY; - mCurrentScale = mToScale; - return true; - } else { - mCurrentY = (int) (mFromY + progress * (mToY - mFromY)); - mCurrentScale = mFromScale + progress * (mToScale - mFromScale); - return mCurrentY == mToY && mCurrentScale == mToScale; - } - } - } - - // ////////////////////////////////////////////////////////////////////////// - // Platform: captures the global X/Y movement. - // ////////////////////////////////////////////////////////////////////////// - private class Platform extends Animatable { - public int mCurrentX, mFromX, mToX, mDefaultX; - public int mCurrentY, mFromY, mToY, mDefaultY; - - @Override - public boolean startSnapback() { - if (mAnimationStartTime != NO_ANIMATION) return false; - if (mAnimationKind == ANIM_KIND_SCROLL && mListener.isHoldingDown()) return false; - if (mInScale) return false; - - final float scaleMin = mExtraScalingRange ? mBox.mScaleMin * SCALE_MIN_EXTRA : mBox.mScaleMin; - final float scaleMax = mExtraScalingRange ? mBox.mScaleMax * SCALE_MAX_EXTRA : mBox.mScaleMax; - final float scale = GalleryUtils.clamp(mBox.mCurrentScale, scaleMin, scaleMax); - int x = mCurrentX; - final int y = mDefaultY; - calculateStableBound(scale, HORIZONTAL_SLACK); - // If the picture is zoomed-in, we want to keep the focus point - // stay in the same position on screen, so we need to adjust - // target mCurrentX (which is the center of the focused - // box). The position of the focus point on screen (relative the - // the center of the view) is: - // - // mCurrentX + scale * mFocusX = mCurrentX' + scale' * mFocusX - // => mCurrentX' = mCurrentX + (scale - scale') * mFocusX - // - if (!viewWiderThanScaledImage(scale)) { - final float scaleDiff = mBox.mCurrentScale - scale; - x += (int) (mFocusX * scaleDiff + 0.5f); - } - x = GalleryUtils.clamp(x, mBoundLeft, mBoundRight); - if (mCurrentX != x || mCurrentY != y) return doAnimation(x, y, ANIM_KIND_SNAPBACK); - return false; - } - - // The updateDefaultXY() should be called whenever these variables - // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4) - // mFilmMode - public void updateDefaultXY() { - // We don't check mFilmMode and return 0 for mDefaultX. Because - // otherwise if we decide to leave film mode because we are - // centered, we will immediately back into film mode because we find - // we are not centered. - if (mConstrained && !mConstrainedFrame.isEmpty()) { - mDefaultX = mConstrainedFrame.centerX() - mViewW / 2; - mDefaultY = mConstrainedFrame.centerY() - mViewH / 2; - } else { - mDefaultX = 0; - mDefaultY = 0; - } - } - - @Override - protected boolean interpolate(final float progress) { - if (mAnimationKind == ANIM_KIND_FLING) - return interpolateFlingPage(progress); - else - return interpolateLinear(progress); - } - - // Starts an animation for the platform. - private boolean doAnimation(final int targetX, final int targetY, final int kind) { - if (mCurrentX == targetX && mCurrentY == targetY) return false; - mAnimationKind = kind; - mFromX = mCurrentX; - mFromY = mCurrentY; - mToX = targetX; - mToY = targetY; - mAnimationStartTime = AnimationTime.startTime(); - mAnimationDuration = ANIM_TIME[kind]; - advanceAnimation(); - return true; - } - - private boolean interpolateFlingPage(final float progress) { - mPageScroller.computeScrollOffset(progress); - - calculateStableBound(mBox.mCurrentScale); - - final int oldX = mCurrentX; - mCurrentX = mPageScroller.getCurrX(); - - // Check if we hit the edges; show edge effects if we do. - if (oldX > mBoundLeft && mCurrentX == mBoundLeft) { - final int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f); - mListener.onAbsorb(v, EdgeView.RIGHT); - } else if (oldX < mBoundRight && mCurrentX == mBoundRight) { - final int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f); - mListener.onAbsorb(v, EdgeView.LEFT); - } - - return progress >= 1; - } - - private boolean interpolateLinear(final float progress) { - // Other animations - if (progress >= 1) { - mCurrentX = mToX; - mCurrentY = mToY; - return true; - } else { - mCurrentX = (int) (mFromX + progress * (mToX - mFromX)); - mCurrentY = (int) (mFromY + progress * (mToY - mFromY)); - return mCurrentX == mToX && mCurrentY == mToY; - } - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/ResourceTexture.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/ResourceTexture.java deleted file mode 100644 index 816675220..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/ResourceTexture.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; - -import org.mariotaku.gallery3d.util.GalleryUtils; - -// ResourceTexture is a texture whose Bitmap is decoded from a resource. -// By default ResourceTexture is not opaque. -public class ResourceTexture extends UploadedTexture { - - protected final Context mContext; - protected final int mResId; - - public ResourceTexture(final Context context, final int resId) { - mContext = GalleryUtils.checkNotNull(context); - mResId = resId; - setOpaque(false); - } - - @Override - protected void onFreeBitmap(final Bitmap bitmap) { - if (!inFinalizer()) { - bitmap.recycle(); - } - } - - @Override - protected Bitmap onGetBitmap() { - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - return BitmapFactory.decodeResource(mContext.getResources(), mResId, options); - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/ScreenNail.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/ScreenNail.java deleted file mode 100644 index bbc8ae0a7..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/ScreenNail.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.RectF; - -public interface ScreenNail { - public void draw(GLCanvas canvas, int x, int y, int width, int height); - - // This is only used by TileImageView to back up the tiles not yet loaded. - public void draw(GLCanvas canvas, RectF source, RectF dest); - - public int getHeight(); - - public int getWidth(); - - // We do not need to draw this ScreenNail in this frame. - public void noDraw(); - - // This ScreenNail will not be used anymore. Release related resources. - public void recycle(); -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/SynchronizedHandler.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/SynchronizedHandler.java deleted file mode 100644 index 0a3ea5f8c..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/SynchronizedHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.os.Handler; -import android.os.Message; - -public class SynchronizedHandler extends Handler { - - protected final GLRoot mGLRoot; - - public SynchronizedHandler(final GLRoot glRoot) { - mGLRoot = glRoot; - } - - @Override - public void dispatchMessage(final Message message) { - if (mGLRoot != null) { - mGLRoot.lockRenderThread(); - } - try { - super.dispatchMessage(message); - } finally { - if (mGLRoot != null) { - mGLRoot.unlockRenderThread(); - } - } - } - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/Texture.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/Texture.java deleted file mode 100644 index 64cebed73..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/Texture.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -// Texture is a rectangular image which can be drawn on GLCanvas. -// The isOpaque() function gives a hint about whether the texture is opaque, -// so the drawing can be done faster. -// -// This is the current texture hierarchy: -// -// Texture -// -- ColorTexture -// -- FadeInTexture -// -- BasicTexture -// -- UploadedTexture -// -- BitmapTexture -// -- Tile -// -- ResourceTexture -// -- NinePatchTexture -// -- CanvasTexture -// -- StringTexture -// -public interface Texture { - public void draw(GLCanvas canvas, int x, int y); - - public void draw(GLCanvas canvas, int x, int y, int w, int h); - - public int getHeight(); - - public int getWidth(); - - public boolean isOpaque(); -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java deleted file mode 100644 index 215d3c599..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java +++ /dev/null @@ -1,759 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.Bitmap; -import android.graphics.Rect; -import android.graphics.RectF; -import android.support.v4.util.LongSparseArray; -import android.util.FloatMath; -import android.util.Log; - -import org.mariotaku.gallery3d.ImageViewerGLActivityOld; -import org.mariotaku.gallery3d.util.ApiHelper; -import org.mariotaku.gallery3d.util.BitmapPool; -import org.mariotaku.gallery3d.util.DecodeUtils; -import org.mariotaku.gallery3d.util.Future; -import org.mariotaku.gallery3d.util.GalleryUtils; -import org.mariotaku.gallery3d.util.ThreadPool; -import org.mariotaku.gallery3d.util.ThreadPool.CancelListener; -import org.mariotaku.gallery3d.util.ThreadPool.JobContext; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class TileImageView extends GLView { - public static final int SIZE_UNKNOWN = -1; - - private static final String TAG = "TileImageView"; - - // TILE_SIZE must be 2^N - 2. We put one pixel border in each side of the - // texture to avoid seams between tiles. - private static int TILE_SIZE; - private static final int TILE_BORDER = 1; - private static int BITMAP_SIZE; - private static final int UPLOAD_LIMIT = 1; - - private static BitmapPool sTilePool; - - /* - * This is the tile state in the CPU side. Life of a Tile: ACTIVATED - * (initial state) --> IN_QUEUE - by queueForDecode() --> RECYCLED - by - * recycleTile() IN_QUEUE --> DECODING - by decodeTile() --> RECYCLED - by - * recycleTile) DECODING --> RECYCLING - by recycleTile() --> DECODED - by - * decodeTile() --> DECODE_FAIL - by decodeTile() RECYCLING --> RECYCLED - - * by decodeTile() DECODED --> ACTIVATED - (after the decoded bitmap is - * uploaded) DECODED --> RECYCLED - by recycleTile() DECODE_FAIL -> RECYCLED - * - by recycleTile() RECYCLED --> ACTIVATED - by obtainTile() - */ - private static final int STATE_ACTIVATED = 0x01; - private static final int STATE_IN_QUEUE = 0x02; - private static final int STATE_DECODING = 0x04; - private static final int STATE_DECODED = 0x08; - private static final int STATE_DECODE_FAIL = 0x10; - private static final int STATE_RECYCLING = 0x20; - private static final int STATE_RECYCLED = 0x40; - - private PhotoView.ITileImageAdapter mModel; - private ScreenNail mScreenNail; - protected int mLevelCount; // cache the value of mScaledBitmaps.length - - // The mLevel variable indicates which level of bitmap we should use. - // Level 0 means the original full-sized bitmap, and a larger value means - // a smaller scaled bitmap (The width and height of each scaled bitmap is - // half size of the previous one). If the value is in [0, mLevelCount), we - // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value - // is mLevelCount, and that means we use mScreenNail for display. - private int mLevel = 0; - - // The offsets of the (left, top) of the upper-left tile to the (left, top) - // of the view. - private int mOffsetX; - private int mOffsetY; - - private int mUploadQuota; - private boolean mRenderComplete; - - private final RectF mSourceRect = new RectF(); - private final RectF mTargetRect = new RectF(); - - private final LongSparseArray mActiveTiles = new LongSparseArray(); - - // The following three queue is guarded by TileImageView.this - private final TileQueue mRecycledQueue = new TileQueue(); - private final TileQueue mUploadQueue = new TileQueue(); - private final TileQueue mDecodeQueue = new TileQueue(); - - // The width and height of the full-sized bitmap - protected int mImageWidth = SIZE_UNKNOWN; - protected int mImageHeight = SIZE_UNKNOWN; - - protected int mCenterX; - protected int mCenterY; - protected float mScale; - protected int mRotation; - - // Temp variables to avoid memory allocation - private final Rect mTileRange = new Rect(); - private final Rect mActiveRange[] = {new Rect(), new Rect()}; - - private final TileUploader mTileUploader = new TileUploader(); - private boolean mIsTextureFreed; - private Future mTileDecoder; - private final ThreadPool mThreadPool; - private boolean mBackgroundTileUploaded; - - public TileImageView(final ImageViewerGLActivityOld context) { - mThreadPool = context.getThreadPool(); - mTileDecoder = mThreadPool.submit(new TileDecoder()); - if (TILE_SIZE == 0) { - if (GalleryUtils.isHighResolution(context)) { - TILE_SIZE = 510; - } else { - TILE_SIZE = 254; - } - BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2; - sTilePool = ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER ? new BitmapPool(BITMAP_SIZE, - BITMAP_SIZE, 128) : null; - } - } - - public void freeTextures() { - mIsTextureFreed = true; - - if (mTileDecoder != null) { - mTileDecoder.cancel(); - mTileDecoder.get(); - mTileDecoder = null; - } - - final int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile texture = mActiveTiles.valueAt(i); - texture.recycle(); - } - mActiveTiles.clear(); - mTileRange.set(0, 0, 0, 0); - - synchronized (this) { - mUploadQueue.clean(); - mDecodeQueue.clean(); - Tile tile = mRecycledQueue.pop(); - while (tile != null) { - tile.recycle(); - tile = mRecycledQueue.pop(); - } - } - setScreenNail(null); - if (sTilePool != null) { - sTilePool.clear(); - } - } - - public void notifyModelInvalidated() { - invalidateTiles(); - if (mModel == null) { - mScreenNail = null; - mImageWidth = 0; - mImageHeight = 0; - mLevelCount = 0; - } else { - setScreenNail(mModel.getScreenNail()); - mImageWidth = mModel.getImageWidth(); - mImageHeight = mModel.getImageHeight(); - mLevelCount = mModel.getLevelCount(); - } - layoutTiles(mCenterX, mCenterY, mScale, mRotation); - invalidate(); - } - - public void prepareTextures() { - if (mTileDecoder == null) { - mTileDecoder = mThreadPool.submit(new TileDecoder()); - } - if (mIsTextureFreed) { - layoutTiles(mCenterX, mCenterY, mScale, mRotation); - mIsTextureFreed = false; - setScreenNail(mModel == null ? null : mModel.getScreenNail()); - } - } - - public void setModel(final PhotoView.ITileImageAdapter model) { - mModel = model; - if (model != null) { - notifyModelInvalidated(); - } - } - - public boolean setPosition(final int centerX, final int centerY, final float scale, final int rotation) { - if (mCenterX == centerX && mCenterY == centerY && mScale == scale && mRotation == rotation) - return false; - mCenterX = centerX; - mCenterY = centerY; - mScale = scale; - mRotation = rotation; - layoutTiles(centerX, centerY, scale, rotation); - invalidate(); - return true; - } - - public void setScreenNail(final ScreenNail s) { - mScreenNail = s; - } - - @Override - protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { - super.onLayout(changeSize, left, top, right, bottom); - if (changeSize) { - layoutTiles(mCenterX, mCenterY, mScale, mRotation); - } - } - - @Override - protected void render(final GLCanvas canvas) { - mUploadQuota = UPLOAD_LIMIT; - mRenderComplete = true; - - final int level = mLevel; - final int rotation = mRotation; - int flags = 0; - if (rotation != 0) { - flags |= GLCanvas.SAVE_FLAG_MATRIX; - } - - if (flags != 0) { - canvas.save(flags); - if (rotation != 0) { - final int centerX = getWidth() / 2, centerY = getHeight() / 2; - canvas.translate(centerX, centerY); - canvas.rotate(rotation, 0, 0, 1); - canvas.translate(-centerX, -centerY); - } - } - try { - if (level != mLevelCount && !isScreenNailAnimating()) { - if (mScreenNail != null) { - mScreenNail.noDraw(); - } - - final int size = TILE_SIZE << level; - final float length = size * mScale; - final Rect r = mTileRange; - - for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) { - final float y = mOffsetY + i * length; - for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) { - final float x = mOffsetX + j * length; - drawTile(canvas, tx, ty, level, x, y, length); - } - } - } else if (mScreenNail != null) { - mScreenNail.draw(canvas, mOffsetX, mOffsetY, Math.round(mImageWidth * mScale), - Math.round(mImageHeight * mScale)); - if (isScreenNailAnimating()) { - invalidate(); - } - } - } finally { - if (flags != 0) { - canvas.restore(); - } - } - - if (mRenderComplete) { - if (!mBackgroundTileUploaded) { - uploadBackgroundTiles(canvas); - } - } else { - invalidate(); - } - } - - private void activateTile(final int x, final int y, final int level) { - final long key = makeTileKey(x, y, level); - Tile tile = mActiveTiles.get(key); - if (tile != null) { - if (tile.mTileState == STATE_IN_QUEUE) { - tile.mTileState = STATE_ACTIVATED; - } - return; - } - tile = obtainTile(x, y, level); - mActiveTiles.put(key, tile); - } - - private boolean decodeTile(final Tile tile) { - synchronized (this) { - if (tile.mTileState != STATE_IN_QUEUE) return false; - tile.mTileState = STATE_DECODING; - } - final boolean decodeComplete = tile.decode(); - synchronized (this) { - if (tile.mTileState == STATE_RECYCLING) { - tile.mTileState = STATE_RECYCLED; - if (tile.mDecodedTile != null) { - if (sTilePool != null) { - sTilePool.recycle(tile.mDecodedTile); - } - tile.mDecodedTile = null; - } - mRecycledQueue.push(tile); - return false; - } - tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL; - return decodeComplete; - } - } - - // Draw the tile to a square at canvas that locates at (x, y) and - // has a side length of length. - private void drawTile(final GLCanvas canvas, final int tx, final int ty, final int level, final float x, - final float y, final float length) { - final RectF source = mSourceRect; - final RectF target = mTargetRect; - target.set(x, y, x + length, y + length); - source.set(0, 0, TILE_SIZE, TILE_SIZE); - - final Tile tile = getTile(tx, ty, level); - if (tile != null) { - if (!tile.isContentValid()) { - if (tile.mTileState == STATE_DECODED) { - if (mUploadQuota > 0) { - --mUploadQuota; - tile.updateContent(canvas); - } else { - mRenderComplete = false; - } - } else if (tile.mTileState != STATE_DECODE_FAIL) { - mRenderComplete = false; - queueForDecode(tile); - } - } - if (drawTile(tile, canvas, source, target)) return; - } - if (mScreenNail != null) { - final int size = TILE_SIZE << level; - final float scaleX = (float) mScreenNail.getWidth() / mImageWidth; - final float scaleY = (float) mScreenNail.getHeight() / mImageHeight; - source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, (ty + size) * scaleY); - mScreenNail.draw(canvas, source, target); - } - } - - // If the bitmap is scaled by the given factor "scale", return the - // rectangle containing visible range. The left-top coordinate returned is - // aligned to the tile boundary. - // - // (cX, cY) is the point on the original bitmap which will be put in the - // center of the ImageViewer. - private void getRange(final Rect out, final int cX, final int cY, final int level, final float scale, - final int rotation) { - - final double radians = Math.toRadians(-rotation); - final double w = getWidth(); - final double h = getHeight(); - - final double cos = Math.cos(radians); - final double sin = Math.sin(radians); - final int width = (int) Math.ceil(Math.max(Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h))); - final int height = (int) Math.ceil(Math.max(Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h))); - - int left = (int) FloatMath.floor(cX - width / (2f * scale)); - int top = (int) FloatMath.floor(cY - height / (2f * scale)); - int right = (int) FloatMath.ceil(left + width / scale); - int bottom = (int) FloatMath.ceil(top + height / scale); - - // align the rectangle to tile boundary - final int size = TILE_SIZE << level; - left = Math.max(0, size * (left / size)); - top = Math.max(0, size * (top / size)); - right = Math.min(mImageWidth, right); - bottom = Math.min(mImageHeight, bottom); - - out.set(left, top, right, bottom); - } - - private void getRange(final Rect out, final int cX, final int cY, final int level, final int rotation) { - getRange(out, cX, cY, level, 1f / (1 << level + 1), rotation); - } - - private Tile getTile(final int x, final int y, final int level) { - return mActiveTiles.get(makeTileKey(x, y, level)); - } - - private synchronized void invalidateTiles() { - mDecodeQueue.clean(); - mUploadQueue.clean(); - - // TODO disable decoder - final int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile tile = mActiveTiles.valueAt(i); - recycleTile(tile); - } - mActiveTiles.clear(); - } - - private boolean isScreenNailAnimating() { - return false; - } - - // Prepare the tiles we want to use for display. - // - // 1. Decide the tile level we want to use for display. - // 2. Decide the tile levels we want to keep as texture (in addition to - // the one we use for display). - // 3. Recycle unused tiles. - // 4. Activate the tiles we want. - private void layoutTiles(final int centerX, final int centerY, final float scale, final int rotation) { - // The width and height of this view. - final int width = getWidth(); - final int height = getHeight(); - - // The tile levels we want to keep as texture is in the range - // [fromLevel, endLevel). - int fromLevel; - int endLevel; - - // We want to use a texture larger than or equal to the display size. - mLevel = GalleryUtils.clamp(GalleryUtils.floorLog2(1f / scale), 0, mLevelCount); - - // We want to keep one more tile level as texture in addition to what - // we use for display. So it can be faster when the scale moves to the - // next level. We choose a level closer to the current scale. - if (mLevel != mLevelCount) { - final Rect range = mTileRange; - getRange(range, centerX, centerY, mLevel, scale, rotation); - mOffsetX = Math.round(width / 2f + (range.left - centerX) * scale); - mOffsetY = Math.round(height / 2f + (range.top - centerY) * scale); - fromLevel = scale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel; - } else { - // Activate the tiles of the smallest two levels. - fromLevel = mLevel - 2; - mOffsetX = Math.round(width / 2f - centerX * scale); - mOffsetY = Math.round(height / 2f - centerY * scale); - } - - fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2)); - endLevel = Math.min(fromLevel + 2, mLevelCount); - - final Rect range[] = mActiveRange; - for (int i = fromLevel; i < endLevel; ++i) { - getRange(range[i - fromLevel], centerX, centerY, i, rotation); - } - - // If rotation is transient, don't update the tile. - if (rotation % 90 != 0) return; - - synchronized (this) { - mDecodeQueue.clean(); - mUploadQueue.clean(); - mBackgroundTileUploaded = false; - - // Recycle unused tiles: if the level of the active tile is outside - // the - // range [fromLevel, endLevel) or not in the visible range. - int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile tile = mActiveTiles.valueAt(i); - final int level = tile.mTileLevel; - if (level < fromLevel || level >= endLevel || !range[level - fromLevel].contains(tile.mX, tile.mY)) { - mActiveTiles.removeAt(i); - i--; - n--; - recycleTile(tile); - } - } - } - - for (int i = fromLevel; i < endLevel; ++i) { - final int size = TILE_SIZE << i; - final Rect r = range[i - fromLevel]; - for (int y = r.top, bottom = r.bottom; y < bottom; y += size) { - for (int x = r.left, right = r.right; x < right; x += size) { - activateTile(x, y, i); - } - } - } - invalidate(); - } - - private synchronized Tile obtainTile(final int x, final int y, final int level) { - final Tile tile = mRecycledQueue.pop(); - if (tile != null) { - tile.mTileState = STATE_ACTIVATED; - tile.update(x, y, level); - return tile; - } - return new Tile(x, y, level); - } - - private synchronized void queueForDecode(final Tile tile) { - if (tile.mTileState == STATE_ACTIVATED) { - tile.mTileState = STATE_IN_QUEUE; - if (mDecodeQueue.push(tile)) { - notifyAll(); - } - } - } - - private void queueForUpload(final Tile tile) { - synchronized (this) { - mUploadQueue.push(tile); - } - if (mTileUploader.mActive.compareAndSet(false, true)) { - getGLRoot().addOnGLIdleListener(mTileUploader); - } - } - - private synchronized void recycleTile(final Tile tile) { - if (tile.mTileState == STATE_DECODING) { - tile.mTileState = STATE_RECYCLING; - return; - } - tile.mTileState = STATE_RECYCLED; - if (tile.mDecodedTile != null) { - if (sTilePool != null) { - sTilePool.recycle(tile.mDecodedTile); - } - tile.mDecodedTile = null; - } - mRecycledQueue.push(tile); - } - - private void uploadBackgroundTiles(final GLCanvas canvas) { - mBackgroundTileUploaded = true; - final int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile tile = mActiveTiles.valueAt(i); - if (!tile.isContentValid()) { - queueForDecode(tile); - } - } - } - - private static boolean drawTile(Tile tile, final GLCanvas canvas, final RectF source, final RectF target) { - while (true) { - if (tile.isContentValid()) { - // offset source rectangle for the texture border. - source.offset(TILE_BORDER, TILE_BORDER); - canvas.drawTexture(tile, source, target); - return true; - } - - // Parent can be divided to four quads and tile is one of the four. - final Tile parent = tile.getParentTile(); - if (parent == null) return false; - if (tile.mX == parent.mX) { - source.left /= 2f; - source.right /= 2f; - } else { - source.left = (TILE_SIZE + source.left) / 2f; - source.right = (TILE_SIZE + source.right) / 2f; - } - if (tile.mY == parent.mY) { - source.top /= 2f; - source.bottom /= 2f; - } else { - source.top = (TILE_SIZE + source.top) / 2f; - source.bottom = (TILE_SIZE + source.bottom) / 2f; - } - tile = parent; - } - } - - private static long makeTileKey(final int x, final int y, final int level) { - long result = x; - result = result << 16 | y; - result = result << 16 | level; - return result; - } - - private class Tile extends UploadedTexture { - public int mX; - public int mY; - public int mTileLevel; - public Tile mNext; - public Bitmap mDecodedTile; - public volatile int mTileState = STATE_ACTIVATED; - - public Tile(final int x, final int y, final int level) { - mX = x; - mY = y; - mTileLevel = level; - } - - public Tile getParentTile() { - if (mTileLevel + 1 == mLevelCount) return null; - final int size = TILE_SIZE << mTileLevel + 1; - final int x = size * (mX / size); - final int y = size * (mY / size); - return getTile(x, y, mTileLevel + 1); - } - - @Override - public int getTextureHeight() { - return TILE_SIZE + TILE_BORDER * 2; - } - - // We override getTextureWidth() and getTextureHeight() here, so the - // texture can be re-used for different tiles regardless of the actual - // size of the tile (which may be small because it is a tile at the - // boundary). - @Override - public int getTextureWidth() { - return TILE_SIZE + TILE_BORDER * 2; - } - - @Override - public String toString() { - return String.format("tile(%s, %s, %s / %s)", mX / TILE_SIZE, mY / TILE_SIZE, mLevel, mLevelCount); - } - - public void update(final int x, final int y, final int level) { - mX = x; - mY = y; - mTileLevel = level; - invalidateContent(); - } - - @Override - protected void onFreeBitmap(final Bitmap bitmap) { - if (sTilePool != null) { - sTilePool.recycle(bitmap); - } - } - - @Override - protected Bitmap onGetBitmap() { - GalleryUtils.assertTrue(mTileState == STATE_DECODED); - - // We need to override the width and height, so that we won't - // draw beyond the boundaries. - final int rightEdge = (mImageWidth - mX >> mTileLevel) + TILE_BORDER; - final int bottomEdge = (mImageHeight - mY >> mTileLevel) + TILE_BORDER; - setSize(Math.min(BITMAP_SIZE, rightEdge), Math.min(BITMAP_SIZE, bottomEdge)); - - final Bitmap bitmap = mDecodedTile; - mDecodedTile = null; - mTileState = STATE_ACTIVATED; - return bitmap; - } - - boolean decode() { - // Get a tile from the original image. The tile is down-scaled - // by (1 << mTilelevel) from a region in the original image. - try { - mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(mTileLevel, mX, mY, TILE_SIZE, - TILE_BORDER, sTilePool)); - } catch (final Throwable t) { - Log.w(TAG, "fail to decode tile", t); - } - return mDecodedTile != null; - } - } - - private class TileDecoder implements ThreadPool.Job { - - private final CancelListener mNotifier = new CancelListener() { - @Override - public void onCancel() { - synchronized (TileImageView.this) { - TileImageView.this.notifyAll(); - } - } - }; - - @Override - public Void run(final JobContext jc) { - jc.setMode(ThreadPool.MODE_NONE); - jc.setCancelListener(mNotifier); - while (!jc.isCancelled()) { - Tile tile = null; - synchronized (TileImageView.this) { - tile = mDecodeQueue.pop(); - if (tile == null && !jc.isCancelled()) { - GalleryUtils.waitWithoutInterrupt(TileImageView.this); - } - } - if (tile == null) { - continue; - } - if (decodeTile(tile)) { - queueForUpload(tile); - } - } - return null; - } - } - - private static class TileQueue { - private Tile mHead; - - public void clean() { - mHead = null; - } - - public Tile pop() { - final Tile tile = mHead; - if (tile != null) { - mHead = tile.mNext; - } - return tile; - } - - public boolean push(final Tile tile) { - final boolean wasEmpty = mHead == null; - tile.mNext = mHead; - mHead = tile; - return wasEmpty; - } - } - - private class TileUploader implements GLRoot.OnGLIdleListener { - AtomicBoolean mActive = new AtomicBoolean(false); - - @Override - public boolean onGLIdle(final GLCanvas canvas, final boolean renderRequested) { - // Skips uploading if there is a pending rendering request. - // Returns true to keep uploading in next rendering loop. - if (renderRequested) return true; - int quota = UPLOAD_LIMIT; - Tile tile = null; - while (quota > 0) { - synchronized (TileImageView.this) { - tile = mUploadQueue.pop(); - } - if (tile == null) { - break; - } - if (!tile.isContentValid()) { - final boolean hasBeenLoaded = tile.isLoaded(); - if (tile.mTileState != STATE_DECODED) return false; - tile.updateContent(canvas); - if (!hasBeenLoaded) { - tile.draw(canvas, 0, 0); - } - --quota; - } - } - if (tile == null) { - mActive.set(false); - } - return tile != null; - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/UploadedTexture.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/UploadedTexture.java deleted file mode 100644 index 447772827..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/UploadedTexture.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.ui; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.opengl.GLUtils; - -import org.mariotaku.gallery3d.util.GalleryUtils; - -import java.util.HashMap; - -import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; - -// UploadedTextures use a Bitmap for the content of the texture. -// -// Subclasses should implement onGetBitmap() to provide the Bitmap and -// implement onFreeBitmap(mBitmap) which will be called when the Bitmap -// is not needed anymore. -// -// isContentValid() is meaningful only when the isLoaded() returns true. -// It means whether the content needs to be updated. -// -// The user of this class should call recycle() when the texture is not -// needed anymore. -// -// By default an UploadedTexture is opaque (so it can be drawn faster without -// blending). The user or subclass can override it using setOpaque(). -abstract class UploadedTexture extends BasicTexture { - - // To prevent keeping allocation the borders, we store those used borders - // here. - // Since the length will be power of two, it won't use too much memory. - private static HashMap sBorderLines = new HashMap(); - private static BorderKey sBorderKey = new BorderKey(); - - @SuppressWarnings("unused") - private static final String TAG = "Texture"; - private boolean mContentValid = true; - - // indicate this textures is being uploaded in background - private final boolean mIsUploading = false; - private boolean mOpaque = true; - private static int sUploadedCount; - private static final int UPLOAD_LIMIT = 100; - - protected Bitmap mBitmap; - private int mBorder; - - static int[] sTextureId = new int[1]; - - static float[] sCropRect = new float[4]; - - protected UploadedTexture() { - this(false); - } - - protected UploadedTexture(final boolean hasBorder) { - super(null, 0, STATE_UNLOADED); - if (hasBorder) { - setBorder(true); - mBorder = 1; - } - } - - @Override - public int getHeight() { - if (mWidth == UNSPECIFIED) { - getBitmap(); - } - return mHeight; - } - - @Override - public int getWidth() { - if (mWidth == UNSPECIFIED) { - getBitmap(); - } - return mWidth; - } - - /** - * Whether the content on GPU is valid. - */ - public boolean isContentValid() { - return isLoaded() && mContentValid; - } - - @Override - public boolean isOpaque() { - return mOpaque; - } - - public boolean isUploading() { - return mIsUploading; - } - - @Override - public void recycle() { - super.recycle(); - if (mBitmap != null) { - freeBitmap(); - } - } - - public void setOpaque(final boolean isOpaque) { - mOpaque = isOpaque; - } - - /** - * Updates the content on GPU's memory. - * - * @param canvas - */ - public void updateContent(final GLCanvas canvas) { - if (!isLoaded()) { - uploadToCanvas(canvas); - } else if (!mContentValid) { - final Bitmap bitmap = getBitmap(); - if (bitmap == null) return; - final int format = GLUtils.getInternalFormat(bitmap); - final int type = GLUtils.getType(bitmap); - canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder, bitmap, format, type); - freeBitmap(); - mContentValid = true; - } - } - - @Override - protected int getTarget() { - return GL11.GL_TEXTURE_2D; - } - - protected void invalidateContent() { - if (mBitmap != null) { - freeBitmap(); - } - mContentValid = false; - mWidth = UNSPECIFIED; - mHeight = UNSPECIFIED; - } - - @Override - protected boolean onBind(final GLCanvas canvas) { - updateContent(canvas); - return isContentValid(); - } - - protected abstract void onFreeBitmap(Bitmap bitmap); - - protected abstract Bitmap onGetBitmap(); - - private void freeBitmap() { - GalleryUtils.assertTrue(mBitmap != null); - onFreeBitmap(mBitmap); - mBitmap = null; - } - - private Bitmap getBitmap() { - if (mBitmap == null) { - mBitmap = onGetBitmap(); - final int w = mBitmap.getWidth() + mBorder * 2; - final int h = mBitmap.getHeight() + mBorder * 2; - if (mWidth == UNSPECIFIED) { - setSize(w, h); - } - } - return mBitmap; - } - - private void uploadToCanvas(final GLCanvas canvas) { - final GL11 gl = canvas.getGLInstance(); - - final Bitmap bitmap = getBitmap(); - if (bitmap != null) { - try { - final int bWidth = bitmap.getWidth(); - final int bHeight = bitmap.getHeight(); - final int texWidth = getTextureWidth(); - final int texHeight = getTextureHeight(); - - GalleryUtils.assertTrue(bWidth <= texWidth && bHeight <= texHeight); - - // Define a vertically flipped crop rectangle for - // OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = mBorder; - sCropRect[1] = mBorder + bHeight; - sCropRect[2] = bWidth; - sCropRect[3] = -bHeight; - - // Upload the bitmap to a new texture. - GLId.glGenTextures(1, sTextureId, 0); - gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]); - gl.glTexParameterfv(GL11.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - - if (bWidth == texWidth && bHeight == texHeight) { - GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0); - } else { - final int format = GLUtils.getInternalFormat(bitmap); - final int type = GLUtils.getType(bitmap); - final Config config = bitmap.getConfig(); - - gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, texWidth, texHeight, 0, format, type, null); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder, bitmap, format, type); - - if (mBorder > 0) { - // Left border - Bitmap line = getBorderLine(true, config, texHeight); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, line, format, type); - - // Top border - line = getBorderLine(false, config, texWidth); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, line, format, type); - } - - // Right border - if (mBorder + bWidth < texWidth) { - final Bitmap line = getBorderLine(true, config, texHeight); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder + bWidth, 0, line, format, type); - } - - // Bottom border - if (mBorder + bHeight < texHeight) { - final Bitmap line = getBorderLine(false, config, texWidth); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, mBorder + bHeight, line, format, type); - } - } - } finally { - freeBitmap(); - } - // Update texture state. - setAssociatedCanvas(canvas); - mId = sTextureId[0]; - mState = STATE_LOADED; - mContentValid = true; - } else { - mState = STATE_ERROR; - // throw new RuntimeException("Texture load fail, no bitmap"); - } - } - - public static void resetUploadLimit() { - sUploadedCount = 0; - } - - public static boolean uploadLimitReached() { - return sUploadedCount > UPLOAD_LIMIT; - } - - private static Bitmap getBorderLine(final boolean vertical, final Config config, final int length) { - final BorderKey key = sBorderKey; - key.vertical = vertical; - key.config = config; - key.length = length; - Bitmap bitmap = sBorderLines.get(key); - if (bitmap == null) { - bitmap = vertical ? Bitmap.createBitmap(1, length, config) : Bitmap.createBitmap(length, 1, config); - sBorderLines.put(key.clone(), bitmap); - } - return bitmap; - } - - private static class BorderKey implements Cloneable { - public boolean vertical; - public Config config; - public int length; - - @Override - public BorderKey clone() { - try { - return (BorderKey) super.clone(); - } catch (final CloneNotSupportedException e) { - throw new AssertionError(e); - } - } - - @Override - public boolean equals(final Object object) { - if (!(object instanceof BorderKey)) return false; - final BorderKey o = (BorderKey) object; - return vertical == o.vertical && config == o.config && length == o.length; - } - - @Override - public int hashCode() { - final int x = config.hashCode() ^ length; - return vertical ? x : -x; - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/ApiHelper.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/ApiHelper.java deleted file mode 100644 index 22e486acf..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/ApiHelper.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -import android.os.Build; - -public class ApiHelper { - public static final boolean USE_888_PIXEL_FORMAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - - public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapPool.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapPool.java deleted file mode 100644 index baea4dd31..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapPool.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -import android.graphics.Bitmap; - -import java.util.ArrayList; - -public class BitmapPool { - @SuppressWarnings("unused") - private static final String TAG = "BitmapPool"; - - private final ArrayList mPool; - private final int mPoolLimit; - - // mOneSize is true if the pool can only cache Bitmap with one size. - private final boolean mOneSize; - private final int mWidth, mHeight; // only used if mOneSize is true - - // Construct a BitmapPool which caches bitmap with the specified size. - public BitmapPool(final int width, final int height, final int poolLimit) { - mWidth = width; - mHeight = height; - mPoolLimit = poolLimit; - mPool = new ArrayList(poolLimit); - mOneSize = true; - } - - public synchronized void clear() { - mPool.clear(); - } - - // Get a Bitmap from the pool. - public synchronized Bitmap getBitmap() { - GalleryUtils.assertTrue(mOneSize); - final int size = mPool.size(); - return size > 0 ? mPool.remove(size - 1) : null; - } - - public boolean isOneSize() { - return mOneSize; - } - - // Put a Bitmap into the pool, if the Bitmap has a proper size. Otherwise - // the Bitmap will be recycled. If the pool is full, an old Bitmap will be - // recycled. - public void recycle(final Bitmap bitmap) { - if (bitmap == null) return; - if (mOneSize && (bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight)) { - bitmap.recycle(); - return; - } - synchronized (this) { - if (mPool.size() >= mPoolLimit) { - mPool.remove(0); - } - mPool.add(bitmap); - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/DecodeUtils.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/DecodeUtils.java deleted file mode 100644 index f1feb0615..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/DecodeUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; - -public class DecodeUtils { - - // TODO: This function should not be called directly from - // DecodeUtils.requestDecode(...), since we don't have the knowledge - // if the bitmap will be uploaded to GL. - public static Bitmap ensureGLCompatibleBitmap(final Bitmap bitmap) { - if (bitmap == null || bitmap.getConfig() != null) return bitmap; - final Bitmap newBitmap = bitmap.copy(Config.RGB_565, false); - bitmap.recycle(); - return newBitmap; - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/Future.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/Future.java deleted file mode 100644 index 39557c036..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/Future.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -// This Future differs from the java.util.concurrent.Future in these aspects: -// -// - Once cancel() is called, isCancelled() always returns true. It is a sticky -// flag used to communicate to the implementation. The implmentation may -// ignore that flag. Regardless whether the Future is cancelled, a return -// value will be provided to get(). The implementation may choose to return -// null if it finds the Future is cancelled. -// -// - get() does not throw exceptions. -// -public interface Future { - public void cancel(); - - public T get(); - - public boolean isCancelled(); - - public boolean isDone(); - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/FutureListener.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/FutureListener.java deleted file mode 100644 index d2ace6625..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/FutureListener.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -public interface FutureListener { - public void onFutureDone(Future future); - - public void onFutureStart(Future future); -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java deleted file mode 100644 index eba45eccd..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -import android.content.Context; -import android.graphics.Color; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.WindowManager; - -import java.io.Closeable; - -public class GalleryUtils { - - private static float sPixelDensity = -1f; - private static final String TAG = "Utils"; - - // Throws AssertionError if the input is false. - public static void assertTrue(final boolean cond) { - if (!cond) throw new AssertionError(); - } - - public static int ceilLog2(final float value) { - int i; - for (i = 0; i < 31; i++) { - if (1 << i >= value) { - break; - } - } - return i; - } - - // Throws NullPointerException if the input is null. - public static T checkNotNull(final T object) { - if (object == null) throw new NullPointerException(); - return object; - } - - // Returns the input value x clamped to the range [min, max]. - public static float clamp(final float x, final float min, final float max) { - if (x > max) return max; - if (x < min) return min; - return x; - } - - // Returns the input value x clamped to the range [min, max]. - public static int clamp(final int x, final int min, final int max) { - if (x > max) return max; - if (x < min) return min; - return x; - } - - public static void closeSilently(final Closeable c) { - if (c == null) return; - try { - c.close(); - } catch (final Throwable t) { - Log.w(TAG, "close fail", t); - } - } - - public static int dpToPixel(final int dp) { - return Math.round(dpToPixel((float) dp)); - } - - public static int floorLog2(final float value) { - int i; - for (i = 0; i < 31; i++) { - if (1 << i > value) { - break; - } - } - return i - 1; - } - - public static void initialize(final Context context) { - final DisplayMetrics metrics = new DisplayMetrics(); - final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getMetrics(metrics); - sPixelDensity = metrics.density; - } - - public static float[] intColorToFloatARGBArray(final int from) { - return new float[] { Color.alpha(from) / 255f, Color.red(from) / 255f, Color.green(from) / 255f, - Color.blue(from) / 255f }; - } - - public static boolean isHighResolution(final Context context) { - final DisplayMetrics metrics = new DisplayMetrics(); - final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getMetrics(metrics); - return metrics.heightPixels > 2048 || metrics.widthPixels > 2048; - } - - public static boolean isOpaque(final int color) { - return color >>> 24 == 0xFF; - } - - public static void waitWithoutInterrupt(final Object object) { - try { - object.wait(); - } catch (final InterruptedException e) { - Log.w(TAG, "unexpected interrupt: " + object); - } - } - - private static float dpToPixel(final float dp) { - return sPixelDensity * dp; - } - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/IntArray.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/IntArray.java deleted file mode 100644 index 5ea6064e2..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/IntArray.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -public class IntArray { - private static final int INIT_CAPACITY = 8; - - private int mData[] = new int[INIT_CAPACITY]; - private int mSize = 0; - - public void add(final int value) { - if (mData.length == mSize) { - final int temp[] = new int[mSize + mSize]; - System.arraycopy(mData, 0, temp, 0, mSize); - mData = temp; - } - mData[mSize++] = value; - } - - public void clear() { - mSize = 0; - if (mData.length != INIT_CAPACITY) { - mData = new int[INIT_CAPACITY]; - } - } - - public int[] getInternalArray() { - return mData; - } - - public int size() { - return mSize; - } - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/MotionEventHelper.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/MotionEventHelper.java deleted file mode 100644 index f895e60a0..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/MotionEventHelper.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -import android.graphics.Matrix; -import android.view.MotionEvent; - -public final class MotionEventHelper { - - private MotionEventHelper() { - throw new AssertionError(); - } - - public static MotionEvent transformEvent(final MotionEvent e, final Matrix m) { - final MotionEvent newEvent = MotionEvent.obtain(e); - newEvent.transform(m); - return newEvent; - } -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/PriorityThreadFactory.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/PriorityThreadFactory.java deleted file mode 100644 index 07936161d..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/PriorityThreadFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -import android.os.Process; - -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A thread factory that creates threads with a given thread priority. - */ -public class PriorityThreadFactory implements ThreadFactory { - - private final int mPriority; - private final AtomicInteger mNumber = new AtomicInteger(); - private final String mName; - - public PriorityThreadFactory(final String name, final int priority) { - mName = name; - mPriority = priority; - } - - @Override - public Thread newThread(final Runnable r) { - return new Thread(r, mName + '-' + mNumber.getAndIncrement()) { - @Override - public void run() { - Process.setThreadPriority(mPriority); - super.run(); - } - }; - } - -} diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/ThreadPool.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/ThreadPool.java deleted file mode 100644 index a3c30288c..000000000 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/ThreadPool.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package org.mariotaku.gallery3d.util; - -import android.util.Log; - -import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class ThreadPool { - @SuppressWarnings("unused") - private static final String TAG = "ThreadPool"; - private static final int CORE_POOL_SIZE = 4; - private static final int MAX_POOL_SIZE = 8; - private static final int KEEP_ALIVE_TIME = 10; // 10 seconds - - // Resource type - public static final int MODE_NONE = 0; - public static final int MODE_CPU = 1; - public static final int MODE_NETWORK = 2; - - ResourceCounter mCpuCounter = new ResourceCounter(2); - ResourceCounter mNetworkCounter = new ResourceCounter(2); - - private final Executor mExecutor; - - public ThreadPool() { - this(CORE_POOL_SIZE, MAX_POOL_SIZE); - } - - private ThreadPool(final int initPoolSize, final int maxPoolSize) { - mExecutor = new ThreadPoolExecutor(initPoolSize, maxPoolSize, KEEP_ALIVE_TIME, TimeUnit.SECONDS, - new LinkedBlockingQueue(), new PriorityThreadFactory("thread-pool", - android.os.Process.THREAD_PRIORITY_BACKGROUND)); - } - - public Future submit(final Job job) { - return submit(job, null); - } - - // Submit a job to the thread pool. The listener will be called when the - // job is finished (or cancelled). - private Future submit(final Job job, final FutureListener listener) { - final Worker w = new Worker(job, listener); - mExecutor.execute(w); - return w; - } - - public interface CancelListener { - public void onCancel(); - } - - // A Job is like a Callable, but it has an addition JobContext parameter. - public interface Job { - public T run(JobContext jc); - } - - public interface JobContext { - boolean isCancelled(); - - void setCancelListener(CancelListener listener); - - boolean setMode(int mode); - } - - private static class ResourceCounter { - public int value; - - public ResourceCounter(final int v) { - value = v; - } - } - - private class Worker implements Runnable, Future, JobContext { - private static final String TAG = "Worker"; - private final Job mJob; - private final FutureListener mListener; - private CancelListener mCancelListener; - private ResourceCounter mWaitOnResource; - private volatile boolean mIsCancelled; - private boolean mIsDone; - private T mResult; - private int mMode; - - public Worker(final Job job, final FutureListener listener) { - mJob = job; - mListener = listener; - } - - // Below are the methods for Future. - @Override - public synchronized void cancel() { - if (mIsCancelled) return; - mIsCancelled = true; - if (mWaitOnResource != null) { - synchronized (mWaitOnResource) { - mWaitOnResource.notifyAll(); - } - } - if (mCancelListener != null) { - mCancelListener.onCancel(); - } - } - - @Override - public synchronized T get() { - while (!mIsDone) { - try { - wait(); - } catch (final Exception ex) { - Log.w(TAG, "ingore exception", ex); - // ignore. - } - } - return mResult; - } - - @Override - public boolean isCancelled() { - return mIsCancelled; - } - - @Override - public synchronized boolean isDone() { - return mIsDone; - } - - // This is called by a thread in the thread pool. - @Override - public void run() { - if (mListener != null) { - mListener.onFutureStart(this); - } - T result = null; - - // A job is in CPU mode by default. setMode returns false - // if the job is cancelled. - if (setMode(MODE_CPU)) { - try { - result = mJob.run(this); - } catch (final Throwable ex) { - Log.w(TAG, "Exception in running a job", ex); - } - } - - synchronized (this) { - setMode(MODE_NONE); - mResult = result; - mIsDone = true; - notifyAll(); - } - if (mListener != null) { - mListener.onFutureDone(this); - } - } - - // Below are the methods for JobContext (only called from the - // thread running the job) - @Override - public synchronized void setCancelListener(final CancelListener listener) { - mCancelListener = listener; - if (mIsCancelled && mCancelListener != null) { - mCancelListener.onCancel(); - } - } - - @Override - public boolean setMode(final int mode) { - // Release old resource - ResourceCounter rc = modeToCounter(mMode); - if (rc != null) { - releaseResource(rc); - } - mMode = MODE_NONE; - - // Acquire new resource - rc = modeToCounter(mode); - if (rc != null) { - if (!acquireResource(rc)) return false; - mMode = mode; - } - - return true; - } - - private boolean acquireResource(final ResourceCounter counter) { - while (true) { - synchronized (this) { - if (mIsCancelled) { - mWaitOnResource = null; - return false; - } - mWaitOnResource = counter; - } - - synchronized (counter) { - if (counter.value > 0) { - counter.value--; - break; - } else { - try { - counter.wait(); - } catch (final InterruptedException ex) { - // ignore. - } - } - } - } - - synchronized (this) { - mWaitOnResource = null; - } - - return true; - } - - private ResourceCounter modeToCounter(final int mode) { - if (mode == MODE_CPU) - return mCpuCounter; - else if (mode == MODE_NETWORK) - return mNetworkCounter; - else - return null; - } - - private void releaseResource(final ResourceCounter counter) { - synchronized (counter) { - counter.value++; - counter.notifyAll(); - } - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/Constants.java b/twidere/src/main/java/org/mariotaku/twidere/Constants.java index a4c6470ab..f711f5da3 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/Constants.java +++ b/twidere/src/main/java/org/mariotaku/twidere/Constants.java @@ -28,7 +28,7 @@ package org.mariotaku.twidere; public interface Constants extends TwidereConstants { public static final String DATABASES_NAME = "twidere.sqlite"; - public static final int DATABASES_VERSION = 81; + public static final int DATABASES_VERSION = 82; public static final int MENU_GROUP_STATUS_EXTENSION = 10; public static final int MENU_GROUP_COMPOSE_EXTENSION = 11; diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java index 930b4b089..4bc564757 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java @@ -47,10 +47,16 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.support.v4.util.LongSparseArray; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; +import android.support.v7.widget.RecyclerView.ItemDecoration; +import android.support.v7.widget.RecyclerView.State; +import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; -import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -61,15 +67,9 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.EditText; import android.widget.GridView; import android.widget.ImageView; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; @@ -77,12 +77,9 @@ import android.widget.Toast; import com.nostra13.universalimageloader.utils.IoUtils; import com.twitter.Extractor; -import org.apache.commons.lang3.ArrayUtils; import org.mariotaku.dynamicgridview.DraggableArrayAdapter; import org.mariotaku.menucomponent.internal.menu.MenuUtils; -import org.mariotaku.menucomponent.internal.widget.IListPopupWindow; import org.mariotaku.twidere.R; -import org.mariotaku.twidere.adapter.BaseArrayAdapter; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.fragment.support.BaseSupportDialogFragment; import org.mariotaku.twidere.model.DraftItem; @@ -110,7 +107,7 @@ import org.mariotaku.twidere.util.UserColorNameUtils; import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.util.accessor.ViewAccessor; import org.mariotaku.twidere.util.menu.TwidereMenuInfo; -import org.mariotaku.twidere.view.ComposeSelectAccountButton; +import org.mariotaku.twidere.view.ShapedImageView; import org.mariotaku.twidere.view.StatusTextCountView; import org.mariotaku.twidere.view.TwidereMenuBar; import org.mariotaku.twidere.view.holder.StatusViewHolder; @@ -130,7 +127,6 @@ import java.util.TreeSet; import static android.os.Environment.getExternalStorageState; import static android.text.TextUtils.isEmpty; import static org.mariotaku.twidere.util.ParseUtils.parseString; -import static org.mariotaku.twidere.util.ThemeUtils.getActionBarBackground; import static org.mariotaku.twidere.util.ThemeUtils.getComposeThemeResource; import static org.mariotaku.twidere.util.ThemeUtils.getWindowContentOverlayForCompose; import static org.mariotaku.twidere.util.Utils.addIntentToMenu; @@ -146,7 +142,7 @@ import static org.mariotaku.twidere.util.Utils.showErrorMessage; import static org.mariotaku.twidere.util.Utils.showMenuItemToast; public class ComposeActivity extends BaseSupportDialogActivity implements TextWatcher, LocationListener, - OnMenuItemClickListener, OnClickListener, OnEditorActionListener, OnLongClickListener, OnItemClickListener { + OnMenuItemClickListener, OnClickListener, OnEditorActionListener, OnLongClickListener { private static final String FAKE_IMAGE_LINK = "https://www.example.com/fake_image.jpg"; @@ -158,52 +154,45 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa private static final String EXTRA_TEMP_URI = "temp_uri"; private static final String EXTRA_SHARE_SCREENSHOT = "share_screenshot"; - - private TwidereValidator mValidator; private final Extractor mExtractor = new Extractor(); - + private final Rect mWindowDecorHitRect = new Rect(); + private TwidereValidator mValidator; private AsyncTwitterWrapper mTwitterWrapper; private LocationManager mLocationManager; private SharedPreferencesWrapper mPreferences; private ParcelableLocation mRecentLocation; - private ContentResolver mResolver; private TwidereAsyncTask mTask; - private IListPopupWindow mAccountSelectorPopup; - private TextView mTitleView, mSubtitleView; private GridView mMediaPreviewGrid; - private TwidereMenuBar mMenuBar; private EditText mEditText; - private ProgressBar mProgress; private View mSendView; private StatusTextCountView mSendTextCountView; - private ComposeSelectAccountButton mSelectAccountAccounts; - + private RecyclerView mAccountSelector; private MediaPreviewAdapter mMediaPreviewAdapter; - private boolean mIsPossiblySensitive, mShouldSaveAccounts; - - private long[] mSendAccountIds; - private Uri mTempPhotoUri; private boolean mImageUploaderUsed, mStatusShortenerUsed; private ParcelableStatus mInReplyToStatus; - private ParcelableUser mMentionUser; private DraftItem mDraftItem; private long mInReplyToStatusId; private String mOriginalText; - - private final Rect mWindowDecorHitRect = new Rect(); + private AccountIconsAdapter mAccountsAdapter; @Override - public void afterTextChanged(final Editable s) { + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + setMenu(); + updateTextCount(); + } + + @Override + public void afterTextChanged(final Editable s) { } @@ -217,6 +206,21 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return getComposeThemeResource(this); } + @Override + public void onSaveInstanceState(final Bundle outState) { + outState.putLongArray(EXTRA_ACCOUNT_IDS, mAccountsAdapter.getSelectedAccounts()); + outState.putParcelableArrayList(EXTRA_MEDIA, new ArrayList(getMediaList())); + outState.putBoolean(EXTRA_IS_POSSIBLY_SENSITIVE, mIsPossiblySensitive); + outState.putParcelable(EXTRA_STATUS, mInReplyToStatus); + outState.putLong(EXTRA_STATUS_ID, mInReplyToStatusId); + outState.putParcelable(EXTRA_USER, mMentionUser); + outState.putParcelable(EXTRA_DRAFT, mDraftItem); + outState.putBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS, mShouldSaveAccounts); + outState.putString(EXTRA_ORIGINAL_TEXT, mOriginalText); + outState.putParcelable(EXTRA_TEMP_URI, mTempPhotoUri); + super.onSaveInstanceState(outState); + } + public boolean handleMenuItem(final MenuItem item) { switch (item.getItemId()) { case MENU_TAKE_PHOTO: @@ -273,10 +277,11 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa try { final String action = intent.getAction(); if (INTENT_ACTION_EXTENSION_COMPOSE.equals(action)) { + final long[] accountIds = mAccountsAdapter.getSelectedAccounts(); intent.putExtra(EXTRA_TEXT, ParseUtils.parseString(mEditText.getText())); - intent.putExtra(EXTRA_ACCOUNT_IDS, mSendAccountIds); - if (mSendAccountIds != null && mSendAccountIds.length > 0) { - final long account_id = mSendAccountIds[0]; + intent.putExtra(EXTRA_ACCOUNT_IDS, accountIds); + if (accountIds.length > 0) { + final long account_id = accountIds[0]; intent.putExtra(EXTRA_NAME, getAccountName(this, account_id)); intent.putExtra(EXTRA_SCREEN_NAME, getAccountScreenName(this, account_id)); } @@ -345,8 +350,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa case REQUEST_EDIT_IMAGE: { if (resultCode == Activity.RESULT_OK) { final Uri uri = intent.getData(); - if (uri != null) { - } else { + if (uri == null) { break; } setMenu(); @@ -390,13 +394,16 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa } } + @Override + protected void onStop() { + saveAccountSelection(); + mLocationManager.removeUpdates(this); + super.onStop(); + } + @Override public void onClick(final View view) { switch (view.getId()) { - case R.id.close: { - onBackPressed(); - break; - } case R.id.send: { if (isQuotingProtectedStatus()) { new RetweetProtectedStatusWarnFragment().show(getSupportFragmentManager(), @@ -406,41 +413,9 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa } break; } - case R.id.select_account: { - if (!mAccountSelectorPopup.isShowing()) { - mAccountSelectorPopup.show(); - } - final ListView listView = mAccountSelectorPopup.getListView(); - listView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE); - listView.setOnItemClickListener(this); - for (int i = 0, j = listView.getCount(); i < j; i++) { - final long itemId = listView.getItemIdAtPosition(i); - listView.setItemChecked(i, ArrayUtils.contains(mSendAccountIds, itemId)); - } - break; - } } } - @Override - public void onContentChanged() { - super.onContentChanged(); - findViewById(R.id.close).setOnClickListener(this); - mEditText = (EditText) findViewById(R.id.edit_text); - mTitleView = (TextView) findViewById(R.id.actionbar_title); - mSubtitleView = (TextView) findViewById(R.id.actionbar_subtitle); - mMediaPreviewGrid = (GridView) findViewById(R.id.media_thumbnail_preview); - mMenuBar = (TwidereMenuBar) findViewById(R.id.menu_bar); - mProgress = (ProgressBar) findViewById(R.id.actionbar_progress_indeterminate); - final View composeActionBar = findViewById(R.id.compose_actionbar); - final View composeBottomBar = findViewById(R.id.compose_bottombar); - mSendView = composeBottomBar.findViewById(R.id.send); - mSendTextCountView = (StatusTextCountView) mSendView.findViewById(R.id.status_text_count); - mSelectAccountAccounts = (ComposeSelectAccountButton) composeActionBar.findViewById(R.id.select_account); - ViewAccessor.setBackground(findViewById(R.id.compose_content), getWindowContentOverlayForCompose(this)); - ViewAccessor.setBackground(composeActionBar, getActionBarBackground(this, getCurrentThemeResourceId())); - } - @Override public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) { if (event == null) return false; @@ -461,6 +436,20 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa } } + @Override + public void onStatusChanged(final String provider, final int status, final Bundle extras) { + + } + + @Override + public void onProviderEnabled(final String provider) { + } + + @Override + public void onProviderDisabled(final String provider) { + setProgressBarIndeterminateVisibility(false); + } + @Override public boolean onLongClick(final View v) { switch (v.getId()) { @@ -477,41 +466,6 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return handleMenuItem(item); } - @Override - public void onProviderDisabled(final String provider) { - setProgressBarIndeterminateVisibility(false); - } - - @Override - public void onProviderEnabled(final String provider) { - } - - @Override - public void onSaveInstanceState(final Bundle outState) { - outState.putLongArray(EXTRA_ACCOUNT_IDS, mSendAccountIds); - outState.putParcelableArrayList(EXTRA_MEDIA, new ArrayList(getMediaList())); - outState.putBoolean(EXTRA_IS_POSSIBLY_SENSITIVE, mIsPossiblySensitive); - outState.putParcelable(EXTRA_STATUS, mInReplyToStatus); - outState.putLong(EXTRA_STATUS_ID, mInReplyToStatusId); - outState.putParcelable(EXTRA_USER, mMentionUser); - outState.putParcelable(EXTRA_DRAFT, mDraftItem); - outState.putBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS, mShouldSaveAccounts); - outState.putString(EXTRA_ORIGINAL_TEXT, mOriginalText); - outState.putParcelable(EXTRA_TEMP_URI, mTempPhotoUri); - super.onSaveInstanceState(outState); - } - - @Override - public void onStatusChanged(final String provider, final int status, final Bundle extras) { - - } - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - setMenu(); - updateTextCount(); - } - @Override public boolean onTouchEvent(final MotionEvent event) { switch (event.getActionMasked()) { @@ -527,6 +481,19 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return super.onTouchEvent(event); } + @Override + public void onContentChanged() { + super.onContentChanged(); + mEditText = (EditText) findViewById(R.id.edit_text); + mMediaPreviewGrid = (GridView) findViewById(R.id.media_thumbnail_preview); + mMenuBar = (TwidereMenuBar) findViewById(R.id.menu_bar); + final View composeBottomBar = findViewById(R.id.compose_bottombar); + mSendView = composeBottomBar.findViewById(R.id.send); + mSendTextCountView = (StatusTextCountView) mSendView.findViewById(R.id.status_text_count); + mAccountSelector = (RecyclerView) findViewById(R.id.account_selector); + ViewAccessor.setBackground(findViewById(R.id.compose_content), getWindowContentOverlayForCompose(this)); + } + public void removeAllMedia(final List list) { mMediaPreviewAdapter.removeAll(list); updateMediaPreview(); @@ -535,7 +502,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa public void saveToDrafts() { final String text = mEditText != null ? ParseUtils.parseString(mEditText.getText()) : null; final ParcelableStatusUpdate.Builder builder = new ParcelableStatusUpdate.Builder(); - builder.accounts(ParcelableAccount.getAccounts(this, mSendAccountIds)); + builder.accounts(ParcelableAccount.getAccounts(this, mAccountsAdapter.getSelectedAccounts())); builder.text(text); builder.inReplyToStatusId(mInReplyToStatusId); builder.location(mRecentLocation); @@ -571,20 +538,14 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mMenuBar.setOnMenuItemClickListener(this); mEditText.setOnEditorActionListener(mPreferences.getBoolean(KEY_QUICK_SEND, false) ? this : null); mEditText.addTextChangedListener(this); - final AccountSelectorAdapter accountAdapter = new AccountSelectorAdapter(mMenuBar.getPopupContext()); - accountAdapter.addAll(ParcelableAccount.getAccountsList(this, false)); - mAccountSelectorPopup = IListPopupWindow.InstanceHelper.getInstance(mMenuBar.getPopupContext()); - mAccountSelectorPopup.setInputMethodMode(IListPopupWindow.INPUT_METHOD_NOT_NEEDED); - mAccountSelectorPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - mAccountSelectorPopup.setModal(true); - mAccountSelectorPopup.setContentWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width)); - mAccountSelectorPopup.setAdapter(accountAdapter); - mAccountSelectorPopup.setAnchorView(mSelectAccountAccounts); -// mSelectAccountButton.setOnTouchListener(ListPopupWindowCompat.createDragToOpenListener( -// mAccountSelectorPopup, mSelectAccountButton)); - mSelectAccountAccounts.setOnClickListener(this); - mSelectAccountAccounts.setOnLongClickListener(this); + final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); + linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mAccountSelector.setLayoutManager(linearLayoutManager); + mAccountSelector.addItemDecoration(new SpacingItemDecoration(this)); + mAccountsAdapter = new AccountIconsAdapter(this); + mAccountSelector.setAdapter(mAccountsAdapter); + mAccountsAdapter.setAccounts(ParcelableAccount.getAccounts(this, false, false)); mMediaPreviewAdapter = new MediaPreviewAdapter(this); mMediaPreviewGrid.setAdapter(mMediaPreviewAdapter); @@ -593,7 +554,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa if (savedInstanceState != null) { // Restore from previous saved state - mSendAccountIds = savedInstanceState.getLongArray(EXTRA_ACCOUNT_IDS); + mAccountsAdapter.setSelectedAccounts(savedInstanceState.getLongArray(EXTRA_ACCOUNT_IDS)); mIsPossiblySensitive = savedInstanceState.getBoolean(EXTRA_IS_POSSIBLY_SENSITIVE); final ArrayList mediaList = savedInstanceState.getParcelableArrayList(EXTRA_MEDIA); if (mediaList != null) { @@ -616,11 +577,12 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa if (!handleIntent(intent)) { handleDefaultIntent(intent); } - if (mSendAccountIds == null || mSendAccountIds.length == 0) { + final long[] accountIds = mAccountsAdapter.getSelectedAccounts(); + if (accountIds.length == 0) { final long[] idsInPrefs = TwidereArrayUtils.parseLongArray( mPreferences.getString(KEY_COMPOSE_ACCOUNTS, null), ','); final long[] intersection = TwidereArrayUtils.intersection(idsInPrefs, defaultAccountIds); - mSendAccountIds = intersection.length > 0 ? intersection : defaultAccountIds; + mAccountsAdapter.setSelectedAccounts(intersection.length > 0 ? intersection : defaultAccountIds); } mOriginalText = ParseUtils.parseString(mEditText.getText()); } @@ -640,7 +602,6 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa addIntentToMenu(this, mediaMenuItem.getSubMenu(), imageExtensionsIntent, MENU_GROUP_IMAGE_EXTENSION); } setMenu(); - updateAccountSelection(); updateMediaPreview(); } @@ -655,19 +616,9 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mEditText.setTextSize(text_size * 1.25f); } - @Override - protected void onStop() { - if (mAccountSelectorPopup != null && mAccountSelectorPopup.isShowing()) { - mAccountSelectorPopup.dismiss(); - } - mLocationManager.removeUpdates(this); - super.onStop(); - } - @Override protected void onTitleChanged(final CharSequence title, final int color) { super.onTitleChanged(title, color); - mTitleView.setText(title); } private void addMedia(final ParcelableMediaUpdate media) { @@ -723,6 +674,10 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return list.toArray(new ParcelableMediaUpdate[list.size()]); } + private int getMediaCount() { + return mMediaPreviewAdapter.getCount(); + } + private List getMediaList() { return mMediaPreviewAdapter.getAsList(); } @@ -732,10 +687,10 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa final String action = intent.getAction(); final boolean hasAccountIds; if (intent.hasExtra(EXTRA_ACCOUNT_IDS)) { - mSendAccountIds = intent.getLongArrayExtra(EXTRA_ACCOUNT_IDS); + mAccountsAdapter.setSelectedAccounts(intent.getLongArrayExtra(EXTRA_ACCOUNT_IDS)); hasAccountIds = true; } else if (intent.hasExtra(EXTRA_ACCOUNT_ID)) { - mSendAccountIds = new long[]{intent.getLongExtra(EXTRA_ACCOUNT_ID, -1)}; + mAccountsAdapter.setSelectedAccounts(intent.getLongExtra(EXTRA_ACCOUNT_ID, -1)); hasAccountIds = true; } else { hasAccountIds = false; @@ -773,7 +728,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mEditText.setText(draft.text); final int selection_end = mEditText.length(); mEditText.setSelection(selection_end); - mSendAccountIds = draft.account_ids; + mAccountsAdapter.setSelectedAccounts(draft.account_ids); if (draft.media != null) { addMedia(Arrays.asList(draft.media)); } @@ -826,7 +781,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mEditText.setText("@" + user.screen_name + " "); final int selection_end = mEditText.length(); mEditText.setSelection(selection_end); - mSendAccountIds = new long[]{user.account_id}; + mAccountsAdapter.setSelectedAccounts(user.account_id); return true; } @@ -834,7 +789,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa if (status == null || status.id <= 0) return false; mEditText.setText(getQuoteStatus(this, status.user_screen_name, status.text_plain)); mEditText.setSelection(0); - mSendAccountIds = new long[]{status.account_id}; + mAccountsAdapter.setSelectedAccounts(status.account_id); return true; } @@ -858,7 +813,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa } final int selectionEnd = mEditText.length(); mEditText.setSelection(selectionStart, selectionEnd); - mSendAccountIds = new long[]{status.account_id}; + mAccountsAdapter.setSelectedAccounts(status.account_id); return true; } @@ -874,7 +829,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mEditText.append("@" + screenName + " "); } mEditText.setSelection(mEditText.length()); - mSendAccountIds = new long[]{accountId}; + mAccountsAdapter.setSelectedAccounts(accountId); mInReplyToStatusId = inReplyToStatusId; return true; } @@ -883,10 +838,6 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return !mMediaPreviewAdapter.isEmpty(); } - private int getMediaCount() { - return mMediaPreviewAdapter.getCount(); - } - private boolean isQuotingProtectedStatus() { if (INTENT_ACTION_QUOTE.equals(getIntent().getAction()) && mInReplyToStatus != null) return mInReplyToStatus.user_is_protected && mInReplyToStatus.account_id != mInReplyToStatus.user_id; @@ -941,8 +892,8 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa final String display_name = UserColorNameUtils.getDisplayName(this, mInReplyToStatus.user_id, mInReplyToStatus.user_name, mInReplyToStatus.user_screen_name); setTitle(getString(R.string.quote_user, display_name)); - mSubtitleView.setVisibility(mInReplyToStatus.user_is_protected - && mInReplyToStatus.account_id != mInReplyToStatus.user_id ? View.VISIBLE : View.GONE); +// mSubtitleView.setVisibility(mInReplyToStatus.user_is_protected +// && mInReplyToStatus.account_id != mInReplyToStatus.user_id ? View.VISIBLE : View.GONE); } else if (INTENT_ACTION_EDIT_DRAFT.equals(action)) { if (mDraftItem == null) return false; setTitle(R.string.edit_draft); @@ -1007,7 +958,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa } private void setProgressVisibility(final boolean visible) { - mProgress.setVisibility(visible ? View.VISIBLE : View.GONE); +// mProgress.setVisibility(visible ? View.VISIBLE : View.GONE); } private boolean takePhoto() { @@ -1026,14 +977,11 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return true; } - private void updateAccountSelection() { - if (mSendAccountIds == null) return; - if (mShouldSaveAccounts) { - final SharedPreferences.Editor editor = mPreferences.edit(); - editor.putString(KEY_COMPOSE_ACCOUNTS, TwidereArrayUtils.toString(mSendAccountIds, ',', false)); - editor.apply(); - } - mSelectAccountAccounts.setSelectedAccounts(mSendAccountIds); + private void saveAccountSelection() { + if (!mShouldSaveAccounts) return; + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(KEY_COMPOSE_ACCOUNTS, TwidereArrayUtils.toString(mAccountsAdapter.getSelectedAccounts(), ',', false)); + editor.apply(); } private void updateMediaPreview() { @@ -1067,12 +1015,13 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa } mRecentLocation = location != null ? new ParcelableLocation(location) : null; } + final long[] accountIds = mAccountsAdapter.getSelectedAccounts(); final boolean isQuote = INTENT_ACTION_QUOTE.equals(getIntent().getAction()); final ParcelableLocation statusLocation = attach_location ? mRecentLocation : null; final boolean linkToQuotedTweet = mPreferences.getBoolean(KEY_LINK_TO_QUOTED_TWEET, true); final long inReplyToStatusId = !isQuote || linkToQuotedTweet ? mInReplyToStatusId : -1; final boolean isPossiblySensitive = hasMedia && mIsPossiblySensitive; - mTwitterWrapper.updateStatusAsync(mSendAccountIds, text, statusLocation, getMedia(), inReplyToStatusId, + mTwitterWrapper.updateStatusAsync(accountIds, text, statusLocation, getMedia(), inReplyToStatusId, isPossiblySensitive); if (mPreferences.getBoolean(KEY_NO_CLOSE_AFTER_TWEET_SENT, false) && (mInReplyToStatus == null || mInReplyToStatusId <= 0)) { @@ -1107,158 +1056,105 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mSendTextCountView.setTextCount(validatedCount); } - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - final ListView listView = (ListView) parent; - final SparseBooleanArray checkedPositions = listView.getCheckedItemPositions(); - mSendAccountIds = new long[listView.getCheckedItemCount()]; - for (int i = 0, j = listView.getCount(), k = 0; i < j; i++) { - if (checkedPositions.get(i)) { - mSendAccountIds[k++] = listView.getItemIdAtPosition(i); - } - } - updateAccountSelection(); - } + static class AccountIconViewHolder extends ViewHolder implements OnClickListener { - public static class RetweetProtectedStatusWarnFragment extends BaseSupportDialogFragment implements - DialogInterface.OnClickListener { + private final AccountIconsAdapter adapter; + private final ShapedImageView iconView; + + public AccountIconViewHolder(AccountIconsAdapter adapter, View itemView) { + super(itemView); + this.adapter = adapter; + iconView = (ShapedImageView) itemView.findViewById(android.R.id.icon); + itemView.setOnClickListener(this); + } + + public void showAccount(AccountIconsAdapter adapter, ParcelableAccount account, boolean isSelected) { + itemView.setAlpha(isSelected ? 1 : 0.7f); + final ImageLoaderWrapper loader = adapter.getImageLoader(); + loader.displayProfileImage(iconView, account.profile_image_url); + iconView.setBorderColor(account.color); + } @Override - public void onClick(final DialogInterface dialog, final int which) { - final Activity activity = getActivity(); - switch (which) { - case DialogInterface.BUTTON_POSITIVE: { - if (activity instanceof ComposeActivity) { - ((ComposeActivity) activity).updateStatus(); - } - break; - } - } + public void onClick(View v) { + adapter.toggleSelection(getPosition()); + } + + } + + private static class AccountIconsAdapter extends Adapter { + + private final LayoutInflater mInflater; + private final ImageLoaderWrapper mImageLoader; + private final LongSparseArray mSelection; + + private ParcelableAccount[] mAccounts; + + public AccountIconsAdapter(Context context) { + mInflater = LayoutInflater.from(context); + mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); + mSelection = new LongSparseArray<>(); + } + + public ImageLoaderWrapper getImageLoader() { + return mImageLoader; } @NonNull - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final Context wrapped = ThemeUtils.getDialogThemedContext(getActivity()); - final AlertDialog.Builder builder = new AlertDialog.Builder(wrapped); - builder.setMessage(R.string.quote_protected_status_warning_message); - builder.setPositiveButton(R.string.send_anyway, this); - builder.setNegativeButton(android.R.string.cancel, null); - return builder.create(); - } - } - - public static class UnsavedTweetDialogFragment extends BaseSupportDialogFragment implements - DialogInterface.OnClickListener { - - @Override - public void onClick(final DialogInterface dialog, final int which) { - final Activity activity = getActivity(); - switch (which) { - case DialogInterface.BUTTON_POSITIVE: { - if (activity instanceof ComposeActivity) { - ((ComposeActivity) activity).saveToDrafts(); - } - activity.finish(); - break; - } - case DialogInterface.BUTTON_NEGATIVE: { - if (activity instanceof ComposeActivity) { - new DiscardTweetTask((ComposeActivity) activity).executeTask(); - } else { - activity.finish(); - } - break; + public long[] getSelectedAccounts() { + if (mAccounts == null) return new long[0]; + final long[] temp = new long[mAccounts.length]; + int selectedCount = 0; + for (ParcelableAccount account : mAccounts) { + if (mSelection.get(account.account_id, false)) { + temp[selectedCount++] = account.account_id; } } - + final long[] result = new long[selectedCount]; + System.arraycopy(temp, 0, result, 0, result.length); + return result; } - @NonNull - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final Context wrapped = ThemeUtils.getDialogThemedContext(getActivity()); - final AlertDialog.Builder builder = new AlertDialog.Builder(wrapped); - builder.setMessage(R.string.unsaved_status); - builder.setPositiveButton(R.string.save, this); - builder.setNegativeButton(R.string.discard, this); - return builder.create(); - } - } - - public static class ViewStatusDialogFragment extends BaseSupportDialogFragment { - - private StatusViewHolder mHolder; - private View mStatusContainer; - - public ViewStatusDialogFragment() { - setStyle(STYLE_NO_TITLE, 0); - } - - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - final Bundle args = getArguments(); - if (args == null || args.getParcelable(EXTRA_STATUS) == null) { - dismiss(); - return; + public void setSelectedAccounts(long... accountIds) { + mSelection.clear(); + if (accountIds != null) { + for (long accountId : accountIds) { + mSelection.put(accountId, true); + } } - final TwidereApplication application = getApplication(); - final FragmentActivity activity = getActivity(); - final ImageLoaderWrapper loader = application.getImageLoaderWrapper(); - final ImageLoadingHandler handler = new ImageLoadingHandler(R.id.media_preview_progress); - final AsyncTwitterWrapper twitter = getTwitterWrapper(); - final SharedPreferencesWrapper preferences = SharedPreferencesWrapper.getInstance(activity, - SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - final ParcelableStatus status = args.getParcelable(EXTRA_STATUS); - final int profileImageStyle = Utils.getProfileImageStyle(preferences.getString(KEY_PROFILE_IMAGE_STYLE, null)); - final int mediaPreviewStyle = Utils.getMediaPreviewStyle(preferences.getString(KEY_MEDIA_PREVIEW_STYLE, null)); - mHolder.displayStatus(activity, loader, handler, twitter, profileImageStyle, - mediaPreviewStyle, status, null); - mStatusContainer.findViewById(R.id.item_menu).setVisibility(View.GONE); - mStatusContainer.findViewById(R.id.action_buttons).setVisibility(View.GONE); - mStatusContainer.findViewById(R.id.reply_retweet_status).setVisibility(View.GONE); + notifyDataSetChanged(); } @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup parent, final Bundle savedInstanceState) { - return inflater.inflate(R.layout.dialog_scrollable_status, parent, false); + public AccountIconViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final View view = mInflater.inflate(R.layout.adapter_item_compose_account, parent, false); + return new AccountIconViewHolder(this, view); } @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mStatusContainer = view.findViewById(R.id.status_container); - mHolder = new StatusViewHolder(view); - } - } - - private static class AccountSelectorAdapter extends BaseArrayAdapter { - - public AccountSelectorAdapter(final Context context) { - super(context, android.R.layout.simple_list_item_multiple_choice); + public void onBindViewHolder(AccountIconViewHolder holder, int position) { + final ParcelableAccount account = mAccounts[position]; + final boolean isSelected = mSelection.get(account.account_id, false); + holder.showAccount(this, account, isSelected); } @Override - public long getItemId(int position) { - return super.getItem(position).account_id; + public int getItemCount() { + return mAccounts != null ? mAccounts.length : 0; } - @Override - public boolean hasStableIds() { - return true; + public void setAccounts(ParcelableAccount[] accounts) { + mAccounts = accounts; + notifyDataSetChanged(); } - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final View view = super.getView(position, convertView, parent); - final ParcelableAccount account = getItem(position); - final TextView text1 = (TextView) view.findViewById(android.R.id.text1); - text1.setText(Utils.getAccountDisplayName(getContext(), account.account_id, isDisplayNameFirst())); - return view; + private void toggleSelection(int position) { + if (mAccounts == null) return; + final long accountId = mAccounts[position].account_id; + mSelection.put(accountId, !mSelection.get(accountId, false)); + notifyDataSetChanged(); } - } private static class AddBitmapTask extends AddMediaTask { @@ -1296,10 +1192,6 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa private final Uri src, dst; private final boolean delete_src; - Uri getSrc() { - return src; - } - AddMediaTask(final ComposeActivity activity, final Uri src, final Uri dst, final int media_type, final boolean delete_src) { this.activity = activity; @@ -1330,6 +1222,10 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return true; } + Uri getSrc() { + return src; + } + @Override protected void onPostExecute(final Boolean result) { activity.setProgressVisibility(false); @@ -1437,6 +1333,10 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); } + public List getAsList() { + return Collections.unmodifiableList(getObjects()); + } + @Override public View getView(final int position, final View convertView, final ViewGroup parent) { final View view = super.getView(position, convertView, parent); @@ -1446,10 +1346,108 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return view; } - public List getAsList() { - return Collections.unmodifiableList(getObjects()); + + } + + public static class RetweetProtectedStatusWarnFragment extends BaseSupportDialogFragment implements + DialogInterface.OnClickListener { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + final Activity activity = getActivity(); + switch (which) { + case DialogInterface.BUTTON_POSITIVE: { + if (activity instanceof ComposeActivity) { + ((ComposeActivity) activity).updateStatus(); + } + break; + } + } + + } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final Context wrapped = ThemeUtils.getDialogThemedContext(getActivity()); + final AlertDialog.Builder builder = new AlertDialog.Builder(wrapped); + builder.setMessage(R.string.quote_protected_status_warning_message); + builder.setPositiveButton(R.string.send_anyway, this); + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); } } + private static class SpacingItemDecoration extends ItemDecoration { + private final int mSpacingSmall, mSpacingExtraSmall; + + SpacingItemDecoration(Context context) { + final Resources resources = context.getResources(); + mSpacingSmall = resources.getDimensionPixelSize(R.dimen.element_spacing_small); + mSpacingExtraSmall = resources.getDimensionPixelSize(R.dimen.element_spacing_xsmall); + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { + final int pos = parent.getChildPosition(view); + if (pos == 0) { + outRect.set(mSpacingSmall, mSpacingSmall, mSpacingSmall, mSpacingExtraSmall); + } else if (pos == parent.getAdapter().getItemCount() - 1) { + outRect.set(mSpacingSmall, mSpacingExtraSmall, mSpacingSmall, mSpacingSmall); + } else { + outRect.set(mSpacingSmall, mSpacingExtraSmall, mSpacingSmall, mSpacingExtraSmall); + + } + } + } + + public static class ViewStatusDialogFragment extends BaseSupportDialogFragment { + + private StatusViewHolder mHolder; + private View mStatusContainer; + + public ViewStatusDialogFragment() { + setStyle(STYLE_NO_TITLE, 0); + } + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup parent, final Bundle savedInstanceState) { + return inflater.inflate(R.layout.dialog_scrollable_status, parent, false); + } + + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Bundle args = getArguments(); + if (args == null || args.getParcelable(EXTRA_STATUS) == null) { + dismiss(); + return; + } + final TwidereApplication application = getApplication(); + final FragmentActivity activity = getActivity(); + final ImageLoaderWrapper loader = application.getImageLoaderWrapper(); + final ImageLoadingHandler handler = new ImageLoadingHandler(R.id.media_preview_progress); + final AsyncTwitterWrapper twitter = getTwitterWrapper(); + final SharedPreferencesWrapper preferences = SharedPreferencesWrapper.getInstance(activity, + SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + final ParcelableStatus status = args.getParcelable(EXTRA_STATUS); + final int profileImageStyle = Utils.getProfileImageStyle(preferences.getString(KEY_PROFILE_IMAGE_STYLE, null)); + final int mediaPreviewStyle = Utils.getMediaPreviewStyle(preferences.getString(KEY_MEDIA_PREVIEW_STYLE, null)); + mHolder.displayStatus(activity, loader, handler, twitter, profileImageStyle, + mediaPreviewStyle, status, null, true); + mStatusContainer.findViewById(R.id.item_menu).setVisibility(View.GONE); + mStatusContainer.findViewById(R.id.action_buttons).setVisibility(View.GONE); + mStatusContainer.findViewById(R.id.reply_retweet_status).setVisibility(View.GONE); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mStatusContainer = view.findViewById(R.id.status_container); + mHolder = new StatusViewHolder(view); + } + + + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java index 51dd740d6..9dd122db2 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java @@ -29,9 +29,12 @@ import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.View.OnLayoutChangeListener; -import android.widget.ImageView; import android.widget.ProgressBar; +import com.diegocarloslima.byakugallery.lib.TileBitmapDrawable; +import com.diegocarloslima.byakugallery.lib.TileBitmapDrawable.OnInitializeListener; +import com.diegocarloslima.byakugallery.lib.TouchImageView; + import org.mariotaku.menucomponent.widget.MenuBar; import org.mariotaku.menucomponent.widget.MenuBar.MenuBarListener; import org.mariotaku.tileimageview.widget.TileImageView; @@ -52,7 +55,7 @@ public final class MediaViewerActivity extends BaseSupportActivity implements Co private ActionBar mActionBar; private ProgressBar mProgress; - private ImageView mImageView; + private TouchImageView mImageView; private MenuBar mMenuBar; private long mContentLength; @@ -69,7 +72,7 @@ public final class MediaViewerActivity extends BaseSupportActivity implements Co @Override public void onContentChanged() { super.onContentChanged(); - mImageView = (ImageView) findViewById(R.id.image_viewer); + mImageView = (TouchImageView) findViewById(R.id.image_viewer); mProgress = (ProgressBar) findViewById(R.id.progress); mMenuBar = (MenuBar) findViewById(R.id.menu_bar); } @@ -120,7 +123,21 @@ public final class MediaViewerActivity extends BaseSupportActivity implements Co mImageView.setVisibility(View.VISIBLE); // mImageView.setBitmapRegionDecoder(data.decoder, data.bitmap); // mImageView.setScale(1); - mImageView.setImageBitmap(data.bitmap); + if (data.useDecoder) { + TileBitmapDrawable.attachTileBitmapDrawable(mImageView, data.file.getAbsolutePath(), null, new OnInitializeListener() { + @Override + public void onStartInitialization() { + + } + + @Override + public void onEndInitialization() { + + } + }); + } else { + mImageView.setImageBitmap(data.bitmap); + } mImageFile = data.file; } else { mImageView.setVisibility(View.GONE); @@ -232,7 +249,8 @@ public final class MediaViewerActivity extends BaseSupportActivity implements Co } // mImageView.setScaleToFit(false); - mImageView.addOnLayoutChangeListener(new TileImageViewLayoutListener()); + mImageView.setMaxScale(2); + mMenuBar.setMenuBarListener(this); mMenuBar.inflate(R.menu.menu_image_viewer); @@ -241,34 +259,6 @@ public final class MediaViewerActivity extends BaseSupportActivity implements Co } - private static class TileImageViewLayoutListener implements OnLayoutChangeListener { - @Override - public void onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, - final int oldLeft, final int oldTop, final int oldRight, final int oldBottom) { - if (!(v instanceof TileImageView)) return; - final TileImageView tileView = (TileImageView) v; - final int baseWidth = tileView.getBaseWidth(), baseHeight = tileView.getBaseHeight(); - final double scaleMin = getMinScale(left, top, right, bottom, baseWidth, baseHeight); - tileView.setScaleLimits(scaleMin, Math.max(scaleMin, 2.0)); - final double oldScaleMin = getMinScale(oldLeft, oldTop, oldRight, oldBottom, baseWidth, baseHeight); - final double oldScale = tileView.getScale(); - tileView.setScaleLimits(scaleMin, Math.max(scaleMin, 2.0)); - if (oldScale == oldScaleMin) { - tileView.setScale(scaleMin); - } - } - - private static double getMinScale(final int left, final int top, final int right, final int bottom, - final int baseWidth, final int baseHeight) { - final double viewWidth = right - left, viewHeight = bottom - top; - if (viewWidth <= 0 || viewHeight <= 0) return 0; - final double widthScale = Math.min(1, baseWidth / viewWidth), heightScale = Math.min(1, baseHeight - / viewHeight); - return Math.min(widthScale, heightScale); - } - - } - @Override protected void onDestroy() { mActionBar.removeOnMenuVisibilityListener(this); diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java index 75f52f9fa..8454ada72 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.PorterDuff.Mode; +import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.AsyncTaskLoader; @@ -353,8 +354,8 @@ public class QuickSearchBarActivity extends BaseSupportActivity implements OnCli static final int ITEM_VIEW_TYPE = 3; private final ParcelableUser mUser; - public UserSuggestionItem(Cursor c, CachedIndices i) { - mUser = new ParcelableUser(c, i, -1); + public UserSuggestionItem(Cursor c, CachedIndices i, long accountId) { + mUser = new ParcelableUser(c, i, accountId); } @Override @@ -485,10 +486,19 @@ public class QuickSearchBarActivity extends BaseSupportActivity implements OnCli @Override public List loadInBackground() { + final boolean emptyQuery = TextUtils.isEmpty(mQuery); final Context context = getContext(); final ContentResolver resolver = context.getContentResolver(); final List result = new ArrayList<>(); - if (!TextUtils.isEmpty(mQuery)) { + final String[] historyProjection = {SearchHistory.QUERY}; + final Cursor historyCursor = resolver.query(SearchHistory.CONTENT_URI, + historyProjection, null, null, SearchHistory.DEFAULT_SORT_ORDER); + for (int i = 0, j = Math.min(emptyQuery ? 3 : 2, historyCursor.getCount()); i < j; i++) { + historyCursor.moveToPosition(i); + result.add(new SearchHistoryItem(historyCursor.getString(0))); + } + historyCursor.close(); + if (!emptyQuery) { final String queryEscaped = mQuery.replace("_", "^_"); final SharedPreferences nicknamePrefs = context.getSharedPreferences( USER_NICKNAME_PREFERENCES_NAME, Context.MODE_PRIVATE); @@ -499,38 +509,31 @@ public class QuickSearchBarActivity extends BaseSupportActivity implements OnCli Expression.likeRaw(new Column(CachedUsers.NAME), "?||'%'", "^"), Expression.in(new Column(CachedUsers.USER_ID), new RawItemArray(nicknameIds))); final String[] selectionArgs = new String[]{queryEscaped, queryEscaped}; - final OrderBy orderBy = new OrderBy(CachedUsers.LAST_SEEN + " DESC", + final OrderBy orderBy = new OrderBy(CachedUsers.LAST_SEEN + " DESC", "score DESC", CachedUsers.SCREEN_NAME, CachedUsers.NAME); - final Cursor usersCursor = context.getContentResolver().query(CachedUsers.CONTENT_URI, - CachedUsers.BASIC_COLUMNS, selection != null ? selection.getSQL() : null, + final Uri uri = Uri.withAppendedPath(CachedUsers.CONTENT_URI_WITH_SCORE, String.valueOf(mAccountId)); + final Cursor usersCursor = context.getContentResolver().query(uri, + CachedUsers.COLUMNS, selection != null ? selection.getSQL() : null, selectionArgs, orderBy.getSQL()); final CachedIndices usersIndices = new CachedIndices(usersCursor); for (int i = 0, j = Math.min(5, usersCursor.getCount()); i < j; i++) { usersCursor.moveToPosition(i); - result.add(new UserSuggestionItem(usersCursor, usersIndices)); + result.add(new UserSuggestionItem(usersCursor, usersIndices, mAccountId)); } usersCursor.close(); - return result; + } else { + final String[] savedSearchesProjection = {SavedSearches.QUERY}; + final Expression savedSearchesWhere = Expression.equals(SavedSearches.ACCOUNT_ID, mAccountId); + final Cursor savedSearchesCursor = resolver.query(SavedSearches.CONTENT_URI, + savedSearchesProjection, savedSearchesWhere.getSQL(), null, + SavedSearches.DEFAULT_SORT_ORDER); + savedSearchesCursor.moveToFirst(); + while (!savedSearchesCursor.isAfterLast()) { + result.add(new SavedSearchItem(savedSearchesCursor.getString(0))); + savedSearchesCursor.moveToNext(); + } + savedSearchesCursor.close(); } - final String[] historyProjection = {SearchHistory.QUERY}; - final Cursor historyCursor = resolver.query(SearchHistory.CONTENT_URI, - historyProjection, null, null, SearchHistory.DEFAULT_SORT_ORDER); - for (int i = 0, j = Math.min(3, historyCursor.getCount()); i < j; i++) { - historyCursor.moveToPosition(i); - result.add(new SearchHistoryItem(historyCursor.getString(0))); - } - historyCursor.close(); - final String[] savedSearchesProjection = {SavedSearches.QUERY}; - final Expression savedSearchesWhere = Expression.equals(SavedSearches.ACCOUNT_ID, mAccountId); - final Cursor savedSearchesCursor = resolver.query(SavedSearches.CONTENT_URI, - savedSearchesProjection, savedSearchesWhere.getSQL(), null, - SavedSearches.DEFAULT_SORT_ORDER); - savedSearchesCursor.moveToFirst(); - while (!savedSearchesCursor.isAfterLast()) { - result.add(new SavedSearchItem(savedSearchesCursor.getString(0))); - savedSearchesCursor.moveToNext(); - } - savedSearchesCursor.close(); return result; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/SignInActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/SignInActivity.java index d66a459e9..955ec9184 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/SignInActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/SignInActivity.java @@ -19,6 +19,7 @@ package org.mariotaku.twidere.activity.support; +import android.app.ActionBar; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentResolver; @@ -26,6 +27,8 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.ColorStateList; +import android.content.res.Resources; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; @@ -57,6 +60,7 @@ import org.mariotaku.twidere.util.ParseUtils; import org.mariotaku.twidere.util.ThemeUtils; import org.mariotaku.twidere.util.TwitterContentUtils; import org.mariotaku.twidere.util.Utils; +import org.mariotaku.twidere.util.accessor.ViewAccessor; import org.mariotaku.twidere.util.net.TwidereHostResolverFactory; import org.mariotaku.twidere.util.net.TwidereHttpClientFactory; @@ -286,7 +290,10 @@ public class SignInActivity extends BaseSupportActivity implements TwitterConsta setContentView(R.layout.activity_sign_in); setProgressBarIndeterminateVisibility(false); final long[] account_ids = getActivatedAccountIds(this); - getActionBar().setDisplayHomeAsUpEnabled(account_ids.length > 0); + final ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(account_ids.length > 0); + } if (savedInstanceState != null) { mAPIUrlFormat = savedInstanceState.getString(Accounts.API_URL_FORMAT); @@ -308,6 +315,9 @@ public class SignInActivity extends BaseSupportActivity implements TwitterConsta mEditUsername.addTextChangedListener(this); mEditPassword.setText(mPassword); mEditPassword.addTextChangedListener(this); + final Resources resources = getResources(); + final ColorStateList color = ColorStateList.valueOf(resources.getColor(R.color.material_light_green)); + ViewAccessor.setBackgroundTintList(mSignInButton, color); setSignInButton(); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java index b3a0c7cf8..dcb5bc67e 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java @@ -172,7 +172,7 @@ public abstract class AbsActivitiesAdapter extends Adapter imp } @Override - public void onItemMenuClick(ViewHolder holder, int position) { + public void onItemMenuClick(ViewHolder holder, View menuView, int position) { } @@ -188,6 +188,8 @@ public abstract class AbsActivitiesAdapter extends Adapter imp final View view; if (mCompactCards) { view = mInflater.inflate(R.layout.card_item_status_compact, parent, false); + final View itemContent = view.findViewById(R.id.item_content); + itemContent.setBackgroundColor(mCardBackgroundColor); } else { view = mInflater.inflate(R.layout.card_item_status, parent, false); final CardView cardView = (CardView) view.findViewById(R.id.card); @@ -240,7 +242,7 @@ public abstract class AbsActivitiesAdapter extends Adapter imp final StatusViewHolder statusViewHolder = (StatusViewHolder) holder; statusViewHolder.displayStatus(getContext(), getImageLoader(), getImageLoadingHandler(), getTwitterWrapper(), - getProfileImageStyle(), getMediaPreviewStyle(), status, null); + getProfileImageStyle(), getMediaPreviewStyle(), status, null, false); break; } case ITEM_VIEW_TYPE_TITLE_SUMMARY: { diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java index 1de928f56..7e91b6ddc 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java @@ -41,11 +41,11 @@ public abstract class AbsStatusesAdapter extends Adapter implemen private final LayoutInflater mInflater; private final ImageLoaderWrapper mImageLoader; private final ImageLoadingHandler mLoadingHandler; - private final int mCardLayoutResource; private final AsyncTwitterWrapper mTwitterWrapper; private final int mCardBackgroundColor; private final int mTextSize; private final int mProfileImageStyle, mMediaPreviewStyle; + private final boolean mCompactCards; private boolean mLoadMoreIndicatorEnabled; private StatusAdapterListener mStatusAdapterListener; @@ -60,11 +60,7 @@ public abstract class AbsStatusesAdapter extends Adapter implemen final SharedPreferencesWrapper preferences = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); mTextSize = preferences.getInt(KEY_TEXT_SIZE, context.getResources().getInteger(R.integer.default_text_size)); - if (compact) { - mCardLayoutResource = R.layout.card_item_status_compact; - } else { - mCardLayoutResource = R.layout.card_item_status; - } + mCompactCards = compact; mProfileImageStyle = Utils.getProfileImageStyle(preferences.getString(KEY_PROFILE_IMAGE_STYLE, null)); mMediaPreviewStyle = Utils.getMediaPreviewStyle(preferences.getString(KEY_MEDIA_PREVIEW_STYLE, null)); } @@ -143,9 +139,14 @@ public abstract class AbsStatusesAdapter extends Adapter implemen public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case ITEM_VIEW_TYPE_STATUS: { - final View view = mInflater.inflate(mCardLayoutResource, parent, false); - final CardView cardView = (CardView) view.findViewById(R.id.card); - if (cardView != null) { + final View view; + if (mCompactCards) { + view = mInflater.inflate(R.layout.card_item_status_compact, parent, false); + final View itemContent = view.findViewById(R.id.item_content); + itemContent.setBackgroundColor(mCardBackgroundColor); + } else { + view = mInflater.inflate(R.layout.card_item_status, parent, false); + final CardView cardView = (CardView) view.findViewById(R.id.card); cardView.setCardBackgroundColor(mCardBackgroundColor); } final StatusViewHolder holder = new StatusViewHolder(this, view); @@ -205,9 +206,9 @@ public abstract class AbsStatusesAdapter extends Adapter implemen } @Override - public void onItemMenuClick(ViewHolder holder, int position) { + public void onItemMenuClick(ViewHolder holder, View menuView, int position) { if (mStatusAdapterListener != null) { - mStatusAdapterListener.onStatusMenuClick((StatusViewHolder) holder, position); + mStatusAdapterListener.onStatusMenuClick((StatusViewHolder) holder, menuView, position); } } @@ -230,7 +231,7 @@ public abstract class AbsStatusesAdapter extends Adapter implemen void onStatusClick(StatusViewHolder holder, int position); - void onStatusMenuClick(StatusViewHolder holder, int position); + void onStatusMenuClick(StatusViewHolder holder, View menuView, int position); } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/CursorStatusesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/CursorStatusesAdapter.java index a3cab2f30..c4ddb73c8 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/CursorStatusesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/CursorStatusesAdapter.java @@ -65,7 +65,7 @@ public class CursorStatusesAdapter extends AbsStatusesAdapter { @Override protected void bindStatus(StatusViewHolder holder, int position) { mCursor.moveToPosition(position); - holder.displayStatus(mCursor, mIndices); + holder.displayStatus(mCursor, mIndices, true); } @Override diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java index 7d2866a47..3e29b9bb0 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java @@ -21,6 +21,7 @@ package org.mariotaku.twidere.adapter; import android.content.Context; import android.support.v7.widget.RecyclerView.ViewHolder; +import android.view.View; import org.mariotaku.twidere.model.ParcelableActivity; import org.mariotaku.twidere.view.holder.ActivityTitleSummaryViewHolder; @@ -35,7 +36,7 @@ public class ParcelableActivitiesAdapter extends AbsActivitiesAdapter mData; public ParcelableActivitiesAdapter(Context context, boolean compact) { - super(context,compact); + super(context, compact); } @Override @@ -73,7 +74,7 @@ public class ParcelableActivitiesAdapter extends AbsActivitiesAdapter>, OnItemClickListener, OnItemLongClickListener, - OnMenuItemClickListener { +public class ExtensionsListFragment extends BaseListFragment implements Constants, LoaderCallbacks> { private ExtensionsAdapter mAdapter; private PackageManager mPackageManager; private PermissionsManager mPermissionsManager; - private ExtensionInfo mSelectedExtension; - private ListView mListView; - private PopupMenu mPopupMenu; @Override public void onActivityCreated(final Bundle savedInstanceState) { @@ -63,47 +57,22 @@ public class ExtensionsListFragment extends BaseListFragment implements Constant mPermissionsManager = new PermissionsManager(getActivity()); mAdapter = new ExtensionsAdapter(getActivity()); setListAdapter(mAdapter); - mListView = getListView(); - mListView.setOnItemClickListener(this); - mListView.setOnItemLongClickListener(this); + final ListView listView = getListView(); + listView.setOnCreateContextMenuListener(this); getLoaderManager().initLoader(0, null, this); setListShown(false); } + @Override + public void onStop() { + super.onStop(); + } + @Override public Loader> onCreateLoader(final int id, final Bundle args) { return new ExtensionsListLoader(getActivity(), mPackageManager); } - @Override - public void onItemClick(final AdapterView parent, final View view, final int position, final long id) { - openSettings(mAdapter.getItem(position)); - } - - @Override - public boolean onItemLongClick(final AdapterView parent, final View view, final int position, final long id) { - mSelectedExtension = mAdapter.getItem(position); - if (mSelectedExtension == null) return false; - mPopupMenu = PopupMenu.getInstance(getActivity(), view); - mPopupMenu.inflate(R.menu.action_extension); - final Menu menu = mPopupMenu.getMenu(); - final MenuItem settings = menu.findItem(MENU_SETTINGS); - final Intent intent = mSelectedExtension.pname != null && mSelectedExtension.settings != null ? new Intent( - INTENT_ACTION_EXTENSION_SETTINGS) : null; - if (intent != null) { - intent.setClassName(mSelectedExtension.pname, mSelectedExtension.settings); - } - settings.setVisible(intent != null && mPackageManager.queryIntentActivities(intent, 0).size() == 1); - mPopupMenu.setOnMenuItemClickListener(this); - mPopupMenu.show(); - return true; - } - - @Override - public void onLoaderReset(final Loader> loader) { - mAdapter.setData(null); - } - @Override public void onLoadFinished(final Loader> loader, final List data) { mAdapter.setData(data); @@ -111,26 +80,13 @@ public class ExtensionsListFragment extends BaseListFragment implements Constant } @Override - public boolean onMenuItemClick(final MenuItem item) { - if (mSelectedExtension == null) return false; - switch (item.getItemId()) { - case MENU_SETTINGS: { - openSettings(mSelectedExtension); - break; - } - case MENU_DELETE: { - final Uri packageUri = Uri.parse("package:" + mSelectedExtension.pname); - final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); - startActivity(uninstallIntent); - break; - } - case MENU_REVOKE: { - mPermissionsManager.revoke(mSelectedExtension.pname); - mAdapter.notifyDataSetChanged(); - break; - } - } - return false; + public void onLoaderReset(final Loader> loader) { + mAdapter.setData(null); + } + + @Override + public void onListItemClick(final ListView l, final View v, final int position, final long id) { + openSettings(mAdapter.getItem(position)); } @Override @@ -140,11 +96,44 @@ public class ExtensionsListFragment extends BaseListFragment implements Constant } @Override - public void onStop() { - if (mPopupMenu != null) { - mPopupMenu.dismiss(); + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + final MenuInflater inflater = new MenuInflater(v.getContext()); + inflater.inflate(R.menu.action_extension, menu); + final AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo; + final ExtensionInfo extensionInfo = mAdapter.getItem(adapterMenuInfo.position); + if (extensionInfo.pname != null && extensionInfo.settings != null) { + final Intent intent = new Intent(INTENT_ACTION_EXTENSION_SETTINGS); + intent.setClassName(extensionInfo.pname, extensionInfo.settings); + Utils.setMenuItemAvailability(menu, MENU_SETTINGS, mPackageManager.queryIntentActivities(intent, 0).size() == 1); + } else { + Utils.setMenuItemAvailability(menu, MENU_SETTINGS, false); } - super.onStop(); + + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo(); + final ExtensionInfo extensionInfo = mAdapter.getItem(adapterMenuInfo.position); + switch (item.getItemId()) { + case MENU_SETTINGS: { + openSettings(extensionInfo); + break; + } + case MENU_DELETE: { + uninstallExtension(extensionInfo); + break; + } + case MENU_REVOKE: { + mPermissionsManager.revoke(extensionInfo.pname); + mAdapter.notifyDataSetChanged(); + break; + } + default: { + return false; + } + } + return true; } private boolean openSettings(final ExtensionInfo info) { @@ -160,4 +149,17 @@ public class ExtensionsListFragment extends BaseListFragment implements Constant return true; } + private boolean uninstallExtension(final ExtensionInfo info) { + if (info == null) return false; + final Uri packageUri = Uri.parse("package:" + info.pname); + final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); + try { + startActivity(uninstallIntent); + } catch (final Exception e) { + Log.w(LOGTAG, e); + return false; + } + return true; + } + } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java index d9e23666b..395ac3357 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java @@ -20,6 +20,7 @@ import android.view.ViewGroup; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; +import org.mariotaku.menucomponent.widget.PopupMenu; import org.mariotaku.twidere.R; import org.mariotaku.twidere.adapter.AbsStatusesAdapter; import org.mariotaku.twidere.adapter.AbsStatusesAdapter.StatusAdapterListener; @@ -37,6 +38,8 @@ import org.mariotaku.twidere.view.HeaderDrawerLayout.DrawerCallback; import org.mariotaku.twidere.view.holder.GapViewHolder; import org.mariotaku.twidere.view.holder.StatusViewHolder; +import static org.mariotaku.twidere.util.Utils.setMenuForStatus; + /** * Created by mariotaku on 14/11/5. */ @@ -44,16 +47,16 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl OnRefreshListener, DrawerCallback, RefreshScrollTopInterface, StatusAdapterListener { + private final Object mStatusesBusCallback; private AbsStatusesAdapter mAdapter; private LinearLayoutManager mLayoutManager; - - private final Object mStatusesBusCallback; private View mContentView; private SharedPreferences mPreferences; private View mProgressContainer; private SwipeRefreshLayout mSwipeRefreshLayout; private RecyclerView mRecyclerView; private SimpleDrawerCallback mDrawerCallback; + private OnScrollListener mOnScrollListener = new OnScrollListener() { private int mScrollState; @@ -74,6 +77,7 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl } } }; + private PopupMenu mPopupMenu; protected AbsStatusesFragment() { mStatusesBusCallback = createMessageBusCallback(); @@ -230,6 +234,14 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl super.onStop(); } + @Override + public void onDestroyView() { + if (mPopupMenu != null) { + mPopupMenu.dismiss(); + } + super.onDestroyView(); + } + @Override public void onGapClick(GapViewHolder holder, int position) { final ParcelableStatus status = mAdapter.getStatus(position); @@ -276,12 +288,15 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl } @Override - public void onStatusMenuClick(StatusViewHolder holder, int position) { - final Bundle args = new Bundle(); - args.putParcelable(EXTRA_STATUS, mAdapter.getStatus(position)); - final StatusMenuDialogFragment f = new StatusMenuDialogFragment(); - f.setArguments(args); - f.show(getActivity().getSupportFragmentManager(), "status_menu"); + public void onStatusMenuClick(StatusViewHolder holder, View menuView, int position) { + if (mPopupMenu != null) { + mPopupMenu.dismiss(); + } + final PopupMenu popupMenu = PopupMenu.getInstance(mAdapter.getContext(), menuView); + popupMenu.inflate(R.menu.action_status); + setMenuForStatus(mAdapter.getContext(), popupMenu.getMenu(), mAdapter.getStatus(position)); + popupMenu.show(); + mPopupMenu = popupMenu; } @Override diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java index 99857f766..92cd9f9a6 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java @@ -118,8 +118,6 @@ abstract class BaseUserListsListFragment extends BasePullToRefreshListFragment i } mAdapter = new ParcelableUserListsListAdapter(getActivity()); mListView = getListView(); - mListView.setDivider(null); - mListView.setSelector(android.R.color.transparent); mListView.setFastScrollEnabled(mPreferences.getBoolean(KEY_FAST_SCROLL_THUMB, false)); // final long account_id = args.getLong(EXTRA_ACCOUNT_ID, -1); // if (mAccountId != account_id) { diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java index d8feee437..f7f032def 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java @@ -101,7 +101,7 @@ public class RetweetQuoteDialogFragment extends BaseSupportDialogFragment implem holder.displayStatus(context, loader, handler, twitter, profileImageStyle, mediaPreviewStyle, - getStatus(), null); + getStatus(), null, true); view.findViewById(R.id.item_menu).setVisibility(View.GONE); view.findViewById(R.id.action_buttons).setVisibility(View.GONE); view.findViewById(R.id.reply_retweet_status).setVisibility(View.GONE); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java index d249ece36..063bcbfe6 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java @@ -85,6 +85,7 @@ import org.mariotaku.twidere.util.CompareUtils; import org.mariotaku.twidere.util.ImageLoaderWrapper; import org.mariotaku.twidere.util.ImageLoadingHandler; import org.mariotaku.twidere.util.OnLinkClickHandler; +import org.mariotaku.twidere.util.StatisticUtils; import org.mariotaku.twidere.util.ThemeUtils; import org.mariotaku.twidere.util.TwidereLinkify; import org.mariotaku.twidere.util.TwitterCardUtils; @@ -98,6 +99,7 @@ import org.mariotaku.twidere.view.holder.GapViewHolder; import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder; import org.mariotaku.twidere.view.holder.StatusViewHolder; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; @@ -269,7 +271,7 @@ public class StatusFragment extends BaseSupportFragment } @Override - public void onStatusMenuClick(StatusViewHolder holder, int position) { + public void onStatusMenuClick(StatusViewHolder holder, View itemView, int position) { final Bundle args = new Bundle(); args.putParcelable(EXTRA_STATUS, mStatusAdapter.getStatus(position)); final StatusMenuDialogFragment f = new StatusMenuDialogFragment(); @@ -324,6 +326,11 @@ public class StatusFragment extends BaseSupportFragment final int position = mStatusAdapter.findPositionById(itemId); mLayoutManager.scrollToPositionWithOffset(position, top); } + try { + StatisticUtils.writeStatusOpen(status, null, 0); + } catch (IOException e) { + e.printStackTrace(); + } setState(STATE_LOADED); } else { //TODO show errors @@ -402,6 +409,7 @@ public class StatusFragment extends BaseSupportFragment private final int mTextSize; private final int mCardBackgroundColor; private final boolean mIsCompact; + private final int mProfileImageStyle; private ParcelableStatus mStatus; private ParcelableCredentials mStatusAccount; @@ -424,6 +432,7 @@ public class StatusFragment extends BaseSupportFragment mNameFirst = preferences.getBoolean(KEY_NAME_FIRST, true); mNicknameOnly = preferences.getBoolean(KEY_NICKNAME_ONLY, true); mTextSize = preferences.getInt(KEY_TEXT_SIZE, res.getInteger(R.integer.default_text_size)); + mProfileImageStyle = Utils.getProfileImageStyle(preferences.getString(KEY_PROFILE_IMAGE_STYLE, null)); mIsCompact = compact; if (compact) { mCardLayoutResource = R.layout.card_item_status_compact; @@ -483,7 +492,7 @@ public class StatusFragment extends BaseSupportFragment @Override public int getProfileImageStyle() { - return 0; + return mProfileImageStyle; } @Override @@ -550,8 +559,10 @@ public class StatusFragment extends BaseSupportFragment final DividerItemDecoration decoration = mFragment.getItemDecoration(); decoration.setDecorationStart(0); if (mReplies != null) { +// decoration.setDecorationEndOffset(2); decoration.setDecorationEnd(getItemCount() - 2); } else { +// decoration.setDecorationEndOffset(3); decoration.setDecorationEnd(getItemCount() - 3); } mFragment.mRecyclerView.invalidateItemDecorations(); @@ -602,11 +613,11 @@ public class StatusFragment extends BaseSupportFragment final View view; if (mIsCompact) { view = mInflater.inflate(R.layout.header_status_compact, parent, false); + final View cardView = view.findViewById(R.id.compact_card); + cardView.setBackgroundColor(mCardBackgroundColor); } else { view = mInflater.inflate(R.layout.header_status, parent, false); - } - final CardView cardView = (CardView) view.findViewById(R.id.card); - if (cardView != null) { + final CardView cardView = (CardView) view.findViewById(R.id.card); cardView.setCardBackgroundColor(mCardBackgroundColor); } return new DetailStatusViewHolder(this, view); @@ -646,7 +657,9 @@ public class StatusFragment extends BaseSupportFragment case VIEW_TYPE_LIST_STATUS: { final ParcelableStatus status = getStatus(position); final StatusViewHolder statusHolder = (StatusViewHolder) holder; - statusHolder.displayStatus(status); + // Display 'in reply to' for first item + // useful to indicate whether first tweet has reply or not + statusHolder.displayStatus(status, position == 0); break; } } @@ -693,9 +706,9 @@ public class StatusFragment extends BaseSupportFragment } @Override - public void onItemMenuClick(ViewHolder holder, int position) { + public void onItemMenuClick(ViewHolder holder, View itemView, int position) { if (mStatusAdapterListener != null) { - mStatusAdapterListener.onStatusMenuClick((StatusViewHolder) holder, position); + mStatusAdapterListener.onStatusMenuClick((StatusViewHolder) holder, itemView, position); } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusTranslateDialogFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusTranslateDialogFragment.java index 25179559c..d8a066721 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusTranslateDialogFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusTranslateDialogFragment.java @@ -136,7 +136,7 @@ public class StatusTranslateDialogFragment extends BaseSupportDialogFragment imp final int profileImageStyle = Utils.getProfileImageStyle(preferences.getString(KEY_PROFILE_IMAGE_STYLE, null)); final int mediaPreviewStyle = Utils.getMediaPreviewStyle(preferences.getString(KEY_MEDIA_PREVIEW_STYLE, null)); mHolder.displayStatus(activity, loader, handler, twitter, profileImageStyle, - mediaPreviewStyle, status, null); + mediaPreviewStyle, status, null, true); mStatusContainer.findViewById(R.id.item_menu).setVisibility(View.GONE); mStatusContainer.findViewById(R.id.action_buttons).setVisibility(View.GONE); mStatusContainer.findViewById(R.id.reply_retweet_status).setVisibility(View.GONE); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java index d8d9094da..5385fed9b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java @@ -1256,13 +1256,14 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener final Twitter twitter = getTwitterInstance(context, account_id, false); if (twitter == null) return SingleResponse.getInstance(); try { - final Relationship result = twitter.showFriendship(account_id, user_id); - if (result.isSourceBlockingTarget() || result.isSourceBlockedByTarget()) { + final Relationship relationship = twitter.showFriendship(account_id, user_id); + if (relationship.isSourceBlockingTarget() || relationship.isSourceBlockedByTarget()) { Utils.setLastSeen(context, user_id, -1); } else { Utils.setLastSeen(context, user_id, System.currentTimeMillis()); } - return SingleResponse.getInstance(result); + Utils.updateRelationship(context, relationship, account_id); + return SingleResponse.getInstance(relationship); } catch (final TwitterException e) { return SingleResponse.getInstance(e); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java index 234f26059..4b7e499bd 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java @@ -29,6 +29,7 @@ import android.view.View; import android.view.ViewGroup; import org.mariotaku.twidere.R; +import org.mariotaku.twidere.activity.iface.IThemedActivity; import org.mariotaku.twidere.adapter.support.SupportTabsAdapter; import org.mariotaku.twidere.fragment.iface.IBaseFragment.SystemWindowsInsetsCallback; import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface; @@ -59,13 +60,15 @@ public class UserListsFragment extends BaseSupportFragment implements RefreshScr final Bundle args = getArguments(); final FragmentActivity activity = getActivity(); mAdapter = new SupportTabsAdapter(activity, getChildFragmentManager(), null, 1); - mAdapter.addTab(UserListsListFragment.class, args, getString(R.string.lists), null, 0); - mAdapter.addTab(UserListMembershipsListFragment.class, args, - getString(R.string.lists_following_user), 0, 1); + mAdapter.addTab(UserListsListFragment.class, args, getString(R.string.follows), null, 0); + mAdapter.addTab(UserListMembershipsListFragment.class, args, getString(R.string.belongs_to), 0, 1); mViewPager.setAdapter(mAdapter); mViewPager.setOffscreenPageLimit(2); mPagerIndicator.setViewPager(mViewPager); mPagerIndicator.setTabDisplayOption(TabPagerIndicator.LABEL); + if (activity instanceof IThemedActivity) { + mPagerIndicator.setStripColor(((IThemedActivity) activity).getCurrentThemeColor()); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/BaseUserListsLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/BaseUserListsLoader.java index 8f91a6a3c..2264f7396 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/BaseUserListsLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/BaseUserListsLoader.java @@ -28,7 +28,7 @@ import org.mariotaku.twidere.model.ParcelableUserList; import org.mariotaku.twidere.util.collection.NoDuplicatesArrayList; import twitter4j.CursorSupport; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.UserList; @@ -79,7 +79,7 @@ public abstract class BaseUserListsLoader extends AsyncTaskLoader getCursoredUsers(Twitter twitter, CursorPaging paging) + protected abstract PageableResponseList getCursoredUsers(Twitter twitter, CursorPaging paging) throws TwitterException; @Override @@ -48,7 +48,7 @@ public abstract class CursorSupportUsersLoader extends BaseCursorSupportUsersLoa if (getCursor() > 0) { paging.setCursor(getCursor()); } - final PagableResponseList users = getCursoredUsers(twitter, paging); + final PageableResponseList users = getCursoredUsers(twitter, paging); if (users == null) return null; setCursorIds(users); return users; diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/MutesUsersLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/MutesUsersLoader.java index 713edb149..b0e350aa9 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/MutesUsersLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/MutesUsersLoader.java @@ -24,7 +24,7 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUser; import twitter4j.CursorPaging; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.User; @@ -39,7 +39,7 @@ public class MutesUsersLoader extends CursorSupportUsersLoader { } @Override - protected final PagableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) + protected final PageableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) throws TwitterException { if (twitter == null) return null; return twitter.getMutesUsersList(paging); diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableUserLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableUserLoader.java index c408d63f0..023f9544a 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableUserLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableUserLoader.java @@ -29,6 +29,7 @@ import android.support.v4.content.AsyncTaskLoader; import org.mariotaku.querybuilder.Expression; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.model.ParcelableUser; +import org.mariotaku.twidere.model.ParcelableUser.CachedIndices; import org.mariotaku.twidere.model.SingleResponse; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; @@ -82,8 +83,9 @@ public final class ParcelableUserLoader extends AsyncTaskLoader 0) { + final CachedIndices indices = new CachedIndices(cur); cur.moveToFirst(); - return SingleResponse.getInstance(new ParcelableUser(cur, mAccountId)); + return SingleResponse.getInstance(new ParcelableUser(cur, indices, mAccountId)); } } finally { cur.close(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TileImageLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TileImageLoader.java index 878ed4b8b..0c3e208a2 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TileImageLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TileImageLoader.java @@ -24,8 +24,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Rect; import android.net.Uri; import android.os.Handler; import android.support.v4.content.AsyncTaskLoader; @@ -93,7 +91,7 @@ public class TileImageLoader extends AsyncTaskLoader { try { // from SD cache if (ImageValidator.checkImageValidity(cacheFile)) - return decodeImageInternal(cacheFile); + return decodeBitmapOnly(cacheFile, true); final InputStream is = mDownloader.getStream(url, new AccountExtra(mAccountId)); if (is == null) return Result.nullInstance(); @@ -110,13 +108,13 @@ public class TileImageLoader extends AsyncTaskLoader { if (!ImageValidator.checkImageValidity(cacheFile)) { // The file is corrupted, so we remove it from // cache. - final Result result = decodeBitmapOnly(cacheFile); + final Result result = decodeBitmapOnly(cacheFile, false); if (cacheFile.isFile()) { cacheFile.delete(); } return result; } - return decodeImageInternal(cacheFile); + return decodeBitmapOnly(cacheFile, true); } catch (final Exception e) { mHandler.post(new DownloadErrorRunnable(this, mListener, e)); return Result.getInstance(cacheFile, e); @@ -124,7 +122,7 @@ public class TileImageLoader extends AsyncTaskLoader { } else if (ContentResolver.SCHEME_FILE.equals(scheme)) { final File file = new File(mUri.getPath()); try { - return decodeImage(file); + return decodeBitmapOnly(file, true); } catch (final Exception e) { return Result.getInstance(file, e); } @@ -132,7 +130,7 @@ public class TileImageLoader extends AsyncTaskLoader { return Result.nullInstance(); } - protected Result decodeBitmapOnly(final File file) { + protected Result decodeBitmapOnly(final File file, boolean useDecoder) { final String path = file.getAbsolutePath(); final BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; @@ -142,22 +140,7 @@ public class TileImageLoader extends AsyncTaskLoader { o.inJustDecodeBounds = false; o.inSampleSize = BitmapUtils.computeSampleSize(mFallbackSize / Math.max(width, height)); final Bitmap bitmap = BitmapFactory.decodeFile(path, o); - return Result.getInstance(bitmap, Exif.getOrientation(file), file); - } - - protected Result decodeImage(final File file) { - final String path = file.getAbsolutePath(); - try { - final BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(path, false); - final int width = decoder.getWidth(); - final int height = decoder.getHeight(); - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = BitmapUtils.computeSampleSize(mFallbackSize / Math.max(width, height)); - final Bitmap bitmap = decoder.decodeRegion(new Rect(0, 0, width, height), options); - return Result.getInstance(decoder, bitmap, Exif.getOrientation(file), file); - } catch (final IOException e) { - return decodeBitmapOnly(file); - } + return Result.getInstance(useDecoder, bitmap, Exif.getOrientation(file), file); } @Override @@ -165,10 +148,6 @@ public class TileImageLoader extends AsyncTaskLoader { forceLoad(); } - private Result decodeImageInternal(final File file) throws IOException { - if (ImageValidator.checkImageValidity(file)) return decodeImage(file); - throw new InvalidImageException(); - } private void dump(final InputStream is, final OutputStream os) throws IOException { final byte buffer[] = new byte[1024]; @@ -192,47 +171,41 @@ public class TileImageLoader extends AsyncTaskLoader { void onProgressUpdate(long downloaded); } - public static class InvalidImageException extends IOException { - - private static final long serialVersionUID = 8996099908714452289L; - - } - public static class Result { public final Bitmap bitmap; public final File file; public final Exception exception; - public final BitmapRegionDecoder decoder; + public final boolean useDecoder; public final int orientation; - public Result(final BitmapRegionDecoder decoder, final Bitmap bitmap, final int orientation, final File file, - final Exception exception) { + public Result(final boolean useDecoder, final Bitmap bitmap, final int orientation, + final File file, final Exception exception) { this.bitmap = bitmap; this.file = file; - this.decoder = decoder; + this.useDecoder = useDecoder; this.orientation = orientation; this.exception = exception; } public boolean hasData() { - return bitmap != null || decoder != null; + return bitmap != null || useDecoder; } public static Result getInstance(final Bitmap bitmap, final int orientation, final File file) { - return new Result(null, bitmap, orientation, file, null); + return new Result(false, bitmap, orientation, file, null); } - public static Result getInstance(final BitmapRegionDecoder decoder, final Bitmap bitmap, final int orientation, - final File file) { - return new Result(decoder, bitmap, orientation, file, null); + public static Result getInstance(final boolean useDecoder, final Bitmap bitmap, + final int orientation, final File file) { + return new Result(useDecoder, bitmap, orientation, file, null); } public static Result getInstance(final File file, final Exception e) { - return new Result(null, null, 0, file, e); + return new Result(false, null, 0, file, e); } public static Result nullInstance() { - return new Result(null, null, 0, null, null); + return new Result(false, null, 0, null, null); } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserBlocksLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserBlocksLoader.java index c30dfb427..20137bae4 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserBlocksLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserBlocksLoader.java @@ -24,7 +24,7 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUser; import twitter4j.CursorPaging; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.User; @@ -39,7 +39,7 @@ public class UserBlocksLoader extends CursorSupportUsersLoader { } @Override - protected final PagableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) + protected final PageableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) throws TwitterException { if (twitter == null) return null; return twitter.getBlocksList(paging); diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFollowersLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFollowersLoader.java index 75fa5b35f..f28dd641d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFollowersLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFollowersLoader.java @@ -24,7 +24,7 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUser; import twitter4j.CursorPaging; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.User; @@ -44,7 +44,7 @@ public class UserFollowersLoader extends CursorSupportUsersLoader { } @Override - protected PagableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) + protected PageableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) throws TwitterException { if (twitter == null) return null; if (mUserId > 0) diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFriendsLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFriendsLoader.java index 5242cd27b..0fd6bcb86 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFriendsLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFriendsLoader.java @@ -24,7 +24,7 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUser; import twitter4j.CursorPaging; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.User; @@ -44,7 +44,7 @@ public class UserFriendsLoader extends CursorSupportUsersLoader { } @Override - protected PagableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) + protected PageableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) throws TwitterException { if (twitter == null) return null; if (mUserId > 0) diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembersLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembersLoader.java index 549c6f70e..611e6138f 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembersLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembersLoader.java @@ -24,7 +24,7 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUser; import twitter4j.CursorPaging; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.User; @@ -47,7 +47,7 @@ public class UserListMembersLoader extends CursorSupportUsersLoader { } @Override - public PagableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) + public PageableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) throws TwitterException { if (twitter == null) return null; if (mListId > 0) diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembershipsLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembershipsLoader.java index 8c34e2c03..21ca695e0 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembershipsLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembershipsLoader.java @@ -23,7 +23,7 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUserList; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.UserList; @@ -43,7 +43,7 @@ public class UserListMembershipsLoader extends BaseUserListsLoader { } @Override - public PagableResponseList getUserLists(final Twitter twitter) throws TwitterException { + public PageableResponseList getUserLists(final Twitter twitter) throws TwitterException { if (twitter == null) return null; if (mUserId > 0) return twitter.getUserListMemberships(mUserId, getCursor()); diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListSubscribersLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListSubscribersLoader.java index 7f8c82682..346e465c7 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListSubscribersLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListSubscribersLoader.java @@ -24,7 +24,7 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUser; import twitter4j.CursorPaging; -import twitter4j.PagableResponseList; +import twitter4j.PageableResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.User; @@ -48,7 +48,7 @@ public class UserListSubscribersLoader extends CursorSupportUsersLoader { } @Override - public PagableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) + public PageableResponseList getCursoredUsers(final Twitter twitter, final CursorPaging paging) throws TwitterException { if (twitter == null) return null; if (mListId > 0) diff --git a/twidere/src/main/java/org/mariotaku/twidere/popup/AccountSelectorPopupWindow.java b/twidere/src/main/java/org/mariotaku/twidere/popup/AccountSelectorPopupWindow.java new file mode 100644 index 000000000..b827bfa15 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/popup/AccountSelectorPopupWindow.java @@ -0,0 +1,166 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.popup; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Build; +import android.support.annotation.NonNull; +import android.util.SparseBooleanArray; +import android.view.LayoutInflater; +import android.view.LayoutInflater.Factory; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.PopupWindow; + +import org.apache.commons.lang3.ArrayUtils; +import org.mariotaku.twidere.R; +import org.mariotaku.twidere.adapter.ArrayAdapter; +import org.mariotaku.twidere.app.TwidereApplication; +import org.mariotaku.twidere.model.ParcelableAccount; +import org.mariotaku.twidere.util.ImageLoaderWrapper; +import org.mariotaku.twidere.util.ThemeUtils; +import org.mariotaku.twidere.util.ThemedViewFactory; +import org.mariotaku.twidere.util.Utils; + +import java.util.List; + +/** + * Created by mariotaku on 15/1/12. + */ +public class AccountSelectorPopupWindow { + + private final Context mContext; + private final View mAnchor; + private final PopupWindow mPopup; + private final AccountsGridAdapter mAdapter; + private final GridView mGridView; + private AccountSelectionListener mAccountSelectionListener; + + public AccountSelectorPopupWindow(Context context, View anchor) { + mContext = context; + mAnchor = anchor; + final int themeAttr; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + themeAttr = android.R.attr.actionOverflowMenuStyle; + } else { + themeAttr = android.R.attr.popupMenuStyle; + } + mAdapter = new AccountsGridAdapter(context); + final Resources resources = context.getResources(); + final LayoutInflater inflater = LayoutInflater.from(context); + final int themeColor = ThemeUtils.getUserAccentColor(context); + if (!(context instanceof Factory)) { + inflater.setFactory2(new ThemedViewFactory(themeColor)); + } + final View contentView = inflater.inflate(R.layout.popup_account_selector, null); + mGridView = (GridView) contentView.findViewById(R.id.grid_view); + mGridView.setAdapter(mAdapter); + mGridView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE); + mGridView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (mAccountSelectionListener == null) return; + + mAccountSelectionListener.onSelectionChanged(getSelectedAccountIds()); + } + }); + mPopup = new PopupWindow(context, null, themeAttr); + mPopup.setFocusable(true); + mPopup.setWidth(Utils.getActionBarHeight(context) * 2); + mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT); + mPopup.setContentView(contentView); + } + + public void setAccountSelectionListener(AccountSelectionListener listener) { + mAccountSelectionListener = listener; + } + + public boolean isShowing() { + return mPopup.isShowing(); + } + + public void dismiss() { + mPopup.dismiss(); + } + + public void show() { + mPopup.showAsDropDown(mAnchor); + } + + public interface AccountSelectionListener { + + public void onSelectionChanged(long[] accountIds); + + } + + public void setSelectedAccountIds(long[] accountIds) { + if (accountIds == null) { + mGridView.clearChoices(); + } + for (int i = 0, j = mAdapter.getCount(); i < j; i++) { + mGridView.setItemChecked(i, ArrayUtils.contains(accountIds, mAdapter.getItem(i).account_id)); + } + } + + @NonNull + public long[] getSelectedAccountIds() { + final long[] accountIds = new long[mGridView.getCheckedItemCount()]; + final SparseBooleanArray positions = mGridView.getCheckedItemPositions(); + for (int i = 0, j = positions.size(), k = 0; i < j; i++) { + if (positions.valueAt(i)) { + accountIds[k++] = mAdapter.getItem(positions.keyAt(i)).account_id; + } + } + return accountIds; + } + + public void setAccounts(List accounts) { + mAdapter.clear(); + if (accounts != null) { + mAdapter.addAll(accounts); + } + } + + private static class AccountsGridAdapter extends ArrayAdapter { + + private final ImageLoaderWrapper mImageLoader; + + public AccountsGridAdapter(Context context) { + super(context, R.layout.grid_item_selector_account); + mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final View view = super.getView(position, convertView, parent); + final ParcelableAccount account = getItem(position); + final ImageView icon = (ImageView) view.findViewById(android.R.id.icon); + mImageLoader.displayProfileImage(icon, account.profile_image_url); + return view; + } + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/preference/ThemeBackgroundPreference.java b/twidere/src/main/java/org/mariotaku/twidere/preference/ThemeBackgroundPreference.java index a4639e685..9e998d620 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/preference/ThemeBackgroundPreference.java +++ b/twidere/src/main/java/org/mariotaku/twidere/preference/ThemeBackgroundPreference.java @@ -80,7 +80,7 @@ public class ThemeBackgroundPreference extends DialogPreference implements Const @Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { - setValue(restorePersistedValue ? getPersistedString(mValue) : (String) defaultValue); + setValue(restorePersistedValue ? getPersistedString(null) : (String) defaultValue); updateSummary(); } @@ -96,7 +96,7 @@ public class ThemeBackgroundPreference extends DialogPreference implements Const private void persistValue(String value) { // Always persist/notify the first time. - if (!TextUtils.equals(getPersistedString(mValue), value)) { + if (!TextUtils.equals(getPersistedString(null), value)) { persistString(value); notifyChanged(); } @@ -158,7 +158,7 @@ public class ThemeBackgroundPreference extends DialogPreference implements Const final Dialog dialog = getDialog(); final SharedPreferences preferences = getSharedPreferences(); if (dialog instanceof AlertDialog && preferences != null) { - mValue = getPersistedString(mValue); + mValue = getPersistedString(null); final Resources res = dialog.getContext().getResources(); final LayoutInflater inflater = dialog.getLayoutInflater(); final ListView listView = ((AlertDialog) dialog).getListView(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java index 64b58b302..def694f15 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java +++ b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java @@ -56,6 +56,7 @@ import com.squareup.otto.Bus; import org.apache.commons.lang3.ArrayUtils; import org.mariotaku.jsonserializer.JSONFileIO; import org.mariotaku.querybuilder.Expression; +import org.mariotaku.querybuilder.query.SQLSelectQuery; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.activity.support.HomeActivity; @@ -66,6 +67,7 @@ import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.model.SupportTabSpec; import org.mariotaku.twidere.model.UnreadItem; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; +import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; import org.mariotaku.twidere.provider.TwidereDataStore.Drafts; @@ -84,7 +86,8 @@ import org.mariotaku.twidere.util.SQLiteDatabaseWrapper; import org.mariotaku.twidere.util.SQLiteDatabaseWrapper.LazyLoadCallback; import org.mariotaku.twidere.util.SharedPreferencesWrapper; import org.mariotaku.twidere.util.TwidereArrayUtils; -import org.mariotaku.twidere.util.TwidereQueryBuilder; +import org.mariotaku.twidere.util.TwidereQueryBuilder.CachedUsersQueryBuilder; +import org.mariotaku.twidere.util.TwidereQueryBuilder.ConversationQueryBuilder; import org.mariotaku.twidere.util.UserColorNameUtils; import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.util.collection.NoDuplicatesCopyOnWriteArrayList; @@ -300,6 +303,27 @@ public final class TwidereDataProvider extends ContentProvider implements Consta mDatabaseWrapper.update(table, values, where.getSQL(), args); rowId = mDatabaseWrapper.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_IGNORE); + } else if (tableId == TABLE_ID_CACHED_RELATIONSHIPS) { + final long accountId = values.getAsLong(CachedRelationships.ACCOUNT_ID); + final long userId = values.getAsLong(CachedRelationships.USER_ID); + final Expression where = Expression.and( + Expression.equals(CachedRelationships.ACCOUNT_ID, accountId), + Expression.equals(CachedRelationships.USER_ID, userId) + ); + if (mDatabaseWrapper.update(table, values, where.getSQL(), null) > 0) { + final String[] projection = {CachedRelationships._ID}; + final Cursor c = mDatabaseWrapper.query(table, projection, where.getSQL(), null, + null, null, null); + if (c.moveToFirst()) { + rowId = c.getLong(0); + } else { + rowId = 0; + } + c.close(); + } else { + rowId = mDatabaseWrapper.insertWithOnConflict(table, null, values, + SQLiteDatabase.CONFLICT_IGNORE); + } } else if (shouldReplaceOnConflict(tableId)) { rowId = mDatabaseWrapper.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE); @@ -437,9 +461,9 @@ public final class TwidereDataProvider extends ContentProvider implements Consta if (segments.size() != 4) return null; final long accountId = ParseUtils.parseLong(segments.get(2)); final long conversationId = ParseUtils.parseLong(segments.get(3)); - final String query = TwidereQueryBuilder.ConversationQueryBuilder.buildByConversationId(projection, + final SQLSelectQuery query = ConversationQueryBuilder.buildByConversationId(projection, accountId, conversationId, selection, sortOrder); - final Cursor c = mDatabaseWrapper.rawQuery(query, selectionArgs); + final Cursor c = mDatabaseWrapper.rawQuery(query.getSQL(), selectionArgs); setNotificationUri(c, DirectMessages.CONTENT_URI); return c; } @@ -448,12 +472,28 @@ public final class TwidereDataProvider extends ContentProvider implements Consta if (segments.size() != 4) return null; final long accountId = ParseUtils.parseLong(segments.get(2)); final String screenName = segments.get(3); - final String query = TwidereQueryBuilder.ConversationQueryBuilder.buildByScreenName(projection, + final SQLSelectQuery query = ConversationQueryBuilder.buildByScreenName(projection, accountId, screenName, selection, sortOrder); - final Cursor c = mDatabaseWrapper.rawQuery(query, selectionArgs); + final Cursor c = mDatabaseWrapper.rawQuery(query.getSQL(), selectionArgs); setNotificationUri(c, DirectMessages.CONTENT_URI); return c; } + case VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP: { + final long accountId = ParseUtils.parseLong(uri.getLastPathSegment(), -1); + final SQLSelectQuery query = CachedUsersQueryBuilder.buildWithRelationship(projection, + selection, sortOrder, accountId); + final Cursor c = mDatabaseWrapper.rawQuery(query.getSQL(), selectionArgs); + setNotificationUri(c, CachedUsers.CONTENT_URI); + return c; + } + case VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE: { + final long accountId = ParseUtils.parseLong(uri.getLastPathSegment(), -1); + final SQLSelectQuery query = CachedUsersQueryBuilder.buildWithScore(projection, + selection, sortOrder, accountId); + final Cursor c = mDatabaseWrapper.rawQuery(query.getSQL(), selectionArgs); + setNotificationUri(c, CachedUsers.CONTENT_URI); + return c; + } } if (table == null) return null; final Cursor c = mDatabaseWrapper.query(table, projection, selection, selectionArgs, null, null, sortOrder); diff --git a/twidere/src/main/java/org/mariotaku/twidere/task/CacheUsersStatusesTask.java b/twidere/src/main/java/org/mariotaku/twidere/task/CacheUsersStatusesTask.java index 8132ec9e0..2ead7cab4 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/task/CacheUsersStatusesTask.java +++ b/twidere/src/main/java/org/mariotaku/twidere/task/CacheUsersStatusesTask.java @@ -25,6 +25,7 @@ import android.content.Context; import com.twitter.Extractor; +import org.apache.commons.lang3.ArrayUtils; import org.mariotaku.querybuilder.Expression; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags; @@ -32,11 +33,15 @@ import org.mariotaku.twidere.provider.TwidereDataStore.CachedStatuses; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; import org.mariotaku.twidere.provider.TwidereDataStore.Filters; import org.mariotaku.twidere.util.TwitterWrapper.TwitterListResponse; +import org.mariotaku.twidere.util.Utils; import java.util.HashSet; import java.util.List; import java.util.Set; +import twitter4j.Relationship; +import twitter4j.Twitter; +import twitter4j.TwitterException; import twitter4j.User; import static org.mariotaku.twidere.util.ContentValuesCreator.createCachedUser; @@ -46,67 +51,69 @@ import static org.mariotaku.twidere.util.content.ContentResolverUtils.bulkInsert public class CacheUsersStatusesTask extends TwidereAsyncTask implements Constants { - private final TwitterListResponse[] all_statuses; - private final ContentResolver resolver; + private final TwitterListResponse[] responses; + private final Context context; - public CacheUsersStatusesTask(final Context context, final TwitterListResponse... all_statuses) { - resolver = context.getContentResolver(); - this.all_statuses = all_statuses; + public CacheUsersStatusesTask(final Context context, final TwitterListResponse... responses) { + this.context = context; + this.responses = responses; } @Override protected Void doInBackground(final Void... args) { - if (all_statuses == null || all_statuses.length == 0) return null; + if (responses == null || responses.length == 0) return null; + final ContentResolver resolver = context.getContentResolver(); final Extractor extractor = new Extractor(); - final Set cachedUsersValues = new HashSet<>(); - final Set cached_statuses_values = new HashSet<>(); - final Set hashtag_values = new HashSet<>(); - final Set userIds = new HashSet<>(); - final Set status_ids = new HashSet<>(); - final Set hashtags = new HashSet<>(); + final Set usersValues = new HashSet<>(); + final Set statusesValues = new HashSet<>(); + final Set hashTagValues = new HashSet<>(); + final Set allStatusIds = new HashSet<>(); + final Set allHashTags = new HashSet<>(); final Set users = new HashSet<>(); - for (final TwitterListResponse values : all_statuses) { - if (values == null || values.list == null) { + for (final TwitterListResponse response : responses) { + if (response == null || response.list == null) { continue; } - final List list = values.list; + final List list = response.list; + final Set userIds = new HashSet<>(); for (final twitter4j.Status status : list) { if (status == null || status.getId() <= 0) { continue; } - status_ids.add(status.getId()); - cached_statuses_values.add(createStatus(status, values.account_id)); - hashtags.addAll(extractor.extractHashtags(status.getText())); - final User user = status.getUser(); - if (user != null && user.getId() > 0) { - users.add(user); - final ContentValues filtered_users_values = new ContentValues(); - filtered_users_values.put(Filters.Users.NAME, user.getName()); - filtered_users_values.put(Filters.Users.SCREEN_NAME, user.getScreenName()); - final String filtered_users_where = Expression.equals(Filters.Users.USER_ID, user.getId()).getSQL(); - resolver.update(Filters.Users.CONTENT_URI, filtered_users_values, filtered_users_where, null); + if (status.isRetweet()) { + final User retweetUser = status.getRetweetedStatus().getUser(); + userIds.add(retweetUser.getId()); } + allStatusIds.add(status.getId()); + statusesValues.add(createStatus(status, response.account_id)); + allHashTags.addAll(extractor.extractHashtags(status.getText())); + final User user = status.getUser(); + users.add(user); + userIds.add(user.getId()); + final ContentValues filtered_users_values = new ContentValues(); + filtered_users_values.put(Filters.Users.NAME, user.getName()); + filtered_users_values.put(Filters.Users.SCREEN_NAME, user.getScreenName()); + final String filtered_users_where = Expression.equals(Filters.Users.USER_ID, user.getId()).getSQL(); + resolver.update(Filters.Users.CONTENT_URI, filtered_users_values, filtered_users_where, null); } } - bulkDelete(resolver, CachedStatuses.CONTENT_URI, CachedStatuses.STATUS_ID, status_ids, null, false); - bulkInsert(resolver, CachedStatuses.CONTENT_URI, cached_statuses_values); + bulkDelete(resolver, CachedStatuses.CONTENT_URI, CachedStatuses.STATUS_ID, allStatusIds, null, false); + bulkInsert(resolver, CachedStatuses.CONTENT_URI, statusesValues); - for (final String hashtag : hashtags) { - final ContentValues hashtag_value = new ContentValues(); - hashtag_value.put(CachedHashtags.NAME, hashtag); - hashtag_values.add(hashtag_value); + for (final String hashtag : allHashTags) { + final ContentValues values = new ContentValues(); + values.put(CachedHashtags.NAME, hashtag); + hashTagValues.add(values); } - bulkDelete(resolver, CachedHashtags.CONTENT_URI, CachedHashtags.NAME, hashtags, null, true); - bulkInsert(resolver, CachedHashtags.CONTENT_URI, hashtag_values); + bulkDelete(resolver, CachedHashtags.CONTENT_URI, CachedHashtags.NAME, allHashTags, null, true); + bulkInsert(resolver, CachedHashtags.CONTENT_URI, hashTagValues); for (final User user : users) { - userIds.add(user.getId()); - cachedUsersValues.add(createCachedUser(user)); + usersValues.add(createCachedUser(user)); } -// bulkDelete(resolver, CachedUsers.CONTENT_URI, CachedUsers.USER_ID, userIds, null, false); - bulkInsert(resolver, CachedUsers.CONTENT_URI, cachedUsersValues); + bulkInsert(resolver, CachedUsers.CONTENT_URI, usersValues); return null; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/text/TwidereURLSpan.java b/twidere/src/main/java/org/mariotaku/twidere/text/TwidereURLSpan.java index 76fc173ab..627ed74e2 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/text/TwidereURLSpan.java +++ b/twidere/src/main/java/org/mariotaku/twidere/text/TwidereURLSpan.java @@ -66,7 +66,8 @@ public class TwidereURLSpan extends URLSpan implements Constants { ds.setUnderlineText(true); } if ((highlightStyle & VALUE_LINK_HIGHLIGHT_OPTION_CODE_HIGHLIGHT) != 0) { - ds.setColor(ThemeUtils.getOptimalLinkColor(ds.linkColor, ds.getColor())); +// ds.setColor(ThemeUtils.getOptimalLinkColor(ds.linkColor, ds.getColor())); + ds.setColor(ds.linkColor); } } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/StatisticUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/StatisticUtils.java new file mode 100644 index 000000000..e255e7042 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/util/StatisticUtils.java @@ -0,0 +1,49 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util; + +import android.location.Location; +import android.support.v4.util.LogWriter; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.mariotaku.twidere.model.ParcelableStatus; + +import java.io.IOException; + +/** + * Created by mariotaku on 15/1/12. + */ +public class StatisticUtils { + + + public static void writeStatusOpen(ParcelableStatus status, Location location, int signalStrength) throws IOException { + final LogWriter writer = new LogWriter("Twidere"); + final CSVPrinter printer = CSVFormat.DEFAULT.print(writer); + printer.printRecord(status.account_id, status.id, status.user_id, status.user_screen_name, + status.text_html, fromStringLocation(location), signalStrength); + } + + private static String fromStringLocation(Location location) { + if (location == null) return ""; + return location.getLatitude() + "," + location.getLongitude(); + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java index 6ec45f6ef..85a97adaf 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java @@ -249,11 +249,6 @@ public class ThemeUtils implements Constants { ViewAccessor.setProgressTintList(progressBar, tintList); ViewAccessor.setProgressBackgroundTintList(progressBar, tintList); ViewAccessor.setIndeterminateTintList(progressBar, tintList); - } else if (view instanceof Switch) { -// final ColorStateList tintList = ColorStateList.valueOf(tintColor); -// final Switch switchView = (Switch) view; -// DrawableCompat.setTintList(switchView.getThumbDrawable(), tintList); -// DrawableCompat.setTintList(switchView.getTrackDrawable(), tintList); } else if (view instanceof CompoundButton) { final ColorStateList tintList = ColorStateList.valueOf(tintColor); final CompoundButton compoundButton = (CompoundButton) view; @@ -893,8 +888,8 @@ public class ThemeUtils implements Constants { public static void initTextView(TextView view) { if (view.isInEditMode()) return; final Context context = view.getContext(); - view.setLinkTextColor(ThemeUtils.getUserLinkTextColor(context)); - view.setHighlightColor(ThemeUtils.getUserHighlightColor(context)); +// view.setLinkTextColor(ThemeUtils.getUserLinkTextColor(context)); +// view.setHighlightColor(ThemeUtils.getUserHighlightColor(context)); view.setTypeface(ThemeUtils.getUserTypeface(context, view.getTypeface())); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ThemedViewFactory.java b/twidere/src/main/java/org/mariotaku/twidere/util/ThemedViewFactory.java new file mode 100644 index 000000000..29fe092ce --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ThemedViewFactory.java @@ -0,0 +1,47 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater.Factory2; +import android.view.View; + +/** + * Created by mariotaku on 15/1/13. + */ +public class ThemedViewFactory implements Factory2 { + + private final int mThemeColor; + + public ThemedViewFactory(int themeColor) { + mThemeColor = themeColor; + } + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + return ThemeUtils.createView(name, context, attrs, mThemeColor); + } + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + return null; + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java b/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java index 7f60085ce..c13c2598d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java @@ -22,23 +22,108 @@ package org.mariotaku.twidere.util; import org.mariotaku.querybuilder.Columns; import org.mariotaku.querybuilder.Columns.Column; import org.mariotaku.querybuilder.Expression; +import org.mariotaku.querybuilder.Join; +import org.mariotaku.querybuilder.Join.Operation; import org.mariotaku.querybuilder.OrderBy; import org.mariotaku.querybuilder.SQLQueryBuilder; import org.mariotaku.querybuilder.Selectable; +import org.mariotaku.querybuilder.Table; import org.mariotaku.querybuilder.Tables; import org.mariotaku.querybuilder.query.SQLSelectQuery; +import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships; +import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Conversation; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox; +import java.util.Locale; + public class TwidereQueryBuilder { + public static final class CachedUsersQueryBuilder { + + public static SQLSelectQuery buildWithRelationship(final String[] projection, + final String selection, + final String sortOrder, + final long accountId) { + return buildWithRelationship(Utils.getColumnsFromProjection(projection), selection, + sortOrder, accountId); + } + + public static SQLSelectQuery buildWithRelationship(final Selectable select, + final String selection, + final String sortOrder, + final long accountId) { + final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); + qb.select(select).from(new Tables(CachedUsers.TABLE_NAME)); + final Column relationshipsUserId = new Column(new Table(CachedRelationships.TABLE_NAME), + CachedRelationships.USER_ID); + final Column usersUserId = new Column(new Table(CachedUsers.TABLE_NAME), + CachedRelationships.USER_ID); + final Column relationshipsAccountId = new Column(new Table(CachedRelationships.TABLE_NAME), + CachedRelationships.ACCOUNT_ID); + final Expression on = Expression.and( + Expression.equals(relationshipsUserId, usersUserId), + Expression.equals(relationshipsAccountId, accountId) + ); + qb.join(new Join(false, Operation.LEFT, new Table(CachedRelationships.TABLE_NAME), on)); + if (selection != null) { + qb.where(new Expression(selection)); + } + if (sortOrder != null) { + qb.orderBy(new OrderBy(sortOrder)); + } + return qb.build(); + } + + public static SQLSelectQuery buildWithScore(final String[] projection, + final String selection, + final String sortOrder, + final long accountId) { + final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); + final Selectable select = Utils.getColumnsFromProjection(projection); + final Column[] columns = new Column[CachedUsers.COLUMNS.length + 1]; + for (int i = 0, j = columns.length - 1; i < j; i++) { + final String column = CachedUsers.COLUMNS[i]; + if (CachedUsers._ID.equals(column) || CachedUsers.USER_ID.equals(column)) { + columns[i] = new Column(new Table(CachedUsers.TABLE_NAME), column, column); + } else { + columns[i] = new Column(column); + } + } + final String expr = String.format(Locale.ROOT, "%s * 100 + %s * 50 - %s * 100 - %s * 100 - %s * 100", + valueOrZero(CachedRelationships.FOLLOWING, CachedRelationships.FOLLOWED_BY, + CachedRelationships.BLOCKING, CachedRelationships.BLOCKED_BY, + CachedRelationships.MUTING)); + columns[columns.length - 1] = new Column(expr, "score"); + qb.select(select); + qb.from(buildWithRelationship(new Columns(columns), null, null, accountId)); + if (selection != null) { + qb.where(new Expression(selection)); + } + if (sortOrder != null) { + qb.orderBy(new OrderBy(sortOrder)); + } + return qb.build(); + } + + private static Object[] valueOrZero(String... columns) { + final String[] result = new String[columns.length]; + for (int i = 0, j = columns.length; i < j; i++) { + result[i] = String.format(Locale.ROOT, "CASE WHEN %s IS NULL THEN 0 ELSE %s END", + columns[i], columns[i]); + } + return result; + } + + } + public static final class ConversationQueryBuilder { - public static final String buildByConversationId(final String[] projection, final long account_id, - final long conversationId, final String selection, final String sortOrder) { + public static SQLSelectQuery buildByConversationId(final String[] projection, final long account_id, + final long conversationId, final String selection, final String sortOrder) { final Selectable select = Utils.getColumnsFromProjection(projection); final SQLSelectQuery.Builder qb = SQLQueryBuilder.select(select); qb.from(new Tables(DirectMessages.TABLE_NAME)); @@ -54,11 +139,11 @@ public class TwidereQueryBuilder { qb.where(Expression.and(accountIdWhere, conversationWhere)); } qb.orderBy(new OrderBy(sortOrder != null ? sortOrder : Conversation.DEFAULT_SORT_ORDER)); - return qb.build().getSQL(); + return qb.build(); } - public static final String buildByScreenName(final String[] projection, final long account_id, - final String screen_name, final String selection, final String sortOrder) { + public static SQLSelectQuery buildByScreenName(final String[] projection, final long account_id, + final String screen_name, final String selection, final String sortOrder) { final Selectable select = Utils.getColumnsFromProjection(projection); final SQLSelectQuery.Builder qb = SQLQueryBuilder.select(select); qb.select(select); @@ -74,7 +159,7 @@ public class TwidereQueryBuilder { qb.where(Expression.and(accountIdWhere, incomingWhere, outgoingWhere)); } qb.orderBy(new OrderBy(sortOrder != null ? sortOrder : Conversation.DEFAULT_SORT_ORDER)); - return qb.build().getSQL(); + return qb.build(); } } @@ -154,12 +239,12 @@ public class TwidereQueryBuilder { public static final class DirectMessagesQueryBuilder { - public static final SQLSelectQuery build() { + public static SQLSelectQuery build() { return build(null, null, null); } - public static final SQLSelectQuery build(final String[] projection, final String selection, - final String sortOrder) { + public static SQLSelectQuery build(final String[] projection, final String selection, + final String sortOrder) { final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); final Selectable select = Utils.getColumnsFromProjection(projection); qb.select(select).from(new Tables(DirectMessages.Inbox.TABLE_NAME)); diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index 9bf3ba289..d875921d1 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -110,6 +110,7 @@ import org.apache.http.NameValuePair; import org.json.JSONException; import org.mariotaku.jsonserializer.JSONSerializer; import org.mariotaku.menucomponent.internal.menu.MenuUtils; +import org.mariotaku.menucomponent.widget.PopupMenu; import org.mariotaku.querybuilder.AllColumns; import org.mariotaku.querybuilder.Columns; import org.mariotaku.querybuilder.Columns.Column; @@ -231,6 +232,7 @@ import javax.net.ssl.SSLException; import edu.ucdavis.earlybird.UCDService; import twitter4j.DirectMessage; import twitter4j.RateLimitStatus; +import twitter4j.Relationship; import twitter4j.Status; import twitter4j.Twitter; import twitter4j.TwitterConstants; @@ -262,43 +264,51 @@ public final class Utils implements Constants, TwitterConstants { private static final String UA_TEMPLATE = "Mozilla/5.0 (Linux; Android %s; %s Build/%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.111 Safari/537.36"; - public static final Pattern PATTERN_XML_RESOURCE_IDENTIFIER = Pattern.compile("res\\/xml\\/([\\w_]+)\\.xml"); + public static final Pattern PATTERN_XML_RESOURCE_IDENTIFIER = Pattern.compile("res/xml/([\\w_]+)\\.xml"); - public static final Pattern PATTERN_RESOURCE_IDENTIFIER = Pattern.compile("@([\\w_]+)\\/([\\w_]+)"); + public static final Pattern PATTERN_RESOURCE_IDENTIFIER = Pattern.compile("@([\\w_]+)/([\\w_]+)"); private static final UriMatcher CONTENT_PROVIDER_URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); private static final UriMatcher LINK_HANDLER_URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); static { - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Accounts.CONTENT_PATH, TABLE_ID_ACCOUNTS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Statuses.CONTENT_PATH, TABLE_ID_STATUSES); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Mentions.CONTENT_PATH, TABLE_ID_MENTIONS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH, TABLE_ID_DRAFTS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH, TABLE_ID_CACHED_USERS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Users.CONTENT_PATH, TABLE_ID_FILTERED_USERS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Accounts.CONTENT_PATH, + TABLE_ID_ACCOUNTS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Statuses.CONTENT_PATH, + TABLE_ID_STATUSES); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Mentions.CONTENT_PATH, + TABLE_ID_MENTIONS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH, + TABLE_ID_DRAFTS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH, + TABLE_ID_CACHED_USERS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Users.CONTENT_PATH, + TABLE_ID_FILTERED_USERS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Keywords.CONTENT_PATH, TABLE_ID_FILTERED_KEYWORDS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Sources.CONTENT_PATH, TABLE_ID_FILTERED_SOURCES); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Links.CONTENT_PATH, TABLE_ID_FILTERED_LINKS); - CONTENT_PROVIDER_URI_MATCHER - .addURI(TwidereDataStore.AUTHORITY, DirectMessages.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Links.CONTENT_PATH, + TABLE_ID_FILTERED_LINKS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.CONTENT_PATH, + TABLE_ID_DIRECT_MESSAGES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Inbox.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES_INBOX); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Outbox.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES_OUTBOX); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Conversation.CONTENT_PATH + "/#/#", TABLE_ID_DIRECT_MESSAGES_CONVERSATION); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Conversation.CONTENT_PATH_SCREEN_NAME - + "/#/*", TABLE_ID_DIRECT_MESSAGES_CONVERSATION_SCREEN_NAME); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Conversation.CONTENT_PATH_SCREEN_NAME + "/#/*", + TABLE_ID_DIRECT_MESSAGES_CONVERSATION_SCREEN_NAME); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.ConversationEntries.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRIES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedTrends.Local.CONTENT_PATH, TABLE_ID_TRENDS_LOCAL); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Tabs.CONTENT_PATH, TABLE_ID_TABS); - CONTENT_PROVIDER_URI_MATCHER - .addURI(TwidereDataStore.AUTHORITY, CachedStatuses.CONTENT_PATH, TABLE_ID_CACHED_STATUSES); - CONTENT_PROVIDER_URI_MATCHER - .addURI(TwidereDataStore.AUTHORITY, CachedHashtags.CONTENT_PATH, TABLE_ID_CACHED_HASHTAGS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Tabs.CONTENT_PATH, + TABLE_ID_TABS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedStatuses.CONTENT_PATH, + TABLE_ID_CACHED_STATUSES); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedHashtags.CONTENT_PATH, + TABLE_ID_CACHED_HASHTAGS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedRelationships.CONTENT_PATH, TABLE_ID_CACHED_RELATIONSHIPS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, SavedSearches.CONTENT_PATH, @@ -314,7 +324,8 @@ public final class Utils implements Constants, TwitterConstants { VIRTUAL_TABLE_ID_NOTIFICATIONS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Permissions.CONTENT_PATH, VIRTUAL_TABLE_ID_PERMISSIONS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DNS.CONTENT_PATH + "/*", VIRTUAL_TABLE_ID_DNS); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DNS.CONTENT_PATH + "/*", + VIRTUAL_TABLE_ID_DNS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedImages.CONTENT_PATH, VIRTUAL_TABLE_ID_CACHED_IMAGES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CacheFiles.CONTENT_PATH + "/*", @@ -333,6 +344,10 @@ public final class Utils implements Constants, TwitterConstants { VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_DATABASE_READY, VIRTUAL_TABLE_ID_DATABASE_READY); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_RELATIONSHIP + "/#", + VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP); + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_SCORE + "/#", + VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE); LINK_HANDLER_URI_MATCHER.addURI(AUTHORITY_STATUS, null, LINK_ID_STATUS); LINK_HANDLER_URI_MATCHER.addURI(AUTHORITY_USER, null, LINK_ID_USER); @@ -645,6 +660,7 @@ public final class Utils implements Constants, TwitterConstants { // 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)); @@ -3838,6 +3854,12 @@ public final class Utils implements Constants, TwitterConstants { return in.size() != out.size(); } + public static void updateRelationship(Context context, Relationship relationship, long accountId) { + final ContentResolver resolver = context.getContentResolver(); + final ContentValues values = ContentValuesCreator.createCachedRelationship(relationship, accountId); + resolver.insert(CachedRelationships.CONTENT_URI, values); + } + private static Drawable getMetadataDrawable(final PackageManager pm, final ActivityInfo info, final String key) { if (pm == null || info == null || info.metaData == null || key == null || !info.metaData.containsKey(key)) return null; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/ActionIconTextView.java b/twidere/src/main/java/org/mariotaku/twidere/view/ActionIconThemedTextView.java similarity index 91% rename from twidere/src/main/java/org/mariotaku/twidere/view/ActionIconTextView.java rename to twidere/src/main/java/org/mariotaku/twidere/view/ActionIconThemedTextView.java index 677ced640..45b12bc70 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/ActionIconTextView.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/ActionIconThemedTextView.java @@ -27,26 +27,26 @@ import android.graphics.PorterDuff.Mode; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; -import android.widget.TextView; import org.mariotaku.twidere.R; +import org.mariotaku.twidere.view.themed.ThemedTextView; /** * Created by mariotaku on 14/11/20. */ -public class ActionIconTextView extends TextView { +public class ActionIconThemedTextView extends ThemedTextView { private int mColor, mDisabledColor, mActivatedColor; - public ActionIconTextView(Context context) { + public ActionIconThemedTextView(Context context) { this(context, null); } - public ActionIconTextView(Context context, AttributeSet attrs) { + public ActionIconThemedTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public ActionIconTextView(Context context, AttributeSet attrs, int defStyleAttr) { + public ActionIconThemedTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IconActionButton); mColor = a.getColor(R.styleable.IconActionButton_iabColor, 0); diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/ComposeSelectAccountButton.java b/twidere/src/main/java/org/mariotaku/twidere/view/ComposeSelectAccountButton.java index b615dd333..62adec3f4 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/ComposeSelectAccountButton.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/ComposeSelectAccountButton.java @@ -22,7 +22,6 @@ package org.mariotaku.twidere.view; import android.content.Context; import android.graphics.Canvas; import android.support.annotation.NonNull; -import android.support.v4.view.ViewCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.Adapter; @@ -35,8 +34,10 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.StackView; import org.mariotaku.twidere.R; +import org.mariotaku.twidere.adapter.ArrayAdapter; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.model.ParcelableAccount; import org.mariotaku.twidere.util.ImageLoaderWrapper; @@ -46,7 +47,7 @@ import org.mariotaku.twidere.view.iface.IColorLabelView.Helper; * Created by mariotaku on 14/12/8. */ public class ComposeSelectAccountButton extends ViewGroup { - private final AccountIconsAdapter mAccountIconsAdapter; + private final AccountIconsStackAdapter mAccountIconsAdapter; private final Helper mColorLabelHelper; public ComposeSelectAccountButton(Context context) { @@ -61,17 +62,34 @@ public class ComposeSelectAccountButton extends ViewGroup { super(context, attrs, defStyle); mColorLabelHelper = new Helper(this, context, attrs, defStyle); mColorLabelHelper.setIgnorePaddings(true); - mAccountIconsAdapter = new AccountIconsAdapter(context); - final RecyclerView recyclerView = new InternalRecyclerView(context); - final LinearLayoutManager linearLayoutManager = new MyLinearLayoutManager(context); - linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); -// linearLayoutManager.setReverseLayout(true); - recyclerView.setLayoutManager(linearLayoutManager); - recyclerView.setAdapter(mAccountIconsAdapter); - ViewCompat.setOverScrollMode(recyclerView, ViewCompat.OVER_SCROLL_NEVER); - addView(recyclerView); + mAccountIconsAdapter = new AccountIconsStackAdapter(context); + final StackView stackView = new InternalStackView(context); + stackView.setAdapter(mAccountIconsAdapter); + addView(stackView); +// final RecyclerView recyclerView = new InternalRecyclerView(context); +// final LinearLayoutManager linearLayoutManager = new MyLinearLayoutManager(context); +// linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); +// linearLayoutManager.setStackFromEnd(true); +//// linearLayoutManager.setReverseLayout(true); +// recyclerView.setLayoutManager(linearLayoutManager); +// recyclerView.setAdapter(mAccountIconsAdapter); +// ViewCompat.setOverScrollMode(recyclerView, ViewCompat.OVER_SCROLL_NEVER); +// addView(recyclerView); } + public void setSelectedAccounts(long[] accountIds) { + final ParcelableAccount[] accounts = ParcelableAccount.getAccounts(getContext(), accountIds); +// if (accounts != null) { +// final int[] colors = new int[accounts.length]; +// for (int i = 0, j = colors.length; i < j; i++) { +// colors[i] = accounts[i].color; +// } +// mColorLabelHelper.drawEnd(colors); +// } else { +// mColorLabelHelper.drawEnd(null); +// } + mAccountIconsAdapter.setSelectedAccounts(accounts); + } @Override protected void dispatchDraw(@NonNull final Canvas canvas) { @@ -95,15 +113,6 @@ public class ComposeSelectAccountButton extends ViewGroup { } } - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - for (int i = 0, j = getChildCount(); i < j; i++) { - final View child = getChildAt(i); - child.layout(getPaddingLeft(), getPaddingTop(), r - l - getPaddingRight(), - b - t - getPaddingBottom()); - } - } - static class AccountIconViewHolder extends ViewHolder { private final ImageView iconView; @@ -119,18 +128,13 @@ public class ComposeSelectAccountButton extends ViewGroup { } } - public void setSelectedAccounts(long[] accountIds) { - final ParcelableAccount[] accounts = ParcelableAccount.getAccounts(getContext(), accountIds); - if (accounts != null) { - final int[] colors = new int[accounts.length]; - for (int i = 0, j = colors.length; i < j; i++) { - colors[i] = accounts[i].color; - } - mColorLabelHelper.drawEnd(colors); - } else { - mColorLabelHelper.drawEnd(null); + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + for (int i = 0, j = getChildCount(); i < j; i++) { + final View child = getChildAt(i); + child.layout(getPaddingLeft(), getPaddingTop(), r - l - getPaddingRight(), + b - t - getPaddingBottom()); } - mAccountIconsAdapter.setSelectedAccounts(accounts); } private static class AccountIconsAdapter extends Adapter { @@ -139,6 +143,12 @@ public class ComposeSelectAccountButton extends ViewGroup { private final ImageLoaderWrapper mImageLoader; private ParcelableAccount[] mAccounts; + public AccountIconsAdapter(Context context) { + mContext = context; + mInflater = LayoutInflater.from(context); + mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); + } + public ImageLoaderWrapper getImageLoader() { return mImageLoader; } @@ -166,12 +176,67 @@ public class ComposeSelectAccountButton extends ViewGroup { mAccounts = accounts; notifyDataSetChanged(); } + } - public AccountIconsAdapter(Context context) { + private static class AccountIconsStackAdapter extends ArrayAdapter { + private final Context mContext; + private final LayoutInflater mInflater; + private final ImageLoaderWrapper mImageLoader; + + public AccountIconsStackAdapter(Context context) { + super(context, R.layout.adapter_item_compose_account); mContext = context; mInflater = LayoutInflater.from(context); mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final View view = super.getView(position, convertView, parent); + final ParcelableAccount account = getItem(position); + final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon); + mImageLoader.displayProfileImage(iconView, account.profile_image_url); + return view; + } + + + public void setSelectedAccounts(ParcelableAccount[] accounts) { + clear(); + if (accounts != null) { + addAll(accounts); + } + } + } + + private static class InternalStackView extends StackView { + private InternalStackView(Context context) { + super(context); + } + + + @Override + public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { + return false; + } + } + + private static class InternalRecyclerView extends RecyclerView { + public InternalRecyclerView(Context context) { + super(context); + setChildrenDrawingOrderEnabled(true); + } + + @Override + public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { + return false; + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + return childCount - i - 1; + } + + } private static class MyLinearLayoutManager extends LinearLayoutManager { @@ -181,18 +246,6 @@ public class ComposeSelectAccountButton extends ViewGroup { super(context); } - @Override - public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { - final RecyclerView.LayoutParams layoutParams = super.generateLayoutParams(c, attrs); - return layoutParams; - } - - @Override - public void onLayoutChildren(Recycler recycler, State state) { - state.getItemCount(); - super.onLayoutChildren(recycler, state); - } - private int findChildIndex(View view) { for (int i = 0, j = getChildCount(); i < j; i++) { if (getChildAt(i) == view) return i; @@ -240,20 +293,4 @@ public class ComposeSelectAccountButton extends ViewGroup { } } - private static class InternalRecyclerView extends RecyclerView { - public InternalRecyclerView(Context context) { - super(context); - setChildrenDrawingOrderEnabled(true); - } - - @Override - protected int getChildDrawingOrder(int childCount, int i) { - return childCount - i - 1; - } - - @Override - public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { - return false; - } - } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/TwidereMenuBar.java b/twidere/src/main/java/org/mariotaku/twidere/view/TwidereMenuBar.java index 0f081d81c..bec2f44ee 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/TwidereMenuBar.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/TwidereMenuBar.java @@ -3,6 +3,7 @@ package org.mariotaku.twidere.view; import android.content.Context; import android.content.res.Resources; import android.graphics.PorterDuff.Mode; +import android.os.Build; import android.util.AttributeSet; import android.view.Menu; import android.view.MenuItem; @@ -39,6 +40,9 @@ public class TwidereMenuBar extends MenuBar implements MenuBarListener, Constant mPopupItemColor = Utils.getContrastYIQ(popupItemBackgroundColor, colorDark, colorLight); mHighlightColor = isInEditMode() ? 0 : ThemeUtils.getUserAccentColor(getContext()); setMenuBarListener(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setPopupStyleAttribute(android.R.attr.actionOverflowMenuStyle); + } } public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java index bdde9a94d..848b42d62 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java @@ -1,7 +1,9 @@ package org.mariotaku.twidere.view.holder; import android.content.Context; +import android.content.res.Resources; import android.database.Cursor; +import android.graphics.PorterDuff.Mode; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.CardView; @@ -25,6 +27,7 @@ import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.view.CardMediaContainer; import org.mariotaku.twidere.view.ShapedImageView; import org.mariotaku.twidere.view.ShortTimeView; +import org.mariotaku.twidere.view.iface.IColorLabelView; import java.util.Locale; @@ -39,7 +42,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick private final IStatusesAdapter adapter; - private final ImageView retweetProfileImageView; + private final ImageView replyRetweetIcon; private final ShapedImageView profileImageView; private final ImageView profileTypeView; private final TextView textView; @@ -48,6 +51,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick private final ShortTimeView timeView; private final CardMediaContainer mediaPreviewContainer; private final TextView replyCountView, retweetCountView, favoriteCountView; + private final IColorLabelView itemContent; private StatusClickListener statusClickListener; @@ -59,12 +63,13 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick public StatusViewHolder(IStatusesAdapter adapter, View itemView) { super(itemView); this.adapter = adapter; + itemContent = (IColorLabelView) itemView.findViewById(R.id.item_content); profileImageView = (ShapedImageView) itemView.findViewById(R.id.profile_image); profileTypeView = (ImageView) itemView.findViewById(R.id.profile_type); textView = (TextView) itemView.findViewById(R.id.text); nameView = (TextView) itemView.findViewById(R.id.name); screenNameView = (TextView) itemView.findViewById(R.id.screen_name); - retweetProfileImageView = (ImageView) itemView.findViewById(R.id.retweet_profile_image); + replyRetweetIcon = (ImageView) itemView.findViewById(R.id.reply_retweet_icon); replyRetweetView = (TextView) itemView.findViewById(R.id.reply_retweet_status); timeView = (ShortTimeView) itemView.findViewById(R.id.time); @@ -116,32 +121,36 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick favoriteCountView.setTextSize(textSize); } - public void displayStatus(final ParcelableStatus status) { + public void displayStatus(final ParcelableStatus status, final boolean displayInReplyTo) { displayStatus(adapter.getContext(), adapter.getImageLoader(), adapter.getImageLoadingHandler(), adapter.getTwitterWrapper(), - adapter.getProfileImageStyle(), adapter.getMediaPreviewStyle(), status, null); + adapter.getProfileImageStyle(), adapter.getMediaPreviewStyle(), status, null, + displayInReplyTo); } public void displayStatus(final Context context, final ImageLoaderWrapper loader, final ImageLoadingHandler handler, final AsyncTwitterWrapper twitter, final int profileImageStyle, final int mediaPreviewStyle, @NonNull final ParcelableStatus status, - @Nullable final TranslationResult translation) { + @Nullable final TranslationResult translation, + final boolean displayInReplyTo) { final ParcelableMedia[] media = status.media; + replyRetweetIcon.setColorFilter(replyRetweetView.getCurrentTextColor(), Mode.SRC_ATOP); if (status.retweet_id > 0) { replyRetweetView.setText(context.getString(R.string.name_retweeted, status.retweeted_by_name)); + replyRetweetIcon.setImageResource(R.drawable.ic_activity_action_retweet); replyRetweetView.setVisibility(View.VISIBLE); - retweetProfileImageView.setVisibility(View.GONE); - } else if (status.in_reply_to_status_id > 0 && status.in_reply_to_user_id > 0) { + replyRetweetIcon.setVisibility(View.VISIBLE); + } else if (status.in_reply_to_status_id > 0 && status.in_reply_to_user_id > 0 && displayInReplyTo) { replyRetweetView.setText(context.getString(R.string.in_reply_to_name, status.in_reply_to_name)); + replyRetweetIcon.setImageResource(R.drawable.ic_activity_action_reply); replyRetweetView.setVisibility(View.VISIBLE); - retweetProfileImageView.setVisibility(View.GONE); + replyRetweetIcon.setVisibility(View.VISIBLE); } else { replyRetweetView.setText(null); replyRetweetView.setVisibility(View.GONE); - replyRetweetView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - retweetProfileImageView.setVisibility(View.GONE); + replyRetweetIcon.setVisibility(View.GONE); } final int typeIconRes = getUserTypeIconRes(status.user_is_verified, status.user_is_protected); @@ -158,12 +167,13 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick timeView.setTime(status.timestamp); final int userColor = UserColorNameUtils.getUserColor(context, status.user_id); - profileImageView.setBorderColor(userColor); + itemContent.drawStart(userColor); profileImageView.setStyle(profileImageStyle); loader.displayProfileImage(profileImageView, status.user_profile_image_url); mediaPreviewContainer.setStyle(mediaPreviewStyle); + mediaPreviewContainer.setVisibility(media != null && media.length > 0 ? View.VISIBLE : View.GONE); mediaPreviewContainer.displayMedia(media, loader, status.account_id, null, handler); if (translation != null) { textView.setText(translation.getText()); @@ -210,10 +220,12 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick } - public void displayStatus(Cursor cursor, CursorIndices indices) { + public void displayStatus(@NonNull Cursor cursor, @NonNull CursorIndices indices, + final boolean displayInReplyTo) { final ImageLoaderWrapper loader = adapter.getImageLoader(); final AsyncTwitterWrapper twitter = adapter.getTwitterWrapper(); final Context context = adapter.getContext(); + final Resources resources = context.getResources(); final long reply_count = cursor.getLong(indices.reply_count); final long retweet_count; @@ -239,19 +251,21 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick final ParcelableMedia[] media = ParcelableMedia.fromJSONString(cursor.getString(indices.media)); + replyRetweetIcon.setColorFilter(replyRetweetView.getCurrentTextColor(), Mode.SRC_ATOP); if (retweet_id > 0) { replyRetweetView.setText(context.getString(R.string.name_retweeted, retweeted_by_name)); + replyRetweetIcon.setImageResource(R.drawable.ic_activity_action_retweet); replyRetweetView.setVisibility(View.VISIBLE); - retweetProfileImageView.setVisibility(View.GONE); - } else if (in_reply_to_status_id > 0 && in_reply_to_user_id > 0) { + replyRetweetIcon.setVisibility(View.VISIBLE); + } else if (in_reply_to_status_id > 0 && in_reply_to_user_id > 0 && displayInReplyTo) { replyRetweetView.setText(context.getString(R.string.in_reply_to_name, in_reply_to_name)); + replyRetweetIcon.setImageResource(R.drawable.ic_activity_action_reply); replyRetweetView.setVisibility(View.VISIBLE); - retweetProfileImageView.setVisibility(View.GONE); + replyRetweetIcon.setVisibility(View.VISIBLE); } else { replyRetweetView.setText(null); replyRetweetView.setVisibility(View.GONE); - replyRetweetView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - retweetProfileImageView.setVisibility(View.GONE); + replyRetweetIcon.setVisibility(View.GONE); } final int typeIconRes = getUserTypeIconRes(cursor.getInt(indices.is_verified) == 1, @@ -269,13 +283,14 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick timeView.setTime(timestamp); final int userColor = UserColorNameUtils.getUserColor(context, user_id); - profileImageView.setBorderColor(userColor); + itemContent.drawStart(userColor); profileImageView.setStyle(adapter.getProfileImageStyle()); loader.displayProfileImage(profileImageView, user_profile_image_url); final String text_unescaped = cursor.getString(indices.text_unescaped); mediaPreviewContainer.setStyle(adapter.getMediaPreviewStyle()); + mediaPreviewContainer.setVisibility(media != null && media.length > 0 ? View.VISIBLE : View.GONE); mediaPreviewContainer.displayMedia(media, loader, account_id, null, adapter.getImageLoadingHandler()); textView.setText(text_unescaped); @@ -349,7 +364,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick break; } case R.id.item_menu: { - statusClickListener.onItemMenuClick(this, position); + statusClickListener.onItemMenuClick(this, v, position); break; } case R.id.profile_image: { diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedSwitch.java b/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedSwitch.java index e596ac67a..0e3e3d2d1 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedSwitch.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedSwitch.java @@ -20,12 +20,16 @@ package org.mariotaku.twidere.view.themed; import android.content.Context; +import android.content.res.ColorStateList; +import android.os.Build; +import android.support.v4.graphics.drawable.DrawableCompat; import android.util.AttributeSet; import android.widget.Switch; import org.mariotaku.twidere.util.ThemeUtils; +import org.mariotaku.twidere.view.iface.IThemedView; -public class ThemedSwitch extends Switch { +public class ThemedSwitch extends Switch implements IThemedView { public ThemedSwitch(final Context context) { this(context, null); @@ -36,4 +40,11 @@ public class ThemedSwitch extends Switch { ThemeUtils.initTextView(this); } + @Override + public void setThemeTintColor(ColorStateList color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + DrawableCompat.setTintList(getThumbDrawable(), color); + DrawableCompat.setTintList(getTrackDrawable(), color); + } + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedTextView.java b/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedTextView.java index 1b63eab51..d7d7e74df 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedTextView.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedTextView.java @@ -21,6 +21,7 @@ package org.mariotaku.twidere.view.themed; import android.content.Context; import android.content.res.ColorStateList; +import android.graphics.Color; import android.util.AttributeSet; import android.widget.TextView; @@ -44,6 +45,9 @@ public class ThemedTextView extends TextView implements IThemedView { @Override public void setThemeTintColor(ColorStateList color) { - setLinkTextColor(ThemeUtils.getOptimalLinkColor(color.getDefaultColor(), getCurrentTextColor())); + final int linkTextColor = ThemeUtils.getOptimalLinkColor(color.getDefaultColor(), getCurrentTextColor()); + final int red = Color.red(linkTextColor), green = Color.green(linkTextColor), blue = Color.blue(linkTextColor); + setLinkTextColor(linkTextColor); + setHighlightColor(Color.argb(0x66, red, green, blue)); } } diff --git a/twidere/src/main/res/drawable-hdpi/ic_activity_action_reply.png b/twidere/src/main/res/drawable-hdpi/ic_activity_action_reply.png new file mode 100755 index 0000000000000000000000000000000000000000..0082c29fa2c6757b09d37f411681831b265b9c46 GIT binary patch literal 617 zcmV-v0+#)WP)2Lj zQ_tWA^Dt2I>pAQ|yI9L^O2>F4XfIIGXRgBzB>!j79Lh-pRg{?BK;620rI!ekV`@_cfG2aZ8uJQohS#3knfu0kzvQ z5&`JY7f`H(az064#m^}vkYz!+T#une6n{aV0u=NO6oaz%RnkBfuv)HV2lpT;R~=B& zho2+ql378YIg9 zpl~jlYzv;BgL`qk=X<}W=@JQFy!Y<=`+n~I?oP!<{8J;&kNpG04o7N{<2Wgj)|66* zVxq?&0thcqbAoOGYzz%R@FZbXv|~aqhXx>gfzT_IwpRSi_!PBK6WB+Tukp2r@GT0D z&{Rmk7jt*045$$X`UViJ1CP;Wl$BYYbH+tLonCb|;0D8LpW&{(#UKad&0B8+;!Iwk z{D6=r>M}q+?fM^kK%2&yETKIl9^oDfsR7sy1h^A`))}9nY0Lb=xQ^b<0njIH86`ZR zERcKmUEnH#=p%ZK=8f5R5AcjJw}ST3qVYWfaL3_oAYj()OOF4*i-u1xRJW#rX3!Fl z2MrYaZ1&{<@0@g#@h0)<3#z}O7ztqQOOm2jPrrSSKqh#(i?`etKnf0;7h5!Fuy~4o c8~k?wU7F`%8A{PAeEWjC_y%@@3}W?sPikU^Y|MQ zz<6gcWgulBat6{8ik!gc?kNN1Q&=jM+IjXN%d*GO)9YcNmH`5v#CRvrKGX@oJ&*CD zo2UQ~{6Zq>M=d6zj=BLL_%4FJgl1aPS^EG4K81i!qCJjvP}=|mzr-w==A1=by8r~g zji4`}#)1m0JpclqO2B8(PLHkQEp0xc_vj6Jfu5iNihIbTzjqU_U5Mb9nI*H_+u7al zGmr0}i+~J9dj08M5rE)#(C#dnt}&qH@re6_u^_ns1U`cS&Y|tff!lT^K3qmUfJkQq`sFR5HVm$#*}g`K?SA0Z4#Wc=&!Qq;gn?{%O+H4-ZKR-Efp}TTw{RP! z9%Sp3U81{^Q5C*_h385jq&9LdpZ9>Yk-9iO4s~%J0M}vhFHl8*k(Oj*t*MJy0ki#% zwxVBX8k&bfb~kxXtOjBrZ%l6hF#tm9;x3Z+MA6m9mvUVAWH+L}jJE)_qV;G4vVTXo zJfHHg1+U+(FM|LKgw(|qw55Q+>ojV9Q*6R#Kgv6r%P_LF-cSKB5CVPx9Ye17g%`j` zKqfOCe3m+Mz5V4h+XRFxsei8E43sU2yt)Vius|kqFMkRgK~gjJ)ds*rRREI>S6z$= zzyev!{WY}83iu`fqn2${2CM<4F0P`@=2;YgnHX7`htVM<_utW6AimgHPysLlidQc> ziZ*KftwMdQ!B37gbC8@^Z&wAt%EmhGziIw62gH0l<$(7b-`!OOC@(Ut&p=GZYcBw& z>JlJtBGhU?Yo=+RQP0>Ic360000< KMNUMnLSTZq#(_Hk literal 0 HcmV?d00001 diff --git a/twidere/src/main/res/drawable-xxhdpi/ic_activity_action_reply.png b/twidere/src/main/res/drawable-xxhdpi/ic_activity_action_reply.png new file mode 100755 index 0000000000000000000000000000000000000000..6d6f966c78c07487382a65b0d65f51da3f9ce4b0 GIT binary patch literal 1249 zcmV<71Rnc|P)qBbGogE2E&^q7JPQ86`+qBvt;gg6X7n7tKE4}`1u;-GstI<8v&m|Zv1== z@)=a88B{D5A0qVag+k$Dw^K;BcP>*5II}seuhDV>h#sCDIMV#@sXqg2ZHNAyi zNB^RK&^2_Gwb4z>aZ+pUyif#AC((U{rY02S9mctg&Y;uiB*4xlG`96Hu>k>QGTuHz zo2|XtcMvdT=pe8z*msZLBNiawJYxc065n;Ko<(~7g!%zB9BTy2PS${cvk4!cqem=# zCEffK1NH&xkE90rg>-~4zQ^qwKvGsYSuH@#jQkyX-pNCLoqxdfYp#%L)*z}gULW{IU1(a6HDt;4VdC9la0D$`9h7bvmfz--p=uaCvg8Wd_Jx&GNdG86e|-I=_QRYp9a_44|r;f|&veT+J;U zt1gXZ{f#1PC!Mp3cfXl(nr6+!@pq*Z$_0?QnyO1f1g1xa%neZ5Z$FA=q7LHF@fgbu zkb%@hV1H#!$2bI%)=Jvo|CKnj8vlO*$_q@;ulcU28$fzQ@_=kFO0HAPHrPDcdq5%7 zJs=OrZDRTi@)_ha$c=!{AUA%#2Kfx~8RSO5XOJ5|UxR!Gxn literal 0 HcmV?d00001 diff --git a/twidere/src/main/res/layout/action_item_compose_send.xml b/twidere/src/main/res/layout/action_item_compose_send.xml index 121a4061b..26703673e 100644 --- a/twidere/src/main/res/layout/action_item_compose_send.xml +++ b/twidere/src/main/res/layout/action_item_compose_send.xml @@ -28,7 +28,10 @@ android:contentDescription="@string/send" android:gravity="center" android:orientation="horizontal" - android:padding="@dimen/element_spacing_normal"> + android:paddingBottom="@dimen/element_spacing_normal" + android:paddingLeft="@dimen/element_spacing_large" + android:paddingRight="@dimen/element_spacing_normal" + android:paddingTop="@dimen/element_spacing_normal"> + android:orientation="vertical" + android:showDividers="none"> - - + android:layout_height="0dp" + android:layout_weight="1" + android:animateLayoutChanges="true"> - + + + android:layout_height="match_parent" + android:layout_alignBottom="@+id/edit_text_container" + android:layout_alignLeft="@+id/edit_text_container" + android:layout_alignRight="@+id/edit_text_container" + android:layout_alignTop="@+id/edit_text_container" + android:alpha="0.2" + android:numColumns="@integer/grid_column_image_preview" + android:stretchMode="columnWidth" + tools:listitem="@layout/grid_item_image_preview"/> - - - - - - - - - - + android:layout_marginLeft="@dimen/element_spacing_minus_small" + android:layout_toRightOf="@+id/account_selector" + android:padding="@dimen/element_spacing_normal"> + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/activity_compose_actionbar.xml b/twidere/src/main/res/layout/activity_compose_actionbar.xml index 1f2f54e8f..03970a619 100644 --- a/twidere/src/main/res/layout/activity_compose_actionbar.xml +++ b/twidere/src/main/res/layout/activity_compose_actionbar.xml @@ -82,6 +82,6 @@ android:padding="2dp" android:visibility="gone"/> - + \ No newline at end of file diff --git a/twidere/src/main/res/layout/activity_compose_bottombar.xml b/twidere/src/main/res/layout/activity_compose_bottombar.xml index abc8a7431..f37878380 100644 --- a/twidere/src/main/res/layout/activity_compose_bottombar.xml +++ b/twidere/src/main/res/layout/activity_compose_bottombar.xml @@ -25,15 +25,19 @@ android:layout_width="match_parent" android:layout_height="?android:actionBarSize" android:baselineAligned="false" + android:divider="?android:dividerHorizontal" + android:dividerPadding="@dimen/element_spacing_normal" android:gravity="center_vertical" - android:orientation="horizontal"> + android:orientation="horizontal" + android:showDividers="middle"> + android:fadeScrollbars="false" + android:paddingRight="@dimen/element_spacing_normal"> - - \ No newline at end of file diff --git a/twidere/src/main/res/layout/activity_sign_in.xml b/twidere/src/main/res/layout/activity_sign_in.xml index b8e34cf7a..201e86504 100644 --- a/twidere/src/main/res/layout/activity_sign_in.xml +++ b/twidere/src/main/res/layout/activity_sign_in.xml @@ -68,6 +68,16 @@ android:orientation="horizontal" android:padding="8dp"> +