redesigned compose dialog

This commit is contained in:
Mariotaku Lee 2015-01-14 16:47:51 +08:00
parent ff13288b65
commit 79e80bfaad
136 changed files with 2803 additions and 8610 deletions

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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 {

View File

@ -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};
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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",

View File

@ -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

View File

@ -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;
/**

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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 {

View File

@ -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;
}

View File

@ -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')

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}
}

View File

@ -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++;
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
};
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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: {

View File

@ -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);
}
}

View File

@ -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

View File

@ -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) {
}

View File

@ -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

View File

@ -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);
}

View File

@ -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();

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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++) {

View File

@ -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;

View File

@ -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);

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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());

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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