redesigned compose dialog
This commit is contained in:
parent
ff13288b65
commit
79e80bfaad
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<ParcelableCredentials> getCredentialsList(final Context context, final boolean activatedOnly) {
|
||||
return getCredentialsList(context, activatedOnly, false);
|
||||
}
|
||||
|
||||
public static List<ParcelableCredentials> getCredentialsList(final Context context, final boolean activatedOnly,
|
||||
final boolean officialKeyOnly) {
|
||||
if (context == null) return Collections.emptyList();
|
||||
final ArrayList<ParcelableCredentials> 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<ParcelableCredentials> getCredentialsList(final Context context, final boolean activatedOnly) {
|
||||
return getCredentialsList(context, activatedOnly, false);
|
||||
}
|
||||
|
||||
public static List<ParcelableCredentials> getCredentialsList(final Context context, final boolean activatedOnly,
|
||||
final boolean officialKeyOnly) {
|
||||
if (context == null) return Collections.emptyList();
|
||||
final ArrayList<ParcelableCredentials> 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);
|
||||
|
|
|
@ -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<ParcelableU
|
|||
|
||||
public final int background_color, link_color, text_color;
|
||||
|
||||
public final boolean is_cache;
|
||||
public final boolean is_cache, is_basic;
|
||||
|
||||
public ParcelableUser(final long account_id, final long id, final String name,
|
||||
final String screen_name, final String profile_image_url) {
|
||||
|
@ -106,11 +106,7 @@ public class ParcelableUser implements TwidereParcelable, Comparable<ParcelableU
|
|||
link_color = 0;
|
||||
text_color = 0;
|
||||
is_cache = true;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ParcelableUser(final Cursor cursor, final long account_id) {
|
||||
this(cursor, new CachedIndices(cursor), account_id);
|
||||
is_basic = true;
|
||||
}
|
||||
|
||||
public ParcelableUser(final Cursor cursor, CachedIndices indices, final long account_id) {
|
||||
|
@ -136,12 +132,13 @@ public class ParcelableUser implements TwidereParcelable, Comparable<ParcelableU
|
|||
url = indices.url != -1 ? cursor.getString(indices.url) : null;
|
||||
url_expanded = indices.url_expanded != -1 ? cursor.getString(indices.url_expanded) : null;
|
||||
profile_banner_url = indices.profile_banner_url != -1 ? cursor.getString(indices.profile_banner_url) : null;
|
||||
is_cache = true;
|
||||
description_unescaped = HtmlEscapeHelper.toPlainText(description_html);
|
||||
is_following = indices.is_following != -1 && cursor.getInt(indices.is_following) == 1;
|
||||
background_color = indices.background_color != -1 ? cursor.getInt(indices.background_color) : 0;
|
||||
link_color = indices.link_color != -1 ? cursor.getInt(indices.link_color) : 0;
|
||||
text_color = indices.text_color != -1 ? cursor.getInt(indices.text_color) : 0;
|
||||
is_cache = true;
|
||||
is_basic = indices.description_plain == -1 || indices.url == -1 || indices.location == -1;
|
||||
}
|
||||
|
||||
public ParcelableUser(final JSONParcel in) {
|
||||
|
@ -167,12 +164,13 @@ public class ParcelableUser implements TwidereParcelable, Comparable<ParcelableU
|
|||
statuses_count = in.readInt("statuses_count");
|
||||
favorites_count = in.readInt("favorites_count");
|
||||
listed_count = in.readInt("listed_count");
|
||||
is_cache = in.readBoolean("is_cache");
|
||||
url_expanded = in.readString("url_expanded");
|
||||
is_following = in.readBoolean("is_following");
|
||||
background_color = in.readInt("background_color");
|
||||
link_color = in.readInt("link_color");
|
||||
text_color = in.readInt("text_color");
|
||||
is_cache = in.readBoolean("is_cache");
|
||||
is_basic = in.readBoolean("is_basic");
|
||||
}
|
||||
|
||||
public ParcelableUser(final Parcel in) {
|
||||
|
@ -198,12 +196,13 @@ public class ParcelableUser implements TwidereParcelable, Comparable<ParcelableU
|
|||
statuses_count = in.readInt();
|
||||
favorites_count = in.readInt();
|
||||
listed_count = in.readInt();
|
||||
is_cache = in.readInt() == 1;
|
||||
url_expanded = in.readString();
|
||||
is_following = in.readInt() == 1;
|
||||
background_color = in.readInt();
|
||||
link_color = in.readInt();
|
||||
text_color = in.readInt();
|
||||
is_cache = in.readInt() == 1;
|
||||
is_basic = in.readInt() == 1;
|
||||
}
|
||||
|
||||
public ParcelableUser(final User user, final long account_id) {
|
||||
|
@ -236,11 +235,12 @@ public class ParcelableUser implements TwidereParcelable, Comparable<ParcelableU
|
|||
statuses_count = user.getStatusesCount();
|
||||
favorites_count = user.getFavouritesCount();
|
||||
listed_count = user.getListedCount();
|
||||
is_cache = false;
|
||||
is_following = user.isFollowing();
|
||||
background_color = ParseUtils.parseColor("#" + user.getProfileBackgroundColor(), 0);
|
||||
link_color = ParseUtils.parseColor("#" + user.getProfileLinkColor(), 0);
|
||||
text_color = ParseUtils.parseColor("#" + user.getProfileTextColor(), 0);
|
||||
is_cache = false;
|
||||
is_basic = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -289,12 +289,13 @@ public class ParcelableUser implements TwidereParcelable, Comparable<ParcelableU
|
|||
out.writeInt(statuses_count);
|
||||
out.writeInt(favorites_count);
|
||||
out.writeInt(listed_count);
|
||||
out.writeInt(is_cache ? 1 : 0);
|
||||
out.writeString(url_expanded);
|
||||
out.writeInt(is_following ? 1 : 0);
|
||||
out.writeInt(background_color);
|
||||
out.writeInt(link_color);
|
||||
out.writeInt(text_color);
|
||||
out.writeInt(is_cache ? 1 : 0);
|
||||
out.writeInt(is_basic ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -393,12 +394,13 @@ public class ParcelableUser implements TwidereParcelable, Comparable<ParcelableU
|
|||
out.writeInt("statuses_count", statuses_count);
|
||||
out.writeInt("favorites_count", favorites_count);
|
||||
out.writeInt("listed_count", listed_count);
|
||||
out.writeBoolean("is_cache", is_cache);
|
||||
out.writeString("url_expanded", url_expanded);
|
||||
out.writeBoolean("is_following", is_following);
|
||||
out.writeInt("background_color", background_color);
|
||||
out.writeInt("link_color", link_color);
|
||||
out.writeInt("text_color", text_color);
|
||||
out.writeBoolean("is_cache", is_cache);
|
||||
out.writeBoolean("is_basic", is_basic);
|
||||
}
|
||||
|
||||
public static final class CachedIndices {
|
||||
|
|
|
@ -205,10 +205,20 @@ public interface TwidereDataStore {
|
|||
public static interface CachedUsers extends CachedValues {
|
||||
|
||||
public static final String TABLE_NAME = "cached_users";
|
||||
|
||||
public static final String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
public static final String CONTENT_PATH_WITH_RELATIONSHIP = TABLE_NAME + "/with_relationship";
|
||||
|
||||
public static final String CONTENT_PATH_WITH_SCORE = TABLE_NAME + "/with_score";
|
||||
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
public static final Uri CONTENT_URI_WITH_RELATIONSHIP = Uri.withAppendedPath(BASE_CONTENT_URI,
|
||||
CONTENT_PATH_WITH_RELATIONSHIP);
|
||||
public static final Uri CONTENT_URI_WITH_SCORE = Uri.withAppendedPath(BASE_CONTENT_URI,
|
||||
CONTENT_PATH_WITH_SCORE);
|
||||
|
||||
public static final String USER_ID = "user_id";
|
||||
|
||||
public static final String CREATED_AT = "created_at";
|
||||
|
@ -848,10 +858,10 @@ public interface TwidereDataStore {
|
|||
|
||||
public static final String MUTING = "muting";
|
||||
|
||||
public static final String[] COLUMNS = {ACCOUNT_ID, USER_ID, FOLLOWING, FOLLOWED_BY, BLOCKING,
|
||||
public static final String[] COLUMNS = {_ID, ACCOUNT_ID, USER_ID, FOLLOWING, FOLLOWED_BY, BLOCKING,
|
||||
BLOCKED_BY, MUTING};
|
||||
|
||||
public static final String[] TYPES = {TYPE_INT, TYPE_INT, TYPE_BOOLEAN_DEFAULT_FALSE,
|
||||
public static final String[] TYPES = {TYPE_PRIMARY_KEY, TYPE_INT, TYPE_INT, TYPE_BOOLEAN_DEFAULT_FALSE,
|
||||
TYPE_BOOLEAN_DEFAULT_FALSE, TYPE_BOOLEAN_DEFAULT_FALSE, TYPE_BOOLEAN_DEFAULT_FALSE,
|
||||
TYPE_BOOLEAN_DEFAULT_FALSE};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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<InternalQuery> internalQueries = new ArrayList<InternalQuery>();
|
||||
private final List<InternalQuery> 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;
|
||||
}
|
||||
|
|
|
@ -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<T extends TwitterResponse> extends ResponseList<T>, CursorSupport {
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package twitter4j;
|
||||
|
||||
/**
|
||||
* ResponseList with cursor support.
|
||||
*
|
||||
* @author Yusuke Yamamoto - yusuke at mac.com
|
||||
*/
|
||||
public interface PageableResponseList<T extends TwitterResponse> extends ResponseList<T>, CursorSupport {
|
||||
|
||||
}
|
|
@ -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 <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package twitter4j;
|
||||
|
@ -461,7 +463,7 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PagableResponseList<User> getBlocksList() throws TwitterException {
|
||||
public PageableResponseList<User> 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<User> getBlocksList(final CursorPaging paging) throws TwitterException {
|
||||
public PageableResponseList<User> 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<User> getFollowersList(final CursorPaging paging) throws TwitterException {
|
||||
public PageableResponseList<User> 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<User> getFollowersList(final long userId, final CursorPaging paging)
|
||||
public PageableResponseList<User> 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<User> getFollowersList(final String screenName, final CursorPaging paging)
|
||||
public PageableResponseList<User> 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<User> getFriendsList(final CursorPaging paging) throws TwitterException {
|
||||
public PageableResponseList<User> 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<User> getFriendsList(final long userId, final CursorPaging paging)
|
||||
public PageableResponseList<User> 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<User> getFriendsList(final String screenName, final CursorPaging paging)
|
||||
public PageableResponseList<User> 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<User> getMutesUsersList() throws TwitterException {
|
||||
public PageableResponseList<User> 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<User> getMutesUsersList(final CursorPaging paging) throws TwitterException {
|
||||
public PageableResponseList<User> 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<User> getUserListMembers(final long listId, final CursorPaging paging)
|
||||
public PageableResponseList<User> 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<User> 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<User> 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<User> 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<User> 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<UserList> getUserListMemberships(final long cursor) throws TwitterException {
|
||||
public PageableResponseList<UserList> 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<UserList> getUserListMemberships(final long listMemberId, final long cursor)
|
||||
public PageableResponseList<UserList> getUserListMemberships(final long listMemberId, final long cursor)
|
||||
throws TwitterException {
|
||||
return getUserListMemberships(listMemberId, cursor, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagableResponseList<UserList> getUserListMemberships(final long listMemberId, final long cursor,
|
||||
final boolean filterToOwnedLists) throws TwitterException {
|
||||
public PageableResponseList<UserList> 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<UserList> getUserListMemberships(final String listMemberScreenName, final long cursor)
|
||||
public PageableResponseList<UserList> getUserListMemberships(final String listMemberScreenName, final long cursor)
|
||||
throws TwitterException {
|
||||
return getUserListMemberships(listMemberScreenName, cursor, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagableResponseList<UserList> getUserListMemberships(final String listMemberScreenName, final long cursor,
|
||||
final boolean filterToOwnedLists) throws TwitterException {
|
||||
public PageableResponseList<UserList> 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<UserList> getUserListOwnerships(final long cursor) throws TwitterException {
|
||||
public PageableResponseList<UserList> 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<UserList> getUserListOwnerships(final long listMemberId, final long cursor)
|
||||
public PageableResponseList<UserList> 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<UserList> getUserListOwnerships(final String listMemberScreenName, final long cursor)
|
||||
public PageableResponseList<UserList> 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<User> getUserListSubscribers(final long listId, final CursorPaging paging)
|
||||
public PageableResponseList<User> 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<User> getUserListSubscribers(final String slug, final long ownerId,
|
||||
final CursorPaging paging) throws TwitterException {
|
||||
public PageableResponseList<User> 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<User> getUserListSubscribers(final String slug, final String ownerScreenName,
|
||||
final CursorPaging paging) throws TwitterException {
|
||||
public PageableResponseList<User> 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<UserList> getUserListSubscriptions(final String listOwnerScreenName, final long cursor)
|
||||
public PageableResponseList<UserList> 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",
|
||||
|
|
|
@ -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<User> getFollowersList(CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getFollowersList(CursorPaging paging) throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getFollowersList(long userId, CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getFollowersList(long userId, CursorPaging paging) throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getFollowersList(String screenName, CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> 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<User> getFriendsList(CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getFriendsList(CursorPaging paging) throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getFriendsList(long userId, CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getFriendsList(long userId, CursorPaging paging) throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getFriendsList(String screenName, CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getFriendsList(String screenName, CursorPaging paging) throws TwitterException;
|
||||
|
||||
/**
|
||||
* Returns an array of numeric IDs for every user who has a pending request
|
||||
|
|
|
@ -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</a>
|
||||
* @since Twitter4J 2.2.3
|
||||
*/
|
||||
PagableResponseList<User> getUserListMembers(long listId, CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getUserListMembers(long listId, CursorPaging paging) throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getUserListMembers(String slug, long ownerId, CursorPaging paging)
|
||||
PageableResponseList<User> getUserListMembers(String slug, long ownerId, CursorPaging paging)
|
||||
throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getUserListMembers(String slug, String ownerScreenName, CursorPaging paging)
|
||||
PageableResponseList<User> getUserListMembers(String slug, String ownerScreenName, CursorPaging paging)
|
||||
throws TwitterException;
|
||||
|
||||
/**
|
||||
|
@ -212,7 +212,7 @@ public interface ListsResources {
|
|||
* lists/memberships | Twitter Developers</a>
|
||||
* @since Twitter4J 2.2.4
|
||||
*/
|
||||
PagableResponseList<UserList> getUserListMemberships(long cursor) throws TwitterException;
|
||||
PageableResponseList<UserList> getUserListMemberships(long cursor) throws TwitterException;
|
||||
|
||||
/**
|
||||
* List the lists the specified user has been added to. <br>
|
||||
|
@ -230,7 +230,7 @@ public interface ListsResources {
|
|||
* lists/memberships | Twitter Developers</a>
|
||||
* @since Twitter4J 2.2.4
|
||||
*/
|
||||
PagableResponseList<UserList> getUserListMemberships(long listMemberId, long cursor) throws TwitterException;
|
||||
PageableResponseList<UserList> getUserListMemberships(long listMemberId, long cursor) throws TwitterException;
|
||||
|
||||
/**
|
||||
* List the lists the specified user has been added to. <br>
|
||||
|
@ -253,7 +253,7 @@ public interface ListsResources {
|
|||
* lists/memberships | Twitter Developers</a>
|
||||
* @since Twitter4J 2.2.4
|
||||
*/
|
||||
PagableResponseList<UserList> getUserListMemberships(long listMemberId, long cursor, boolean filterToOwnedLists)
|
||||
PageableResponseList<UserList> getUserListMemberships(long listMemberId, long cursor, boolean filterToOwnedLists)
|
||||
throws TwitterException;
|
||||
|
||||
/**
|
||||
|
@ -272,7 +272,7 @@ public interface ListsResources {
|
|||
* lists/memberships | Twitter Developers</a>
|
||||
* @since Twitter4J 2.1.0
|
||||
*/
|
||||
PagableResponseList<UserList> getUserListMemberships(String listMemberScreenName, long cursor)
|
||||
PageableResponseList<UserList> getUserListMemberships(String listMemberScreenName, long cursor)
|
||||
throws TwitterException;
|
||||
|
||||
/**
|
||||
|
@ -296,14 +296,14 @@ public interface ListsResources {
|
|||
* lists/memberships | Twitter Developers</a>
|
||||
* @since Twitter4J 2.2.4
|
||||
*/
|
||||
PagableResponseList<UserList> getUserListMemberships(String listMemberScreenName, long cursor,
|
||||
PageableResponseList<UserList> getUserListMemberships(String listMemberScreenName, long cursor,
|
||||
boolean filterToOwnedLists) throws TwitterException;
|
||||
|
||||
PagableResponseList<UserList> getUserListOwnerships(long cursor) throws TwitterException;
|
||||
PageableResponseList<UserList> getUserListOwnerships(long cursor) throws TwitterException;
|
||||
|
||||
PagableResponseList<UserList> getUserListOwnerships(long listMemberId, long cursor) throws TwitterException;
|
||||
PageableResponseList<UserList> getUserListOwnerships(long listMemberId, long cursor) throws TwitterException;
|
||||
|
||||
PagableResponseList<UserList> getUserListOwnerships(String listMemberScreenName, long cursor)
|
||||
PageableResponseList<UserList> getUserListOwnerships(String listMemberScreenName, long cursor)
|
||||
throws TwitterException;
|
||||
|
||||
/**
|
||||
|
@ -374,12 +374,12 @@ public interface ListsResources {
|
|||
* lists/subscribers | Twitter Developers</a>
|
||||
* @since Twitter4J 2.2.3
|
||||
*/
|
||||
PagableResponseList<User> getUserListSubscribers(long listId, CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getUserListSubscribers(long listId, CursorPaging paging) throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getUserListSubscribers(String slug, long ownerId, CursorPaging paging)
|
||||
PageableResponseList<User> getUserListSubscribers(String slug, long ownerId, CursorPaging paging)
|
||||
throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getUserListSubscribers(String slug, String ownerScreenName, CursorPaging paging)
|
||||
PageableResponseList<User> getUserListSubscribers(String slug, String ownerScreenName, CursorPaging paging)
|
||||
throws TwitterException;
|
||||
|
||||
/**
|
||||
|
@ -398,7 +398,7 @@ public interface ListsResources {
|
|||
* lists/subscriptions | Twitter Developers</a>
|
||||
* @since Twitter4J 2.1.0
|
||||
*/
|
||||
PagableResponseList<UserList> getUserListSubscriptions(String listOwnerScreenName, long cursor)
|
||||
PageableResponseList<UserList> getUserListSubscriptions(String listOwnerScreenName, long cursor)
|
||||
throws TwitterException;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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</a>
|
||||
* @since Twitter4J 2.0.4
|
||||
*/
|
||||
PagableResponseList<User> getBlocksList() throws TwitterException;
|
||||
PageableResponseList<User> getBlocksList() throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getBlocksList(CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> 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<User> getMutesUsersList() throws TwitterException;
|
||||
PageableResponseList<User> getMutesUsersList() throws TwitterException;
|
||||
|
||||
PagableResponseList<User> getMutesUsersList(CursorPaging paging) throws TwitterException;
|
||||
PageableResponseList<User> getMutesUsersList(CursorPaging paging) throws TwitterException;
|
||||
|
||||
/**
|
||||
* Access to Twitter's suggested user list. This returns the list of
|
||||
|
|
|
@ -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<User> createPagableUserList(HttpResponse res) throws TwitterException;
|
||||
PageableResponseList<User> createPagableUserList(HttpResponse res) throws TwitterException;
|
||||
|
||||
PagableResponseList<UserList> createPagableUserListList(HttpResponse res) throws TwitterException;
|
||||
PageableResponseList<UserList> createPagableUserListList(HttpResponse res) throws TwitterException;
|
||||
|
||||
Place createPlace(HttpResponse res) throws TwitterException;
|
||||
|
||||
|
|
|
@ -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<User> createPagableUserList(final HttpResponse res) throws TwitterException {
|
||||
public PageableResponseList<User> createPagableUserList(final HttpResponse res) throws TwitterException {
|
||||
return UserJSONImpl.createPagableUserList(res, conf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagableResponseList<UserList> createPagableUserListList(final HttpResponse res) throws TwitterException {
|
||||
public PageableResponseList<UserList> createPagableUserListList(final HttpResponse res) throws TwitterException {
|
||||
return UserListJSONImpl.createPagableUserListList(res, conf);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<T> extends ResponseListImpl implements PagableResponseList {
|
||||
class PageableResponseListImpl<T> 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);
|
|
@ -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<User> createPagableUserList(final HttpResponse res, final Configuration conf)
|
||||
static PageableResponseList<User> 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<User> users = new PagableResponseListImpl<User>(size, json, res);
|
||||
final PageableResponseList<User> users = new PageableResponseListImpl<User>(size, json, res);
|
||||
for (int i = 0; i < size; i++) {
|
||||
final JSONObject userJson = list.getJSONObject(i);
|
||||
final User user = new UserJSONImpl(userJson);
|
||||
|
|
|
@ -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<UserList> createPagableUserListList(final HttpResponse res, final Configuration conf)
|
||||
static PageableResponseList<UserList> 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<UserList> users = new PagableResponseListImpl<UserList>(size, json, res);
|
||||
final PageableResponseList<UserList> users = new PageableResponseListImpl<UserList>(size, json, res);
|
||||
for (int i = 0; i < size; i++) {
|
||||
final JSONObject userListJson = list.getJSONObject(i);
|
||||
final UserList userList = new UserListJSONImpl(userListJson);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<ParcelableCredentials> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package 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<ClusterStatus> 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));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,947 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package 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<ClusterStatus> {
|
||||
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<ClusterStatus> 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<MarkerWithPosition> mMarkers = Collections.newSetFromMap(
|
||||
new ConcurrentHashMap<MarkerWithPosition, Boolean>());
|
||||
|
||||
/**
|
||||
* Icons for each bucket.
|
||||
*/
|
||||
private SparseArray<BitmapDescriptor> mIcons = new SparseArray<>();
|
||||
|
||||
/**
|
||||
* Markers for single ClusterItems.
|
||||
*/
|
||||
private MarkerCache<ClusterStatus> 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<? extends Cluster<ClusterStatus>> mClusters;
|
||||
|
||||
/**
|
||||
* Lookup between markers and the associated cluster.
|
||||
*/
|
||||
private Map<Marker, Cluster<ClusterStatus>> mMarkerToCluster = new HashMap<>();
|
||||
private Map<Cluster<ClusterStatus>, 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<ClusterStatus> mClickListener;
|
||||
private ClusterManager.OnClusterInfoWindowClickListener<ClusterStatus> mInfoWindowClickListener;
|
||||
private ClusterManager.OnClusterItemClickListener<ClusterStatus> mItemClickListener;
|
||||
private ClusterManager.OnClusterItemInfoWindowClickListener<ClusterStatus> mItemInfoWindowClickListener;
|
||||
|
||||
public StatusClusterRenderer(Context context, GoogleMap map, ClusterManager<ClusterStatus> 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<ClusterStatus> 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<? extends Cluster<ClusterStatus>> 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<ClusterStatus> 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.
|
||||
* <p/>
|
||||
* This must be run off the UI thread. Work is coordinated in the RenderTask, then queued up to
|
||||
* be executed by a MarkerModifier.
|
||||
* <p/>
|
||||
* There are three stages for the render:
|
||||
* <p/>
|
||||
* 1. Markers are added to the map
|
||||
* <p/>
|
||||
* 2. Markers are animated to their final position
|
||||
* <p/>
|
||||
* 3. Any old markers are removed from the map
|
||||
* <p/>
|
||||
* 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<? extends Cluster<ClusterStatus>> clusters;
|
||||
private Runnable mCallback;
|
||||
private Projection mProjection;
|
||||
private SphericalMercatorProjection mSphericalMercatorProjection;
|
||||
private float mMapZoom;
|
||||
|
||||
private RenderTask(Set<? extends Cluster<ClusterStatus>> 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<MarkerWithPosition> 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<Point> existingClustersOnScreen = null;
|
||||
if (StatusClusterRenderer.this.mClusters != null && SHOULD_ANIMATE) {
|
||||
existingClustersOnScreen = new ArrayList<>();
|
||||
for (Cluster<ClusterStatus> 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<MarkerWithPosition> newMarkers = Collections.newSetFromMap(
|
||||
new ConcurrentHashMap<MarkerWithPosition, Boolean>());
|
||||
for (Cluster<ClusterStatus> 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<Point> newClustersOnScreen = null;
|
||||
if (SHOULD_ANIMATE) {
|
||||
newClustersOnScreen = new ArrayList<>();
|
||||
for (Cluster<ClusterStatus> 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<? extends Cluster<ClusterStatus>> clusters) {
|
||||
mViewModifier.queue(clusters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClusterClickListener(ClusterManager.OnClusterClickListener<ClusterStatus> listener) {
|
||||
mClickListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClusterInfoWindowClickListener(ClusterManager.OnClusterInfoWindowClickListener<ClusterStatus> listener) {
|
||||
mInfoWindowClickListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClusterItemClickListener(ClusterManager.OnClusterItemClickListener<ClusterStatus> listener) {
|
||||
mItemClickListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClusterItemInfoWindowClickListener(ClusterManager.OnClusterItemInfoWindowClickListener<ClusterStatus> 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<Point> 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<CreateMarkerTask> mCreateMarkerTasks = new LinkedList<>();
|
||||
private Queue<CreateMarkerTask> mOnScreenCreateMarkerTasks = new LinkedList<>();
|
||||
private Queue<Marker> mRemoveMarkerTasks = new LinkedList<>();
|
||||
private Queue<Marker> mOnScreenRemoveMarkerTasks = new LinkedList<>();
|
||||
private Queue<AnimationTask> 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<ClusterStatus> 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<T> {
|
||||
private Map<T, Marker> mCache = new HashMap<>();
|
||||
private Map<Marker, T> 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<ClusterStatus> 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<ClusterStatus> 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<ClusterStatus> 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<ClusterStatus> getCluster(Marker marker) {
|
||||
return mMarkerToCluster.get(marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates markerWithPosition(s) for a particular cluster, animating it if necessary.
|
||||
*/
|
||||
private class CreateMarkerTask {
|
||||
private final Cluster<ClusterStatus> cluster;
|
||||
private final Set<MarkerWithPosition> 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<ClusterStatus> c, Set<MarkerWithPosition> 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<ClusterStatus> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,568 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package 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<TileImageLoader.Result>, 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<TileImageLoader.Result> 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<TileImageLoader.Result> loader) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<TileImageLoader.Result> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<BasicTexture, Object> sAllTextures = new WeakHashMap<BasicTexture, Object>();
|
||||
private static ThreadLocal<Class<BasicTexture>> sInFinalizer = new ThreadLocal<Class<BasicTexture>>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* When drawing, widgets should draw their main content and child views first,
|
||||
* usually by invoking <code>super.draw(canvas)</code> from an overridden
|
||||
* <code>draw</code> 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.
|
||||
* </p>
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* When using a {@link android.widget.Scroller} or
|
||||
* {@link android.widget.OverScroller}, the method
|
||||
* <code>getCurrVelocity</code> will provide a reasonable approximation to
|
||||
* use here.
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<ConfigState> mRestoreStack = new ArrayList<ConfigState>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 <code>GLView</code>s. 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
|
||||
// <code>GLRootView</code> 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<CanvasAnimation> mAnimations = new ArrayList<CanvasAnimation>();
|
||||
|
||||
private final ArrayDeque<OnGLIdleListener> mIdleListeners = new ArrayDeque<OnGLIdleListener>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<GLView> 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<GLView>();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
* <code>android.opengl.GLSurfaceView.BaseConfigChooser</code>. 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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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<Tile> mActiveTiles = new LongSparseArray<Tile>();
|
||||
|
||||
// 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<Void> 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<Void> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<BorderKey, Bitmap> sBorderLines = new HashMap<BorderKey, Bitmap>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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<Bitmap> 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<Bitmap>(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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<T> {
|
||||
public void cancel();
|
||||
|
||||
public T get();
|
||||
|
||||
public boolean isCancelled();
|
||||
|
||||
public boolean isDone();
|
||||
|
||||
}
|
|
@ -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<T> {
|
||||
public void onFutureDone(Future<T> future);
|
||||
|
||||
public void onFutureStart(Future<T> future);
|
||||
}
|
|
@ -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> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Runnable>(), new PriorityThreadFactory("thread-pool",
|
||||
android.os.Process.THREAD_PRIORITY_BACKGROUND));
|
||||
}
|
||||
|
||||
public <T> Future<T> submit(final Job<T> 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 <T> Future<T> submit(final Job<T> job, final FutureListener<T> listener) {
|
||||
final Worker<T> w = new Worker<T>(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<T> {
|
||||
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<T> implements Runnable, Future<T>, JobContext {
|
||||
private static final String TAG = "Worker";
|
||||
private final Job<T> mJob;
|
||||
private final FutureListener<T> mListener;
|
||||
private CancelListener mCancelListener;
|
||||
private ResourceCounter mWaitOnResource;
|
||||
private volatile boolean mIsCancelled;
|
||||
private boolean mIsDone;
|
||||
private T mResult;
|
||||
private int mMode;
|
||||
|
||||
public Worker(final Job<T> job, final FutureListener<T> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<Void, Void, ?> 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<Parcelable>(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<Parcelable>(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<ParcelableMediaUpdate> 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<ParcelableMediaUpdate> 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<ParcelableMediaUpdate> 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<AccountIconViewHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private final ImageLoaderWrapper mImageLoader;
|
||||
private final LongSparseArray<Boolean> 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<ParcelableAccount> {
|
||||
|
||||
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<ParcelableMediaUpdate> 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<ParcelableMediaUpdate> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<SuggestionItem> loadInBackground() {
|
||||
final boolean emptyQuery = TextUtils.isEmpty(mQuery);
|
||||
final Context context = getContext();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final List<SuggestionItem> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -172,7 +172,7 @@ public abstract class AbsActivitiesAdapter<Data> extends Adapter<ViewHolder> 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<Data> extends Adapter<ViewHolder> 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<Data> extends Adapter<ViewHolder> 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: {
|
||||
|
|
|
@ -41,11 +41,11 @@ public abstract class AbsStatusesAdapter<D> extends Adapter<ViewHolder> 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<D> extends Adapter<ViewHolder> 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<D> extends Adapter<ViewHolder> 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<D> extends Adapter<ViewHolder> 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<D> extends Adapter<ViewHolder> implemen
|
|||
|
||||
void onStatusClick(StatusViewHolder holder, int position);
|
||||
|
||||
void onStatusMenuClick(StatusViewHolder holder, int position);
|
||||
void onStatusMenuClick(StatusViewHolder holder, View menuView, int position);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public class CursorStatusesAdapter extends AbsStatusesAdapter<Cursor> {
|
|||
@Override
|
||||
protected void bindStatus(StatusViewHolder holder, int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
holder.displayStatus(mCursor, mIndices);
|
||||
holder.displayStatus(mCursor, mIndices, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<List<Parce
|
|||
private List<ParcelableActivity> mData;
|
||||
|
||||
public ParcelableActivitiesAdapter(Context context, boolean compact) {
|
||||
super(context,compact);
|
||||
super(context, compact);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,7 +74,7 @@ public class ParcelableActivitiesAdapter extends AbsActivitiesAdapter<List<Parce
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onItemMenuClick(ViewHolder holder, int position) {
|
||||
public void onItemMenuClick(ViewHolder holder, View menuView, int position) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ public class ParcelableStatusesAdapter extends AbsStatusesAdapter<List<Parcelabl
|
|||
|
||||
@Override
|
||||
protected void bindStatus(StatusViewHolder holder, int position) {
|
||||
holder.displayStatus(getStatus(position));
|
||||
holder.displayStatus(getStatus(position), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.mariotaku.twidere.adapter.iface;
|
||||
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 14/12/3.
|
||||
|
@ -27,5 +28,5 @@ import android.support.v7.widget.RecyclerView.ViewHolder;
|
|||
public interface ContentCardClickListener {
|
||||
void onItemActionClick(ViewHolder holder, int id, int position);
|
||||
|
||||
void onItemMenuClick(ViewHolder holder, int position);
|
||||
void onItemMenuClick(ViewHolder holder, View menuView, int position);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.content.Context;
|
|||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.ImageLoaderWrapper;
|
||||
import org.mariotaku.twidere.util.ImageLoadingHandler;
|
||||
import org.mariotaku.twidere.view.ShapedImageView.ShapeStyle;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/1/3.
|
||||
|
@ -35,6 +36,7 @@ public interface IContentCardAdapter extends IGapSupportedAdapter, ContentCardCl
|
|||
|
||||
ImageLoadingHandler getImageLoadingHandler();
|
||||
|
||||
@ShapeStyle
|
||||
int getProfileImageStyle();
|
||||
|
||||
int getMediaPreviewStyle();
|
||||
|
|
|
@ -40,7 +40,6 @@ import com.nostra13.universalimageloader.core.download.ImageDownloader;
|
|||
import com.nostra13.universalimageloader.utils.L;
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import org.mariotaku.gallery3d.util.GalleryUtils;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.activity.MainActivity;
|
||||
import org.mariotaku.twidere.activity.MainHondaJOJOActivity;
|
||||
|
@ -181,7 +180,6 @@ public class TwidereApplication extends Application implements Constants, OnShar
|
|||
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
|
||||
mPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
initializeAsyncTask();
|
||||
GalleryUtils.initialize(this);
|
||||
initAccountColor(this);
|
||||
initUserColor(this);
|
||||
|
||||
|
@ -251,7 +249,7 @@ public class TwidereApplication extends Application implements Constants, OnShar
|
|||
// So we load it here to comply the rule.
|
||||
try {
|
||||
Class.forName(AsyncTask.class.getName());
|
||||
} catch (final ClassNotFoundException e) {
|
||||
} catch (final ClassNotFoundException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,35 +26,29 @@ import android.content.pm.PackageManager;
|
|||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MenuItem.OnMenuItemClickListener;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.mariotaku.menucomponent.widget.PopupMenu;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.adapter.ExtensionsAdapter;
|
||||
import org.mariotaku.twidere.loader.ExtensionsListLoader;
|
||||
import org.mariotaku.twidere.loader.ExtensionsListLoader.ExtensionInfo;
|
||||
import org.mariotaku.twidere.util.PermissionsManager;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ExtensionsListFragment extends BaseListFragment implements Constants,
|
||||
LoaderCallbacks<List<ExtensionInfo>>, OnItemClickListener, OnItemLongClickListener,
|
||||
OnMenuItemClickListener {
|
||||
public class ExtensionsListFragment extends BaseListFragment implements Constants, LoaderCallbacks<List<ExtensionInfo>> {
|
||||
|
||||
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<List<ExtensionInfo>> 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<List<ExtensionInfo>> loader) {
|
||||
mAdapter.setData(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<List<ExtensionInfo>> loader, final List<ExtensionInfo> 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<List<ExtensionInfo>> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Data> extends BaseSupportFragment impl
|
|||
OnRefreshListener, DrawerCallback, RefreshScrollTopInterface, StatusAdapterListener {
|
||||
|
||||
|
||||
private final Object mStatusesBusCallback;
|
||||
private AbsStatusesAdapter<Data> 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<Data> extends BaseSupportFragment impl
|
|||
}
|
||||
}
|
||||
};
|
||||
private PopupMenu mPopupMenu;
|
||||
|
||||
protected AbsStatusesFragment() {
|
||||
mStatusesBusCallback = createMessageBusCallback();
|
||||
|
@ -230,6 +234,14 @@ public abstract class AbsStatusesFragment<Data> 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<Data> 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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<List<Parcelabl
|
|||
}
|
||||
if (list_loaded != null) {
|
||||
final int list_size = list_loaded.size();
|
||||
if (list_loaded instanceof PagableResponseList) {
|
||||
if (list_loaded instanceof PageableResponseList) {
|
||||
mNextCursor = ((CursorSupport) list_loaded).getNextCursor();
|
||||
mPrevCursor = ((CursorSupport) list_loaded).getPreviousCursor();
|
||||
for (int i = 0; i < list_size; i++) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -38,7 +38,7 @@ public abstract class CursorSupportUsersLoader extends BaseCursorSupportUsersLoa
|
|||
super(context, account_id, cursor, data);
|
||||
}
|
||||
|
||||
protected abstract PagableResponseList<User> getCursoredUsers(Twitter twitter, CursorPaging paging)
|
||||
protected abstract PageableResponseList<User> 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<User> users = getCursoredUsers(twitter, paging);
|
||||
final PageableResponseList<User> users = getCursoredUsers(twitter, paging);
|
||||
if (users == null) return null;
|
||||
setCursorIds(users);
|
||||
return users;
|
||||
|
|
|
@ -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<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
protected final PageableResponseList<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
throws TwitterException {
|
||||
if (twitter == null) return null;
|
||||
return twitter.getMutesUsersList(paging);
|
||||
|
|
|
@ -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<SingleResponse<P
|
|||
final int count = cur.getCount();
|
||||
try {
|
||||
if (count > 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();
|
||||
|
|
|
@ -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<TileImageLoader.Result> {
|
|||
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<TileImageLoader.Result> {
|
|||
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<TileImageLoader.Result> {
|
|||
} 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<TileImageLoader.Result> {
|
|||
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<TileImageLoader.Result> {
|
|||
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<TileImageLoader.Result> {
|
|||
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<TileImageLoader.Result> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
protected final PageableResponseList<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
throws TwitterException {
|
||||
if (twitter == null) return null;
|
||||
return twitter.getBlocksList(paging);
|
||||
|
|
|
@ -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<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
protected PageableResponseList<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
throws TwitterException {
|
||||
if (twitter == null) return null;
|
||||
if (mUserId > 0)
|
||||
|
|
|
@ -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<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
protected PageableResponseList<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
throws TwitterException {
|
||||
if (twitter == null) return null;
|
||||
if (mUserId > 0)
|
||||
|
|
|
@ -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<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
public PageableResponseList<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
throws TwitterException {
|
||||
if (twitter == null) return null;
|
||||
if (mListId > 0)
|
||||
|
|
|
@ -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<UserList> getUserLists(final Twitter twitter) throws TwitterException {
|
||||
public PageableResponseList<UserList> getUserLists(final Twitter twitter) throws TwitterException {
|
||||
if (twitter == null) return null;
|
||||
if (mUserId > 0)
|
||||
return twitter.getUserListMemberships(mUserId, getCursor());
|
||||
|
|
|
@ -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<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
public PageableResponseList<User> getCursoredUsers(final Twitter twitter, final CursorPaging paging)
|
||||
throws TwitterException {
|
||||
if (twitter == null) return null;
|
||||
if (mListId > 0)
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package 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<ParcelableAccount> accounts) {
|
||||
mAdapter.clear();
|
||||
if (accounts != null) {
|
||||
mAdapter.addAll(accounts);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AccountsGridAdapter extends ArrayAdapter<ParcelableAccount> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Void, Void, Void> implements Constants {
|
||||
|
||||
private final TwitterListResponse<twitter4j.Status>[] all_statuses;
|
||||
private final ContentResolver resolver;
|
||||
private final TwitterListResponse<twitter4j.Status>[] responses;
|
||||
private final Context context;
|
||||
|
||||
public CacheUsersStatusesTask(final Context context, final TwitterListResponse<twitter4j.Status>... all_statuses) {
|
||||
resolver = context.getContentResolver();
|
||||
this.all_statuses = all_statuses;
|
||||
public CacheUsersStatusesTask(final Context context, final TwitterListResponse<twitter4j.Status>... 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<ContentValues> cachedUsersValues = new HashSet<>();
|
||||
final Set<ContentValues> cached_statuses_values = new HashSet<>();
|
||||
final Set<ContentValues> hashtag_values = new HashSet<>();
|
||||
final Set<Long> userIds = new HashSet<>();
|
||||
final Set<Long> status_ids = new HashSet<>();
|
||||
final Set<String> hashtags = new HashSet<>();
|
||||
final Set<ContentValues> usersValues = new HashSet<>();
|
||||
final Set<ContentValues> statusesValues = new HashSet<>();
|
||||
final Set<ContentValues> hashTagValues = new HashSet<>();
|
||||
final Set<Long> allStatusIds = new HashSet<>();
|
||||
final Set<String> allHashTags = new HashSet<>();
|
||||
final Set<User> users = new HashSet<>();
|
||||
|
||||
for (final TwitterListResponse<twitter4j.Status> values : all_statuses) {
|
||||
if (values == null || values.list == null) {
|
||||
for (final TwitterListResponse<twitter4j.Status> response : responses) {
|
||||
if (response == null || response.list == null) {
|
||||
continue;
|
||||
}
|
||||
final List<twitter4j.Status> list = values.list;
|
||||
final List<twitter4j.Status> list = response.list;
|
||||
final Set<Long> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package 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();
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue