added nfc share (user/tweet), and twidere is not required, if you don't have twidere installed, a browser(or other client can handle twitter link) will be opened

This commit is contained in:
Mariotaku Lee 2015-03-14 23:11:20 +08:00
parent b88ffa055a
commit 6ccb61e183
51 changed files with 1285 additions and 611 deletions

View File

@ -47,19 +47,27 @@ public class Expression implements SQLLang {
}
public static Expression equals(final Column l, final Column r) {
return new Expression(String.format(Locale.US, "%s = %s", l.getSQL(), r.getSQL()));
return new Expression(String.format(Locale.ROOT, "%s = %s", l.getSQL(), r.getSQL()));
}
public static Expression equals(final Column l, final Selectable r) {
return new Expression(String.format(Locale.ROOT, "%s = (%s)", l.getSQL(), r.getSQL()));
}
public static Expression equals(final String l, final Selectable r) {
return new Expression(String.format(Locale.ROOT, "%s = (%s)", l, r.getSQL()));
}
public static Expression equals(final Column l, final long r) {
return new Expression(String.format(Locale.US, "%s = %d", l.getSQL(), r));
return new Expression(String.format(Locale.ROOT, "%s = %d", l.getSQL(), r));
}
public static Expression equals(final Column l, final String r) {
return new Expression(String.format(Locale.US, "%s = '%s'", l.getSQL(), r));
return new Expression(String.format(Locale.ROOT, "%s = '%s'", l.getSQL(), r));
}
public static Expression equals(final String l, final long r) {
return new Expression(String.format(Locale.US, "%s = %d", l, r));
return new Expression(String.format(Locale.ROOT, "%s = %d", l, r));
}
public static Expression in(final Column column, final Selectable in) {
@ -67,7 +75,7 @@ public class Expression implements SQLLang {
}
public static Expression notEquals(final String l, final long r) {
return new Expression(String.format(Locale.US, "%s != %d", l, r));
return new Expression(String.format(Locale.ROOT, "%s != %d", l, r));
}
public static Expression notEquals(final String l, final String r) {
@ -101,7 +109,7 @@ public class Expression implements SQLLang {
}
public static Expression equalsArgs(String l) {
return new Expression(String.format(Locale.US, "%s = ?", l));
return new Expression(String.format(Locale.ROOT, "%s = ?", l));
}
public static Expression isNull(Column column) {
@ -113,12 +121,12 @@ public class Expression implements SQLLang {
}
public static Expression likeRaw(final Column column, final String pattern, final String escape) {
return new Expression(String.format(Locale.US, "%s LIKE %s ESCAPE '%s'", column.getSQL(), pattern, escape));
return new Expression(String.format(Locale.ROOT, "%s LIKE %s ESCAPE '%s'", column.getSQL(), pattern, escape));
}
public static Expression likeRaw(final Column column, final String pattern) {
return new Expression(String.format(Locale.US, "%s LIKE %s", column.getSQL(), pattern));
return new Expression(String.format(Locale.ROOT, "%s LIKE %s", column.getSQL(), pattern));
}

View File

@ -31,14 +31,35 @@ package org.mariotaku.querybuilder;
public class OrderBy implements SQLLang {
private final String[] orderBy;
private final boolean[] ascending;
public OrderBy(final String[] orderBy, final boolean[] ascending) {
this.orderBy = orderBy;
this.ascending = ascending;
}
public OrderBy(final String... orderBy) {
this.orderBy = orderBy;
this(orderBy, null);
}
public OrderBy(final String orderBy, final boolean ascending) {
this.orderBy = new String[]{orderBy};
this.ascending = new boolean[]{ascending};
}
@Override
public String getSQL() {
return Utils.toString(orderBy, ',', false);
final StringBuilder sb = new StringBuilder();
for (int i = 0, j = orderBy.length; i < j; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(orderBy[i]);
if (ascending != null) {
sb.append(ascending[i] ? " ASC" : " DESC");
}
}
return sb.toString();
}
}

View File

@ -28,6 +28,7 @@
package org.mariotaku.querybuilder;
import org.mariotaku.querybuilder.query.SQLAlterTableQuery;
import org.mariotaku.querybuilder.query.SQLCreateIndexQuery;
import org.mariotaku.querybuilder.query.SQLCreateTableQuery;
import org.mariotaku.querybuilder.query.SQLCreateTriggerQuery;
import org.mariotaku.querybuilder.query.SQLCreateViewQuery;
@ -65,6 +66,10 @@ public class SQLQueryBuilder {
return new SQLCreateViewQuery.Builder().createView(temporary, createIfNotExists, name);
}
public static SQLCreateIndexQuery.Builder createIndex(final boolean unique, final boolean createIfNotExists) {
return new SQLCreateIndexQuery.Builder().createIndex(unique, createIfNotExists);
}
public static SQLCreateTriggerQuery.Builder createTrigger(final boolean temporary, final boolean createIfNotExists,
final String name) {

View File

@ -0,0 +1,135 @@
/*
* 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.query;
import org.mariotaku.querybuilder.Columns;
import org.mariotaku.querybuilder.Expression;
import org.mariotaku.querybuilder.SQLQuery;
import org.mariotaku.querybuilder.Table;
public class SQLCreateIndexQuery implements SQLQuery {
private boolean unique;
private boolean createIfNotExists;
private Table table;
private String indexName;
private Columns indexedColumns;
private Expression where;
SQLCreateIndexQuery() {
}
@Override
public String getSQL() {
if (table == null) throw new NullPointerException("Table must not be null!");
if (indexName == null)
throw new NullPointerException("SELECT statement must not be null!");
final StringBuilder sb = new StringBuilder("CREATE");
if (unique) {
sb.append(" UNIQUE");
}
sb.append(" INDEX");
if (createIfNotExists) {
sb.append(" IF NOT EXISTS");
}
if (indexedColumns == null)
throw new NullPointerException("Indexed columns must not be null !");
sb.append(String.format(" %s ON %s (%s)", indexName, table.getSQL(), indexedColumns.getSQL()));
if (where != null) {
sb.append(" WHERE");
sb.append(where.getSQL());
}
return sb.toString();
}
public void setIndexedColumns(Columns indexedColumns) {
this.indexedColumns = indexedColumns;
}
public void setWhere(Expression where) {
this.where = where;
}
void setIndexName(final String indexName) {
this.indexName = indexName;
}
void setCreateIfNotExists(final boolean createIfNotExists) {
this.createIfNotExists = createIfNotExists;
}
void setTable(final Table table) {
this.table = table;
}
void setUnique(final boolean unique) {
this.unique = unique;
}
public static final class Builder implements IBuilder<SQLCreateIndexQuery> {
private final SQLCreateIndexQuery query = new SQLCreateIndexQuery();
private boolean buildCalled;
public Builder on(final Table table, Columns indexedColumns) {
checkNotBuilt();
query.setTable(table);
query.setIndexedColumns(indexedColumns);
return this;
}
public Builder name(final String name) {
checkNotBuilt();
query.setIndexName(name);
return this;
}
public Builder where(final Expression expression) {
checkNotBuilt();
query.setWhere(expression);
return this;
}
@Override
public SQLCreateIndexQuery build() {
buildCalled = true;
return query;
}
@Override
public String buildSQL() {
return build().getSQL();
}
public Builder createIndex(final boolean unique, final boolean createIfNotExists) {
checkNotBuilt();
query.setUnique(unique);
query.setCreateIfNotExists(createIfNotExists);
return this;
}
private void checkNotBuilt() {
if (buildCalled) throw new IllegalStateException();
}
}
}

View File

@ -178,6 +178,7 @@ public class SQLCreateTriggerQuery implements SQLQuery {
@Override
public SQLCreateTriggerQuery build() {
buildCalled = true;
return query;
}

View File

@ -59,6 +59,7 @@ public class SQLCreateViewQuery implements SQLQuery {
@Override
public SQLCreateViewQuery build() {
buildCalled = true;
return query;
}

View File

@ -1,9 +1,10 @@
package twitter4j.http;
import java.io.IOException;
import java.net.InetAddress;
public interface HostAddressResolver {
public String resolve(String host) throws IOException;
public InetAddress[] resolve(String host) throws IOException;
}

View File

@ -26,6 +26,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
@ -261,12 +262,12 @@ public class HttpClientImpl extends HttpClientBase implements HttpClient, HttpRe
throw new IOException("Invalid URI " + url_string);
}
final String host = url_orig.getHost(), authority = url_orig.getAuthority();
final String resolved_host = resolver != null ? resolver.resolve(host) : null;
con = (HttpURLConnection) new URL(resolved_host != null ? url_string.replace("://" + host, "://"
+ resolved_host) : url_string).openConnection(proxy);
if (resolved_host != null && !host.equals(resolved_host)) {
con.setRequestProperty("Host", authority);
}
final InetAddress[] resolved_host = resolver != null ? resolver.resolve(host) : InetAddress.getAllByName(host);
con = (HttpURLConnection) new URL(resolved_host.length > 0 ? url_string.replace("://" + host, "://"
+ resolved_host[0].getHostAddress()) : url_string).openConnection(proxy);
// if (resolved_host != null && !host.equals(resolved_host)) {
// con.setRequestProperty("Host", authority);
// }
if (CONF.getHttpConnectionTimeout() > 0) {
con.setConnectTimeout(CONF.getHttpConnectionTimeout());
}

View File

@ -19,67 +19,58 @@
package org.mariotaku.twidere.extension.streaming.util;
import static android.text.TextUtils.isEmpty;
import java.util.LinkedHashMap;
import android.content.Context;
import android.util.Log;
import org.mariotaku.twidere.Twidere;
import org.mariotaku.twidere.extension.streaming.BuildConfig;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import twitter4j.http.HostAddressResolver;
import android.content.Context;
import android.util.Log;
public class TwidereHostAddressResolver implements HostAddressResolver {
private static final String RESOLVER_LOGTAG = "Twidere.Streaming.HostAddressResolver";
private static final String RESOLVER_LOGTAG = "Twidere.Streaming.Host";
private final HostCache mHostCache = new HostCache(512);
private final Context mContext;
private final HostCache mHostCache = new HostCache(512);
private final Context mContext;
public TwidereHostAddressResolver(final Context context) {
mContext = context;
}
public TwidereHostAddressResolver(final Context context) {
mContext = context;
}
@Override
public String resolve(final String host) {
if (host == null) return null;
// First, I'll try to load address cached.
if (mHostCache.containsKey(host)) {
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Got cached address " + mHostCache.get(host) + " for host " + host);
}
return mHostCache.get(host);
}
final String address = Twidere.resolveHost(mContext, host);
if (isValidIpAddress(address)) {
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Resolved address " + address + " for host " + host);
}
return address;
}
if (BuildConfig.DEBUG) {
Log.w(RESOLVER_LOGTAG, "Resolve address " + host + " failed, using original host");
}
return host;
}
@Override
public InetAddress[] resolve(final String host) throws UnknownHostException {
if (host == null) return null;
// First, I'll try to load address cached.
final InetAddress[] cached = mHostCache.get(host);
if (cached != null) {
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Got cached " + Arrays.toString(cached));
}
return cached;
}
final InetAddress[] resolved = Twidere.resolveHost(mContext, host);
mHostCache.put(host, resolved);
return resolved;
}
static boolean isValidIpAddress(final String address) {
return !isEmpty(address);
}
private static class HostCache extends LinkedHashMap<String, InetAddress[]> {
private static class HostCache extends LinkedHashMap<String, String> {
private static final long serialVersionUID = -9216545511009449147L;
private static final long serialVersionUID = -9216545511009449147L;
HostCache(final int initialCapacity) {
super(initialCapacity);
}
HostCache(final int initialCapacity) {
super(initialCapacity);
}
@Override
public String put(final String key, final String value) {
if (value == null) return value;
return super.put(key, value);
}
}
@Override
public InetAddress[] put(final String key, final InetAddress[] value) {
if (value == null) return null;
return super.put(key, value);
}
}
}

View File

@ -29,16 +29,25 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import org.mariotaku.twidere.model.ComposingStatus;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.DNS;
import org.mariotaku.twidere.util.TwidereArrayUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import static android.text.TextUtils.isEmpty;
@SuppressWarnings("unused")
public final class Twidere implements TwidereConstants {
public static void appendComposeActivityText(final Activity activity, final String text) {
@ -142,7 +151,8 @@ public final class Twidere implements TwidereConstants {
}
c.moveToNext();
}
} catch (final SecurityException e) {
} catch (final SecurityException ignore) {
} finally {
c.close();
}
@ -162,17 +172,35 @@ public final class Twidere implements TwidereConstants {
activity.finish();
}
public static String resolveHost(final Context context, final String host) {
if (context == null || host == null) return null;
private static InetAddress fromAddressString(String host, String address) throws UnknownHostException {
InetAddress inetAddress = InetAddress.getByName(address);
if (inetAddress instanceof Inet4Address) {
return Inet4Address.getByAddress(host, inetAddress.getAddress());
} else if (inetAddress instanceof Inet6Address) {
return Inet6Address.getByAddress(host, inetAddress.getAddress());
}
throw new UnknownHostException("Bad address " + host + " = " + address);
}
@NonNull
public static InetAddress[] resolveHost(final Context context, final String host) throws UnknownHostException {
if (context == null || host == null) return InetAddress.getAllByName(host);
final ContentResolver resolver = context.getContentResolver();
final Uri uri = Uri.withAppendedPath(TwidereDataStore.DNS.CONTENT_URI, host);
final Cursor cur = resolver.query(uri, TwidereDataStore.DNS.MATRIX_COLUMNS, null, null, null);
if (cur == null) return null;
final Uri uri = Uri.withAppendedPath(DNS.CONTENT_URI, host);
final Cursor cur = resolver.query(uri, DNS.MATRIX_COLUMNS, null, null, null);
if (cur == null) return InetAddress.getAllByName(host);
try {
if (cur.getCount() == 0) return null;
final int addr_idx = cur.getColumnIndex(TwidereDataStore.DNS.ADDRESS);
cur.moveToFirst();
return cur.getString(addr_idx);
final ArrayList<InetAddress> addresses = new ArrayList<>();
final int idxHost = cur.getColumnIndex(DNS.HOST), idxAddr = cur.getColumnIndex(DNS.ADDRESS);
while (!cur.isAfterLast()) {
addresses.add(fromAddressString(cur.getString(idxHost), cur.getString(idxAddr)));
cur.moveToNext();
}
if (addresses.isEmpty()) {
throw new UnknownHostException("Unknown host " + host);
}
return addresses.toArray(new InetAddress[addresses.size()]);
} finally {
cur.close();
}

View File

@ -21,6 +21,9 @@
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
<uses-feature
android:name="android.hardware.nfc"
android:required="false"/>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
@ -32,6 +35,7 @@
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS"/>
<uses-permission android:name="org.mariotaku.twidere.permission.UPLOAD_MEDIA"/>
<uses-permission android:name="org.mariotaku.twidere.permission.SYNC_TIMELINE"/>
@ -548,6 +552,7 @@
android:scheme="https"/>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>

View File

@ -0,0 +1,170 @@
/*
* 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 com.squareup.okhttp.internal;
import android.util.Log;
import com.squareup.okhttp.Protocol;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import javax.net.ssl.SSLSocket;
/**
* Created by mariotaku on 15/3/13.
*/
public class TwidereOkHttpPlatform extends Platform implements Constants {
// setUseSessionTickets(boolean)
private static final OptionalMethod<Socket> SET_USE_SESSION_TICKETS =
new OptionalMethod<>(null, "setUseSessionTickets", Boolean.TYPE);
// setHostname(String)
private static final OptionalMethod<Socket> SET_HOSTNAME =
new OptionalMethod<>(null, "setHostname", String.class);
// byte[] getAlpnSelectedProtocol()
private static final OptionalMethod<Socket> GET_ALPN_SELECTED_PROTOCOL =
new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
// setAlpnSelectedProtocol(byte[])
private static final OptionalMethod<Socket> SET_ALPN_PROTOCOLS =
new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
private final TwidereApplication application;
// Non-null on Android 4.0+.
private final Method trafficStatsTagSocket;
private final Method trafficStatsUntagSocket;
private TwidereOkHttpPlatform(TwidereApplication application, Method trafficStatsTagSocket, Method trafficStatsUntagSocket) {
this.application = application;
this.trafficStatsTagSocket = trafficStatsTagSocket;
this.trafficStatsUntagSocket = trafficStatsUntagSocket;
}
public static void applyHack(TwidereApplication application) {
final TwidereOkHttpPlatform platform = get(application);
try {
final Field field = Platform.class.getDeclaredField("PLATFORM");
field.setAccessible(true);
field.set(null, platform);
} catch (IllegalAccessException | NoSuchFieldException e) {
//Unable to change default platform
Log.w(LOGTAG, e);
}
}
public static TwidereOkHttpPlatform get(TwidereApplication application) {
// Attempt to find Android 4.0+ APIs.
Method trafficStatsTagSocket = null;
Method trafficStatsUntagSocket = null;
try {
Class<?> trafficStats = Class.forName("android.net.TrafficStats");
trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class);
trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class);
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
}
return new TwidereOkHttpPlatform(application, trafficStatsTagSocket, trafficStatsUntagSocket);
}
@Override
public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
try {
// final HostAddressResolver resolver = application.getHostAddressResolver();
socket.connect(address, connectTimeout);
} catch (SecurityException se) {
// Before android 4.3, socket.connect could throw a SecurityException
// if opening a socket resulted in an EACCES error.
IOException ioException = new IOException("Exception in connect");
ioException.initCause(se);
throw ioException;
}
}
@Override
public void configureTlsExtensions(
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
// Enable SNI and session tickets.
if (hostname != null) {
SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true);
SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname);
}
// Enable ALPN.
boolean alpnSupported = SET_ALPN_PROTOCOLS.isSupported(sslSocket);
if (!alpnSupported) {
return;
}
Object[] parameters = {concatLengthPrefixed(protocols)};
SET_ALPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
}
@Override
public String getSelectedProtocol(SSLSocket socket) {
boolean alpnSupported = GET_ALPN_SELECTED_PROTOCOL.isSupported(socket);
if (!alpnSupported) {
return null;
}
byte[] alpnResult =
(byte[]) GET_ALPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket);
if (alpnResult != null) {
return new String(alpnResult, Util.UTF_8);
}
return null;
}
@Override
public void tagSocket(Socket socket) throws SocketException {
if (trafficStatsTagSocket == null) return;
try {
trafficStatsTagSocket.invoke(null, socket);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
@Override
public void untagSocket(Socket socket) throws SocketException {
if (trafficStatsUntagSocket == null) return;
try {
trafficStatsUntagSocket.invoke(null, socket);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
}

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 = 85;
public static final int DATABASES_VERSION = 86;
public static final int MENU_GROUP_STATUS_EXTENSION = 10;
public static final int MENU_GROUP_COMPOSE_EXTENSION = 11;

View File

@ -573,8 +573,9 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
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", "score DESC",
CachedUsers.SCREEN_NAME, CachedUsers.NAME);
final String[] order = {CachedUsers.LAST_SEEN, "score", CachedUsers.SCREEN_NAME, CachedUsers.NAME};
final boolean[] ascending = {false, false, true, true};
final OrderBy orderBy = new OrderBy(order, ascending);
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,

View File

@ -38,6 +38,7 @@ import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
import com.nostra13.universalimageloader.utils.L;
import com.squareup.okhttp.internal.TwidereOkHttpPlatform;
import com.squareup.otto.Bus;
import org.mariotaku.twidere.Constants;
@ -186,6 +187,7 @@ public class TwidereApplication extends MultiDexApplication implements Constants
}
setTheme(ThemeUtils.getThemeResource(this));
super.onCreate();
// TwidereOkHttpPlatform.applyHack(this);
mHandler = new Handler();
mMessageBus = new Bus();
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);

View File

@ -156,6 +156,7 @@ public abstract class AbsStatusesFragment<Data> extends BaseSupportFragment impl
public void setRefreshing(boolean refreshing) {
if (refreshing == mSwipeRefreshLayout.isRefreshing()) return;
if (!refreshing)
updateRefreshProgressOffset();
mSwipeRefreshLayout.setRefreshing(refreshing);
}
@ -249,7 +250,6 @@ public abstract class AbsStatusesFragment<Data> extends BaseSupportFragment impl
super.fitSystemWindows(insets);
mRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
mSystemWindowsInsets.set(insets);
updateRefreshProgressOffset();
}

View File

@ -37,12 +37,16 @@ import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.util.Pair;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.internal.view.SupportMenuInflater;
import android.support.v7.widget.ActionMenuView;
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.ViewHolder;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@ -70,6 +74,7 @@ import org.mariotaku.twidere.activity.support.QuickSearchBarActivity;
import org.mariotaku.twidere.activity.support.UserProfileEditorActivity;
import org.mariotaku.twidere.adapter.ArrayAdapter;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.menu.SupportAccountActionProvider;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableAccount.Indices;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
@ -89,6 +94,7 @@ import static org.mariotaku.twidere.util.Utils.openUserProfile;
public class AccountsDashboardFragment extends BaseSupportListFragment implements LoaderCallbacks<Cursor>,
OnSharedPreferenceChangeListener, OnCheckedChangeListener, ImageLoadingListener, OnClickListener {
private static final int MENU_GROUP_ACCOUNT_TOGGLE = 101;
private final SupportFragmentReloadCursorObserver mReloadContentObserver = new SupportFragmentReloadCursorObserver(
this, 0, this);
@ -106,7 +112,7 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement
private ImageView mAccountProfileBannerView;
private ShapedImageView mAccountProfileImageView;
private TextView mAccountProfileNameView, mAccountProfileScreenNameView;
private Switch mAccountsToggle;
private ActionMenuView mAccountsToggleMenu;
private View mAccountProfileContainer;
private Context mThemedContext;
@ -166,10 +172,26 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
if (data != null && data.getCount() > 0 && mAccountsAdapter.getSelectedAccountId() <= 0) {
final Menu menu = mAccountsToggleMenu.getMenu();
final SupportAccountActionProvider provider = (SupportAccountActionProvider) MenuItemCompat.getActionProvider(menu.findItem(MENU_SELECT_ACCOUNT));
final ArrayList<ParcelableAccount> accounts = new ArrayList<>();
if (data != null) {
data.moveToFirst();
mAccountsAdapter.setSelectedAccountId(mPreferences.getLong(KEY_DEFAULT_ACCOUNT_ID, -1));
final Indices indices = new Indices(data);
long defaultId = -1;
while (!data.isAfterLast()) {
final ParcelableAccount account = new ParcelableAccount(data, indices);
accounts.add(account);
if (defaultId < 0 && account.is_activated) {
defaultId = account.account_id;
}
data.moveToNext();
}
if (mAccountsAdapter.getSelectedAccountId() <= 0) {
mAccountsAdapter.setSelectedAccountId(mPreferences.getLong(KEY_DEFAULT_ACCOUNT_ID, defaultId));
}
}
provider.setAccounts(accounts.toArray(new ParcelableAccount[accounts.size()]));
mAccountsAdapter.changeCursor(data);
updateAccountOptionsSeparatorLabel();
updateDefaultAccountState();
@ -310,7 +332,7 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement
mAccountsAdapter = new AccountSelectorAdapter(context, this);
mAccountOptionsAdapter = new AccountOptionsAdapter(context);
mAppMenuAdapter = new AppMenuAdapter(context);
mAppMenuSectionView = newSectionView(context, R.string.more);
mAppMenuSectionView = Utils.newSectionView(context, R.string.more);
mAccountSelectorView = inflater.inflate(R.layout.header_drawer_account_selector, listView, false);
mAccountsSelector = (RecyclerView) mAccountSelectorView.findViewById(R.id.other_accounts_list);
final LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
@ -322,11 +344,12 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement
mAccountProfileBannerView = (ImageView) mAccountSelectorView.findViewById(R.id.account_profile_banner);
mAccountProfileNameView = (TextView) mAccountSelectorView.findViewById(R.id.name);
mAccountProfileScreenNameView = (TextView) mAccountSelectorView.findViewById(R.id.screen_name);
mAccountsToggle = (Switch) mAccountSelectorView.findViewById(R.id.toggle);
mAccountsToggleMenu = (ActionMenuView) mAccountSelectorView.findViewById(R.id.toggle_menu);
final SupportMenuInflater menuInflater = new SupportMenuInflater(context);
menuInflater.inflate(R.menu.action_dashboard_timeline_toggle, mAccountsToggleMenu.getMenu());
mAccountProfileContainer.setOnClickListener(this);
mAccountsToggle.setOnCheckedChangeListener(this);
mAdapter.addView(mAccountSelectorView, false);
mAdapter.addAdapter(mAccountOptionsAdapter);
mAdapter.addView(mAppMenuSectionView, false);
@ -370,14 +393,6 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement
return mThemedContext = new ContextThemeWrapper(context, themeResource);
}
private static TextView newSectionView(final Context context, final int titleRes) {
final TextView textView = new TextView(context, null, android.R.attr.listSeparatorTextViewStyle);
if (titleRes != 0) {
textView.setText(titleRes);
}
return textView;
}
private void onAccountSelected(ParcelableAccount account) {
mAccountsAdapter.setSelectedAccountId(account.account_id);
updateAccountOptionsSeparatorLabel();
@ -390,7 +405,6 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement
}
mAccountProfileNameView.setText(account.name);
mAccountProfileScreenNameView.setText("@" + account.screen_name);
mAccountsToggle.setChecked(account.is_activated);
mImageLoader.displayProfileImage(mAccountProfileImageView, account.profile_image_url);
mAccountProfileImageView.setBorderColors(account.color);
final int bannerWidth = mAccountProfileBannerView.getWidth();

View File

@ -746,8 +746,9 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl
selection = null;
selectionArgs = null;
}
final OrderBy orderBy = new OrderBy(CachedUsers.LAST_SEEN + " DESC",
CachedUsers.SCREEN_NAME, CachedUsers.NAME);
final String[] order = {CachedUsers.LAST_SEEN, CachedUsers.SCREEN_NAME, CachedUsers.NAME};
final boolean[] ascending = {false, true, true};
final OrderBy orderBy = new OrderBy(order, ascending);
final Cursor c = context.getContentResolver().query(CachedUsers.CONTENT_URI,
CachedUsers.BASIC_COLUMNS, selection != null ? selection.getSQL() : null,
selectionArgs, orderBy.getSQL());

View File

@ -95,9 +95,10 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade
@Subscribe
public void notifyTaskStateChanged(TaskStateChangedEvent event) {
// updateRefreshState();
updateRefreshState();
}
@Override
public void onEntryClick(int position, DirectMessageEntry entry) {
Utils.openMessageConversation(getActivity(), entry.account_id, entry.conversation_id);
@ -105,7 +106,6 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade
@Override
public void onRefresh() {
if (isRefreshing()) return;
new TwidereAsyncTask<Void, Void, long[][]>() {
@Override
@ -291,7 +291,7 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// updateRefreshState();
updateRefreshState();
}
}
@ -319,11 +319,15 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade
// loadMoreMessages();
// }
// protected void updateRefreshState() {
// final AsyncTwitterWrapper twitter = getTwitterWrapper();
// if (twitter == null || !getUserVisibleHint()) return;
// setRefreshing(twitter.isReceivedDirectMessagesRefreshing() || twitter.isSentDirectMessagesRefreshing());
// }
protected void updateRefreshState() {
final AsyncTwitterWrapper twitter = getTwitterWrapper();
if (twitter == null || !getUserVisibleHint()) return;
setRefreshing(twitter.isReceivedDirectMessagesRefreshing() || twitter.isSentDirectMessagesRefreshing());
}
public void setRefreshing(boolean refreshing) {
mSwipeRefreshLayout.setRefreshing(refreshing);
}
public boolean isRefreshing() {
return mSwipeRefreshLayout.isRefreshing();
@ -346,30 +350,31 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade
counts.add(id);
}
}
//
// private void loadMoreMessages() {
// if (isRefreshing()) return;
// new TwidereAsyncTask<Void, Void, long[][]>() {
//
// @Override
// protected long[][] doInBackground(final Void... params) {
// final long[][] result = new long[3][];
// result[0] = getActivatedAccountIds(getActivity());
// result[1] = getOldestMessageIdsFromDatabase(getActivity(), DirectMessages.Inbox.CONTENT_URI);
// result[2] = getOldestMessageIdsFromDatabase(getActivity(), DirectMessages.Outbox.CONTENT_URI);
// return result;
// }
//
// @Override
// protected void onPostExecute(final long[][] result) {
// final AsyncTwitterWrapper twitter = getTwitterWrapper();
// if (twitter == null) return;
// twitter.getReceivedDirectMessagesAsync(result[0], result[1], null);
// twitter.getSentDirectMessagesAsync(result[0], result[2], null);
// }
//
// }.executeTask();
// }
//
private void loadMoreMessages() {
if (isRefreshing()) return;
new TwidereAsyncTask<Void, Void, long[][]>() {
@Override
protected long[][] doInBackground(final Void... params) {
final long[][] result = new long[3][];
result[0] = Utils.getActivatedAccountIds(getActivity());
result[1] = Utils.getOldestMessageIdsFromDatabase(getActivity(), DirectMessages.Inbox.CONTENT_URI);
result[2] = Utils.getOldestMessageIdsFromDatabase(getActivity(), DirectMessages.Outbox.CONTENT_URI);
return result;
}
@Override
protected void onPostExecute(final long[][] result) {
final AsyncTwitterWrapper twitter = getTwitterWrapper();
if (twitter == null) return;
twitter.getReceivedDirectMessagesAsync(result[0], result[1], null);
twitter.getSentDirectMessagesAsync(result[0], result[2], null);
}
}.executeTask();
}
private MessageEntriesAdapter getAdapter() {
return mAdapter;

View File

@ -3,6 +3,7 @@ package org.mariotaku.twidere.fragment.support;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
@ -12,23 +13,37 @@ import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.ListAdapter;
import android.widget.ListView;
import com.commonsware.cwac.merge.MergeAdapter;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelState;
import org.mariotaku.querybuilder.Columns;
import org.mariotaku.querybuilder.Expression;
import org.mariotaku.querybuilder.OrderBy;
import org.mariotaku.querybuilder.SQLQueryBuilder;
import org.mariotaku.querybuilder.Table;
import org.mariotaku.querybuilder.query.SQLSelectQuery;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.fragment.support.TrendsSuggectionsFragment.TrendsAdapter;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.accessor.ViewAccessor;
import org.mariotaku.twidere.view.ExtendedFrameLayout;
import org.mariotaku.twidere.view.iface.IExtendedView.OnFitSystemWindowsListener;
import static org.mariotaku.twidere.util.Utils.getTableNameByUri;
import static org.mariotaku.twidere.util.Utils.openTweetSearch;
public class QuickMenuFragment extends BaseSupportFragment {
public class QuickMenuFragment extends BaseSupportFragment implements OnFitSystemWindowsListener, OnItemClickListener {
private ExtendedFrameLayout mQuickMenuContainer;
private SharedPreferences mPreferences;
private Context mThemedContext;
private ListView mListView;
@ -46,9 +61,13 @@ public class QuickMenuFragment extends BaseSupportFragment {
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
final Uri uri = CachedTrends.Local.CONTENT_URI;
final String table = getTableNameByUri(uri);
final String where = table != null ? CachedTrends.TIMESTAMP + " = " + "(SELECT " + CachedTrends.TIMESTAMP
+ " FROM " + table + " ORDER BY " + CachedTrends.TIMESTAMP + " DESC LIMIT 1)" : null;
return new CursorLoader(getActivity(), uri, CachedTrends.COLUMNS, where, null, null);
final SQLSelectQuery selectQuery = SQLQueryBuilder.select(new Columns(CachedTrends.TIMESTAMP))
.from(new Table(table))
.orderBy(new OrderBy(CachedTrends.TIMESTAMP, false))
.limit(1)
.build();
final Expression where = Expression.equals(CachedTrends.TIMESTAMP, selectQuery);
return new CursorLoader(getActivity(), uri, CachedTrends.COLUMNS, where.getSQL(), null, null);
}
@Override
@ -66,20 +85,30 @@ public class QuickMenuFragment extends BaseSupportFragment {
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mQuickMenuContainer.setOnFitSystemWindowsListener(this);
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
if (mPreferences.getBoolean(KEY_QUICK_MENU_EXPANDED, false)) {
} else {
}
final Context context = getThemedContext();
mAdapter = new MergeAdapter();
mTrendsAdapter = new TrendsAdapter(getThemedContext());
mTrendsAdapter = new TrendsAdapter(context);
mAdapter.addView(Utils.newSectionView(context, R.string.trends), false);
mAdapter.addAdapter(mTrendsAdapter);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(this);
getLoaderManager().initLoader(LOADER_ID_TRENDS, null, mTrendsCallback);
}
@Override
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
return LayoutInflater.from(getThemedContext());
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return LayoutInflater.from(getThemedContext()).inflate(R.layout.fragment_quick_menu, container, false);
return inflater.inflate(R.layout.fragment_quick_menu, container, false);
}
@Override
@ -93,6 +122,7 @@ public class QuickMenuFragment extends BaseSupportFragment {
@Override
public void onBaseViewCreated(final View view, final Bundle savedInstanceState) {
super.onBaseViewCreated(view, savedInstanceState);
mQuickMenuContainer = (ExtendedFrameLayout) view.findViewById(R.id.quick_menu_fragment);
mListView = (ListView) view.findViewById(android.R.id.list);
mSlidingUpPanel = (SlidingUpPanelLayout) view.findViewById(R.id.activities_drawer);
mActivitiesConfigButton = (ImageButton) view.findViewById(R.id.activities_config_button);
@ -100,6 +130,23 @@ public class QuickMenuFragment extends BaseSupportFragment {
ViewAccessor.setBackground(activitiesContainer, ThemeUtils.getWindowBackground(getThemedContext()));
}
@Override
public void onFitSystemWindows(Rect insets) {
mQuickMenuContainer.setPadding(insets.left, insets.top, insets.right, insets.bottom);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final ListAdapter adapter = mAdapter.getAdapter(position);
if (adapter instanceof TrendsAdapter) {
openTweetSearch(getActivity(), getAccountId(), (String) adapter.getItem(position));
}
}
private long getAccountId() {
return -1;
}
private Context getThemedContext() {
if (mThemedContext != null) return mThemedContext;
final Context context = getActivity();

View File

@ -29,6 +29,11 @@ import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
@ -85,6 +90,7 @@ import org.mariotaku.twidere.util.ClipboardUtils;
import org.mariotaku.twidere.util.CompareUtils;
import org.mariotaku.twidere.util.ImageLoaderWrapper;
import org.mariotaku.twidere.util.ImageLoadingHandler;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.StatusLinkClickHandler;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereLinkify;
@ -129,7 +135,8 @@ import static org.mariotaku.twidere.util.Utils.showOkMessage;
* Created by mariotaku on 14/12/5.
*/
public class StatusFragment extends BaseSupportFragment
implements LoaderCallbacks<SingleResponse<ParcelableStatus>>, OnMediaClickListener, StatusAdapterListener {
implements LoaderCallbacks<SingleResponse<ParcelableStatus>>, OnMediaClickListener,
StatusAdapterListener, CreateNdefMessageCallback {
private static final int LOADER_ID_DETAIL_STATUS = 1;
private static final int LOADER_ID_STATUS_REPLIES = 2;
@ -173,6 +180,19 @@ public class StatusFragment extends BaseSupportFragment
}
};
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
final ParcelableStatus status = getStatus();
if (status == null) return null;
return new NdefMessage(new NdefRecord[]{
NdefRecord.createUri(LinkCreator.getStatusTwitterLink(status.user_screen_name, status.id)),
});
}
private ParcelableStatus getStatus() {
return mStatusAdapter.getStatus();
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
@ -214,6 +234,7 @@ public class StatusFragment extends BaseSupportFragment
if (view == null) throw new AssertionError();
final Context context = view.getContext();
final boolean compact = Utils.isCompactCards(context);
initNdefCallback();
mLayoutManager = new StatusListLinearLayoutManager(context, mRecyclerView);
mItemDecoration = new DividerItemDecoration(context, mLayoutManager.getOrientation());
if (compact) {
@ -364,12 +385,12 @@ public class StatusFragment extends BaseSupportFragment
if (status.media == null) {
SpiceProfilingUtil.profile(getActivity(), status.account_id,
status.id + ",Words," + status.account_id + "," + status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
+ "," + status.text_plain.length() + "," + status.timestamp);
+ "," + status.text_plain.length() + "," + status.timestamp);
SpiceProfilingUtil.log(getActivity(), status.id + ",Words," + status.account_id + "," + status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
+ "," + status.text_plain.length() + "," + status.timestamp);
} else {
for (final ParcelableMedia spiceMedia : status.media) {
if(TypeMapingUtil.getMediaType(spiceMedia.type).equals("image")) {
if (TypeMapingUtil.getMediaType(spiceMedia.type).equals("image")) {
SpiceProfilingUtil.profile(getActivity(), status.account_id,
status.id + ",PreviewM," + status.account_id + "," + status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
+ "," + status.text_plain.length() + "," + TypeMapingUtil.getMediaType(spiceMedia.type) + "," + spiceMedia.width + "x" + spiceMedia.height + ","
@ -1210,6 +1231,18 @@ public class StatusFragment extends BaseSupportFragment
}
private void initNdefCallback() {
try {
final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(getActivity());
if (adapter == null) return;
adapter.setNdefPushMessageCallback(this, getActivity());
} catch (SecurityException e) {
Log.w(LOGTAG, e);
}
}
private static class StatusListLinearLayoutManager extends LinearLayoutManager {
private final RecyclerView recyclerView;

View File

@ -76,9 +76,9 @@ public class TrendsSuggectionsFragment extends BasePullToRefreshListFragment imp
@Override
public void onListItemClick(final ListView l, final View v, final int position, final long id) {
if (mMultiSelectManager.isActive()) return;
final Cursor cur = (Cursor) mTrendsAdapter.getItem(position - l.getHeaderViewsCount());
if (cur == null) return;
openTweetSearch(getActivity(), mAccountId, cur.getString(cur.getColumnIndex(CachedTrends.NAME)));
final String trend = mTrendsAdapter.getItem(position - l.getHeaderViewsCount());
if (trend == null) return;
openTweetSearch(getActivity(), mAccountId, trend);
}
@Override
@ -126,6 +126,23 @@ public class TrendsSuggectionsFragment extends BasePullToRefreshListFragment imp
}
static class TrendsAdapter extends SimpleCursorAdapter {
private int mNameIdx;
@Override
public String getItem(int position) {
final Cursor c = getCursor();
if (c != null && !c.isClosed() && c.moveToPosition(position))
return c.getString(mNameIdx);
return null;
}
@Override
public Cursor swapCursor(Cursor c) {
if (c != null) {
mNameIdx = c.getColumnIndex(CachedTrends.NAME);
}
return super.swapCursor(c);
}
public TrendsAdapter(final Context context) {
super(context, android.R.layout.simple_list_item_1, null, new String[]{CachedTrends.NAME},

View File

@ -42,6 +42,12 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.nfc.tech.Ndef;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
@ -104,6 +110,7 @@ import org.mariotaku.twidere.text.TextAlphaSpan;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.ImageLoaderWrapper;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.ThemeUtils;
@ -160,7 +167,8 @@ import static org.mariotaku.twidere.util.Utils.showInfoMessage;
public class UserFragment extends BaseSupportFragment implements OnClickListener,
OnLinkClickListener, OnSizeChangedListener, OnSharedPreferenceChangeListener,
OnTouchListener, DrawerCallback, SupportFragmentCallback, SystemWindowsInsetsCallback {
OnTouchListener, DrawerCallback, SupportFragmentCallback, SystemWindowsInsetsCallback,
CreateNdefMessageCallback {
public static final String TRANSITION_NAME_PROFILE_IMAGE = "profile_image";
public static final String TRANSITION_NAME_PROFILE_TYPE = "profile_type";
@ -220,7 +228,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
}
private void updateRefreshState() {
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
if (user == null) return;
final AsyncTwitterWrapper twitter = getTwitterWrapper();
final boolean is_creating_friendship = twitter != null
@ -317,7 +325,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
public void onLoadFinished(final Loader<SingleResponse<Relationship>> loader,
final SingleResponse<Relationship> data) {
mFollowProgress.setVisibility(View.GONE);
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
final Relationship relationship = data.getData();
mRelationship = relationship;
if (user == null) return;
@ -566,7 +574,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
switch (requestCode) {
case REQUEST_SET_COLOR: {
if (user == null) return;
@ -644,6 +652,8 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
mProfileImageLoader = getApplication().getImageLoaderWrapper();
final FragmentActivity activity = getActivity();
initNdefCallback();
activity.setEnterSharedElementCallback(new SharedElementCallback() {
@Override
@ -715,6 +725,25 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
setupUserPages();
}
private void initNdefCallback() {
try {
final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(getActivity());
if (adapter == null) return;
adapter.setNdefPushMessageCallback(this, getActivity());
} catch (SecurityException e) {
Log.w(LOGTAG, e);
}
}
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
final ParcelableUser user = getUser();
if (user == null) return null;
return new NdefMessage(new NdefRecord[]{
NdefRecord.createUri(LinkCreator.getUserTwitterLink(user.screen_name)),
});
}
@Override
public void onStart() {
super.onStart();
@ -725,11 +754,15 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Subscribe
public void notifyProfileUpdated(ProfileUpdatedEvent event) {
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
if (user == null || !user.equals(event.user)) return;
displayUser(event.user);
}
public ParcelableUser getUser() {
return mUser;
}
@Override
public void onStop() {
final Bus bus = TwidereApplication.getInstance(getActivity()).getMessageBus();
@ -739,7 +772,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Override
public void onSaveInstanceState(final Bundle outState) {
outState.putParcelable(EXTRA_USER, mUser);
outState.putParcelable(EXTRA_USER, getUser());
super.onSaveInstanceState(outState);
}
@ -763,7 +796,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
public void onPrepareOptionsMenu(final Menu menu) {
if (!shouldUseNativeMenu() || !menu.hasVisibleItems()) return;
final AsyncTwitterWrapper twitter = getTwitterWrapper();
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
final Relationship relationship = mRelationship;
if (twitter == null || user == null) return;
final boolean isMyself = user.account_id == user.id;
@ -827,7 +860,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
final AsyncTwitterWrapper twitter = getTwitterWrapper();
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
final Relationship relationship = mRelationship;
if (user == null || twitter == null) return false;
switch (item.getItemId()) {
@ -964,7 +997,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Override
public void onClick(final View view) {
final FragmentActivity activity = getActivity();
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
if (activity == null || user == null) return;
switch (view.getId()) {
case R.id.retry: {
@ -1030,7 +1063,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Override
public void onLinkClick(final String link, final String orig, final long account_id, final int type,
final boolean sensitive, int start, int end) {
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
if (user == null) return;
switch (type) {
case TwidereLinkify.LINK_TYPE_MENTION: {
@ -1156,7 +1189,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
private void getFriendship() {
mRelationship = null;
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(LOADER_ID_FRIENDSHIP);
final Bundle args = new Bundle();
@ -1187,7 +1220,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
private void updateFollowProgressState() {
final AsyncTwitterWrapper twitter = getTwitterWrapper();
final ParcelableUser user = mUser;
final ParcelableUser user = getUser();
if (twitter == null || user == null) {
mFollowButton.setVisibility(View.GONE);
mFollowProgress.setVisibility(View.GONE);

View File

@ -13,49 +13,59 @@ import org.mariotaku.twidere.model.ParcelableAccount;
public class AccountActionProvider extends ActionProvider implements TwidereConstants {
public static final int MENU_GROUP = 201;
public static final int MENU_GROUP = 201;
private final ParcelableAccount[] mAccounts;
private ParcelableAccount[] mAccounts;
private long mAccountId;
private long mAccountId;
public AccountActionProvider(final Context context) {
super(context);
mAccounts = ParcelableAccount.getAccounts(context, false, false);
}
public AccountActionProvider(final Context context, final ParcelableAccount[] accounts) {
super(context);
setAccounts(accounts);
}
@Override
public boolean hasSubMenu() {
return true;
}
public AccountActionProvider(final Context context) {
this(context, ParcelableAccount.getAccounts(context, false, false));
}
@Override
public View onCreateActionView() {
return null;
}
@Override
public void onPrepareSubMenu(final SubMenu subMenu) {
subMenu.removeGroup(MENU_GROUP);
for (final ParcelableAccount account : mAccounts) {
final MenuItem item = subMenu.add(MENU_GROUP, Menu.NONE, 0, account.name);
final Intent intent = new Intent();
intent.putExtra(EXTRA_ACCOUNT, account);
item.setIntent(intent);
}
subMenu.setGroupCheckable(MENU_GROUP, true, true);
for (int i = 0, j = subMenu.size(); i < j; i++) {
final MenuItem item = subMenu.getItem(i);
final Intent intent = item.getIntent();
final ParcelableAccount account = intent.getParcelableExtra(EXTRA_ACCOUNT);
if (account.account_id == mAccountId) {
item.setChecked(true);
}
}
}
@Override
public boolean hasSubMenu() {
return true;
}
public void setAccountId(final long accountId) {
mAccountId = accountId;
}
@Override
public View onCreateActionView() {
return null;
}
public void setAccounts(ParcelableAccount[] accounts) {
mAccounts = accounts;
}
@Override
public void onPrepareSubMenu(final SubMenu subMenu) {
if (mAccounts == null) return;
subMenu.removeGroup(MENU_GROUP);
for (final ParcelableAccount account : mAccounts) {
final MenuItem item = subMenu.add(MENU_GROUP, Menu.NONE, 0, account.name);
final Intent intent = new Intent();
intent.putExtra(EXTRA_ACCOUNT, account);
item.setIntent(intent);
}
subMenu.setGroupCheckable(MENU_GROUP, true, true);
for (int i = 0, j = subMenu.size(); i < j; i++) {
final MenuItem item = subMenu.getItem(i);
final Intent intent = item.getIntent();
final ParcelableAccount account = intent.getParcelableExtra(EXTRA_ACCOUNT);
if (account.account_id == mAccountId) {
item.setChecked(true);
}
}
}
public void setAccountId(final long accountId) {
mAccountId = accountId;
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.menu;
import android.content.Context;
import android.content.Intent;
import android.support.v4.view.ActionProvider;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.model.ParcelableAccount;
public class SupportAccountActionProvider extends ActionProvider implements TwidereConstants {
public static final int MENU_GROUP = 201;
private ParcelableAccount[] mAccounts;
private long mAccountId;
private boolean mExclusive;
public SupportAccountActionProvider(final Context context, final ParcelableAccount[] accounts) {
super(context);
mAccounts = accounts;
}
public SupportAccountActionProvider(final Context context) {
this(context, ParcelableAccount.getAccounts(context, false, false));
}
@Override
public boolean hasSubMenu() {
return true;
}
@Override
public View onCreateActionView() {
return null;
}
public void setAccounts(ParcelableAccount[] accounts) {
mAccounts = accounts;
}
public void setExclusive(boolean exclusive) {
mExclusive = exclusive;
}
@Override
public void onPrepareSubMenu(final SubMenu subMenu) {
if (mAccounts == null) return;
subMenu.removeGroup(MENU_GROUP);
for (final ParcelableAccount account : mAccounts) {
final MenuItem item = subMenu.add(MENU_GROUP, Menu.NONE, 0, account.name);
final Intent intent = new Intent();
intent.putExtra(EXTRA_ACCOUNT, account);
item.setIntent(intent);
}
subMenu.setGroupCheckable(MENU_GROUP, true, mExclusive);
for (int i = 0, j = subMenu.size(); i < j; i++) {
final MenuItem item = subMenu.getItem(i);
final Intent intent = item.getIntent();
final ParcelableAccount account = intent.getParcelableExtra(EXTRA_ACCOUNT);
if (account.account_id == mAccountId) {
item.setChecked(true);
}
}
}
public void setAccountId(final long accountId) {
mAccountId = accountId;
}
}

View File

@ -35,12 +35,12 @@ import static org.mariotaku.twidere.util.Utils.createStatusShareIntent;
/**
* Created by mariotaku on 14/12/7.
*/
public class StatusShareProvider extends ActionProvider implements Constants {
public class SupportStatusShareProvider extends ActionProvider implements Constants {
private final Context mContext;
private ParcelableStatus mStatus;
public StatusShareProvider(Context context) {
public SupportStatusShareProvider(Context context) {
super(context);
mContext = context;
}

View File

@ -97,6 +97,7 @@ import org.mariotaku.twidere.util.message.UnreadCountUpdatedEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -975,12 +976,14 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
private Cursor getDNSCursor(final String host) {
final MatrixCursor c = new MatrixCursor(TwidereDataStore.DNS.MATRIX_COLUMNS);
try {
final String address = mHostAddressResolver.resolve(host);
if (host != null && address != null) {
c.addRow(new String[]{host, address});
final InetAddress[] addresses = mHostAddressResolver.resolve(host);
for (InetAddress address : addresses) {
c.addRow(new String[]{host, address.getHostAddress()});
}
} catch (final IOException ignore) {
if (Utils.isDebugBuild()) {
Log.w(LOGTAG, ignore);
}
} catch (final IOException e) {
}
return c;
}

View File

@ -534,7 +534,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
cr.delete(SavedSearches.CONTENT_URI, where.getSQL(), null);
ContentResolverUtils.bulkInsert(cr, SavedSearches.CONTENT_URI, values);
} catch (TwitterException e) {
e.printStackTrace();
Log.w(LOGTAG, e);
}
}
return SingleResponse.getInstance();
@ -2332,21 +2332,21 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
bus.post(new StatusRetweetedEvent(status));
//spice
if (status.media == null) {
SpiceProfilingUtil.log(getContext(),status.id + ",Retweet," + account_id + ","
SpiceProfilingUtil.log(getContext(), status.id + ",Retweet," + account_id + ","
+ status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count);
SpiceProfilingUtil.profile(getContext(), account_id, status.id + ",Retweet," + account_id + ","
+ status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count);
} else {
for (final ParcelableMedia spiceMedia : status.media) {
if(TypeMapingUtil.getMediaType(spiceMedia.type).equals("image")) {
SpiceProfilingUtil.log(getContext(),status.id + ",RetweetM," + account_id + ","
if (TypeMapingUtil.getMediaType(spiceMedia.type).equals("image")) {
SpiceProfilingUtil.log(getContext(), status.id + ",RetweetM," + account_id + ","
+ status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
+ "," + spiceMedia.media_url + "," + TypeMapingUtil.getMediaType(spiceMedia.type) + "," + spiceMedia.width + "x" + spiceMedia.height);
SpiceProfilingUtil.profile(getContext(), account_id, status.id + ",RetweetM," + account_id + ","
+ status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
+ "," + spiceMedia.media_url + "," + TypeMapingUtil.getMediaType(spiceMedia.type) + "," + spiceMedia.width + "x" + spiceMedia.height);
} else {
SpiceProfilingUtil.log(getContext(),status.id + ",RetweetO," + account_id + ","
SpiceProfilingUtil.log(getContext(), status.id + ",RetweetO," + account_id + ","
+ status.user_id + "," + status.reply_count + "," + status.retweet_count + "," + status.favorite_count
+ "," + spiceMedia.media_url + "," + TypeMapingUtil.getMediaType(spiceMedia.type));
SpiceProfilingUtil.profile(getContext(), account_id, status.id + ",RetweetO," + account_id + ","

View File

@ -0,0 +1,77 @@
/*
* 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.net.Uri;
import org.mariotaku.twidere.Constants;
/**
* Created by mariotaku on 15/3/14.
*/
public class LinkCreator implements Constants {
private static final String AUTHORITY_TWITTER = "twitter.com";
public static Uri getStatusTwitterLink(String screenName, long statusId) {
Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_HTTPS);
builder.authority(AUTHORITY_TWITTER);
builder.appendPath(screenName);
builder.appendPath("status");
builder.appendPath(String.valueOf(statusId));
return builder.build();
}
public static Uri getTwidereStatusLink(long accountId, long statusId) {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_STATUS);
if (accountId > 0) {
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
}
builder.appendQueryParameter(QUERY_PARAM_STATUS_ID, String.valueOf(statusId));
return builder.build();
}
public static Uri getTwidereUserLink(long accountId, long userId, String screenName) {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER);
if (accountId > 0) {
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
}
if (userId > 0) {
builder.appendQueryParameter(QUERY_PARAM_USER_ID, String.valueOf(userId));
}
if (screenName != null) {
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
}
return builder.build();
}
public static Uri getUserTwitterLink(String screenName) {
Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_HTTPS);
builder.authority(AUTHORITY_TWITTER);
builder.appendPath(screenName);
return builder.build();
}
}

View File

@ -231,7 +231,7 @@ public class TwidereQueryBuilder {
}
qb.where(where);
qb.groupBy(Utils.getColumnsFromProjection(ConversationEntries.CONVERSATION_ID, DirectMessages.ACCOUNT_ID));
qb.orderBy(new OrderBy(ConversationEntries.MESSAGE_TIMESTAMP + " DESC"));
qb.orderBy(new OrderBy(ConversationEntries.MESSAGE_TIMESTAMP ,false));
return qb.build();
}

View File

@ -164,7 +164,7 @@ import org.mariotaku.twidere.fragment.support.UserTimelineFragment;
import org.mariotaku.twidere.fragment.support.UsersListFragment;
import org.mariotaku.twidere.graphic.ActionIconDrawable;
import org.mariotaku.twidere.graphic.PaddingDrawable;
import org.mariotaku.twidere.menu.StatusShareProvider;
import org.mariotaku.twidere.menu.SupportStatusShareProvider;
import org.mariotaku.twidere.model.AccountPreferences;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableAccount.ParcelableCredentials;
@ -648,8 +648,8 @@ public final class Utils implements Constants, TwitterConstants {
final Expression account_where = new Expression(Statuses.ACCOUNT_ID + " = " + account_id);
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(Statuses._ID)).from(new Tables(table));
qb.where(new Expression(Statuses.ACCOUNT_ID + " = " + account_id));
qb.orderBy(new OrderBy(Statuses.STATUS_ID + " DESC"));
qb.where(Expression.equals(Statuses.ACCOUNT_ID, account_id));
qb.orderBy(new OrderBy(Statuses.STATUS_ID, false));
qb.limit(itemLimit);
final Expression where = Expression.and(Expression.notIn(new Column(Statuses._ID), qb.build()), account_where);
resolver.delete(uri, where.getSQL(), null);
@ -659,8 +659,8 @@ public final class Utils implements Constants, TwitterConstants {
final Expression account_where = new Expression(DirectMessages.ACCOUNT_ID + " = " + account_id);
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(DirectMessages._ID)).from(new Tables(table));
qb.where(new Expression(DirectMessages.ACCOUNT_ID + " = " + account_id));
qb.orderBy(new OrderBy(DirectMessages.MESSAGE_ID + " DESC"));
qb.where(Expression.equals(DirectMessages.ACCOUNT_ID, account_id));
qb.orderBy(new OrderBy(DirectMessages.MESSAGE_ID, false));
qb.limit(itemLimit);
final Expression where = Expression.and(Expression.notIn(new Column(DirectMessages._ID), qb.build()), account_where);
resolver.delete(uri, where.getSQL(), null);
@ -673,7 +673,7 @@ public final class Utils implements Constants, TwitterConstants {
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
qb.select(new Column(BaseColumns._ID));
qb.from(new Tables(table));
qb.orderBy(new OrderBy(BaseColumns._ID + " DESC"));
qb.orderBy(new OrderBy(BaseColumns._ID, false));
qb.limit(itemLimit * 20);
final Expression where = Expression.notIn(new Column(BaseColumns._ID), qb.build());
resolver.delete(uri, where.getSQL(), null);
@ -1101,10 +1101,9 @@ public final class Utils implements Constants, TwitterConstants {
}
public static String getStatusShareText(final Context context, final ParcelableStatus status) {
final String link = String.format(Locale.ROOT, "https://twitter.com/%s/status/%d",
status.user_screen_name, status.id);
final Uri link = LinkCreator.getStatusTwitterLink(status.user_screen_name, status.id);
return context.getString(R.string.status_share_text_format_with_link,
status.text_plain, link);
status.text_plain, link.toString());
}
public static String getStatusShareSubject(final Context context, ParcelableStatus status) {
@ -1558,6 +1557,16 @@ public final class Utils implements Constants, TwitterConstants {
return isOAuth && TwitterContentUtils.isOfficialKey(context, consumerKey, consumerSecret);
}
public static TextView newSectionView(final Context context, final int titleRes) {
return newSectionView(context, titleRes != 0 ? context.getString(titleRes) : null);
}
public static TextView newSectionView(final Context context, final CharSequence title) {
final TextView textView = new TextView(context, null, android.R.attr.listSeparatorTextViewStyle);
textView.setText(title);
return textView;
}
public static boolean setLastSeen(Context context, UserMentionEntity[] entities, long time) {
if (entities == null) return false;
boolean result = false;
@ -3053,18 +3062,15 @@ public final class Utils implements Constants, TwitterConstants {
builder.authority(AUTHORITY_SEARCH);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(account_id));
builder.appendQueryParameter(QUERY_PARAM_QUERY, query);
final Intent intent = new Intent(Intent.ACTION_VIEW, builder.build());
final Uri uri = builder.build();
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
context.startActivity(intent);
}
public static void openStatus(final Context context, final long accountId, final long statusId) {
if (context == null || accountId <= 0 || statusId <= 0) return;
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_STATUS);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
builder.appendQueryParameter(QUERY_PARAM_STATUS_ID, String.valueOf(statusId));
final Intent intent = new Intent(Intent.ACTION_VIEW, builder.build());
final Uri uri = LinkCreator.getTwidereStatusLink(accountId, statusId);
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
context.startActivity(intent);
}
@ -3387,17 +3393,8 @@ public final class Utils implements Constants, TwitterConstants {
public static void openUserProfile(final Context context, final long accountId, final long userId,
final String screenName, final Bundle activityOptions) {
if (context == null || accountId <= 0 || userId <= 0 && isEmpty(screenName)) return;
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
if (userId > 0) {
builder.appendQueryParameter(QUERY_PARAM_USER_ID, String.valueOf(userId));
}
if (screenName != null) {
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
}
final Intent intent = new Intent(Intent.ACTION_VIEW, builder.build());
final Uri uri = LinkCreator.getTwidereUserLink(accountId, userId, screenName);
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (context instanceof Activity) {
ActivityCompat.startActivity((Activity) context, intent, activityOptions);
} else {
@ -3613,8 +3610,8 @@ public final class Utils implements Constants, TwitterConstants {
EXTRA_STATUS, EXTRA_STATUS_JSON, status);
final MenuItem shareItem = menu.findItem(R.id.share);
final ActionProvider shareProvider = MenuItemCompat.getActionProvider(shareItem);
if (shareProvider instanceof StatusShareProvider) {
((StatusShareProvider) shareProvider).setStatus(status);
if (shareProvider instanceof SupportStatusShareProvider) {
((SupportStatusShareProvider) shareProvider).setStatus(status);
} else if (shareProvider instanceof ShareActionProvider) {
final Intent shareIntent = createStatusShareIntent(context, status);
((ShareActionProvider) shareProvider).setShareIntent(shareIntent);

View File

@ -24,9 +24,13 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build;
import org.mariotaku.querybuilder.Columns;
import org.mariotaku.querybuilder.NewColumn;
import org.mariotaku.querybuilder.SQLQueryBuilder;
import org.mariotaku.querybuilder.Table;
import org.mariotaku.querybuilder.query.SQLCreateIndexQuery;
import org.mariotaku.querybuilder.query.SQLCreateTableQuery;
import org.mariotaku.querybuilder.query.SQLCreateViewQuery;
import org.mariotaku.twidere.Constants;
@ -85,12 +89,27 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
db.execSQL(createTable(Tabs.TABLE_NAME, Tabs.COLUMNS, Tabs.TYPES, true));
db.execSQL(createTable(SavedSearches.TABLE_NAME, SavedSearches.COLUMNS, SavedSearches.TYPES, true));
db.execSQL(createTable(SearchHistory.TABLE_NAME, SearchHistory.COLUMNS, SearchHistory.TYPES, true));
db.execSQL(createDirectMessagesView().getSQL());
db.execSQL(createDirectMessageConversationEntriesView().getSQL());
createViews(db);
createIndices(db);
db.setTransactionSuccessful();
db.endTransaction();
}
private void createIndices(SQLiteDatabase db) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
db.execSQL(createIndex("statuses_index", Statuses.TABLE_NAME, new String[]{Statuses.ACCOUNT_ID}, true));
db.execSQL(createIndex("mentions_index", Mentions.TABLE_NAME, new String[]{Statuses.ACCOUNT_ID}, true));
}
private void createViews(SQLiteDatabase db) {
db.execSQL(createDirectMessagesView().getSQL());
db.execSQL(createDirectMessageConversationEntriesView().getSQL());
}
@Override
public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
handleVersionChange(db, oldVersion, newVersion);
@ -166,8 +185,8 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
safeUpgrade(db, SavedSearches.TABLE_NAME, SavedSearches.COLUMNS, SavedSearches.TYPES, true, null);
safeUpgrade(db, SearchHistory.TABLE_NAME, SearchHistory.COLUMNS, SearchHistory.TYPES, true, null);
db.beginTransaction();
db.execSQL(createDirectMessagesView().getSQL());
db.execSQL(createDirectMessageConversationEntriesView().getSQL());
createViews(db);
createIndices(db);
db.setTransactionSuccessful();
db.endTransaction();
}
@ -179,4 +198,12 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
return qb.buildSQL();
}
private static String createIndex(final String indexName, final String tableName, final String[] columns,
final boolean createIfNotExists) {
final SQLCreateIndexQuery.Builder qb = SQLQueryBuilder.createIndex(false, createIfNotExists);
qb.name(indexName);
qb.on(new Table(tableName), new Columns(columns));
return qb.buildSQL();
}
}

View File

@ -1,94 +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.twidere.util.net;
import android.net.SSLCertificateSocketFactory;
import org.apache.http.conn.util.InetAddressUtils;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.Socket;
import javax.net.ssl.SSLSocketFactory;
import twitter4j.http.HostAddressResolver;
/**
* Created by mariotaku on 15/1/31.
*/
public class HostResolvedSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory defaultFactory;
private final HostAddressResolver resolver;
public HostResolvedSSLSocketFactory(HostAddressResolver resolver, boolean ignoreError) {
if (ignoreError) {
defaultFactory = SSLCertificateSocketFactory.getInsecure(0, null);
} else {
defaultFactory = SSLCertificateSocketFactory.getDefault(0, null);
}
this.resolver = resolver;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return defaultFactory.createSocket(host, port);
}
@Override
public Socket createSocket() throws IOException {
return defaultFactory.createSocket();
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return defaultFactory.createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return defaultFactory.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return defaultFactory.createSocket(address, port, localAddress, localPort);
}
@Override
public String[] getDefaultCipherSuites() {
return defaultFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return defaultFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return defaultFactory.createSocket(s, host, port, autoClose);
}
}

View File

@ -1,118 +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.twidere.util.net;
import org.apache.http.conn.util.InetAddressUtils;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.Socket;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import twitter4j.http.HostAddressResolver;
/**
* Created by mariotaku on 15/1/31.
*/
public class HostResolvedSocketFactory extends SocketFactory {
private final SocketFactory defaultFactory;
private final HostAddressResolver resolver;
public HostResolvedSocketFactory(HostAddressResolver resolver) {
defaultFactory = SocketFactory.getDefault();
this.resolver = resolver;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
final String resolvedHost = resolver.resolve(host);
if (resolvedHost != null && !resolvedHost.equals(host)) {
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
}
}
return defaultFactory.createSocket(host, port);
}
@Override
public Socket createSocket() throws IOException {
return new Socket();
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
final String resolvedHost = resolver.resolve(host);
if (resolvedHost != null && !resolvedHost.equals(host)) {
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
}
}
return defaultFactory.createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
final String hostName = host.getHostName();
final String resolvedHost = resolver.resolve(hostName);
if (resolvedHost != null && !resolvedHost.equals(hostName)) {
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port);
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port);
}
}
return defaultFactory.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
final String hostName = address.getHostName();
final String resolvedHost = resolver.resolve(hostName);
if (resolvedHost != null && !resolvedHost.equals(hostName)) {
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port, localAddress, localPort);
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port, localAddress, localPort);
}
}
return defaultFactory.createSocket(address, port, localAddress, localPort);
}
protected HostAddressResolver getResolver() {
return resolver;
}
}

View File

@ -19,6 +19,7 @@
package org.mariotaku.twidere.util.net;
import android.net.SSLCertificateSocketFactory;
import android.net.Uri;
import com.squareup.okhttp.Headers;
@ -28,15 +29,19 @@ import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request.Builder;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.Internal;
import com.squareup.okhttp.internal.Network;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.util.Utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -44,6 +49,8 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;
import javax.net.SocketFactory;
import twitter4j.TwitterException;
import twitter4j.auth.Authorization;
import twitter4j.http.HostAddressResolver;
@ -101,12 +108,28 @@ public class OkHttpClientImpl implements HttpClient, TwidereConstants {
final OkHttpClient client = new OkHttpClient();
final boolean ignoreSSLError = conf.isSSLErrorIgnored();
client.setHostnameVerifier(new HostResolvedHostnameVerifier(ignoreSSLError));
client.setSslSocketFactory(new HostResolvedSSLSocketFactory(resolver, ignoreSSLError));
client.setSocketFactory(new HostResolvedSocketFactory(resolver));
if (ignoreSSLError) {
client.setSslSocketFactory(SSLCertificateSocketFactory.getInsecure(0, null));
} else {
client.setSslSocketFactory(SSLCertificateSocketFactory.getDefault(0, null));
}
client.setSocketFactory(SocketFactory.getDefault());
if (conf.isProxyConfigured()) {
client.setProxy(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(conf.getHttpProxyHost(),
conf.getHttpProxyPort())));
}
Internal.instance.setNetwork(client, new Network() {
@Override
public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException {
try {
return resolver.resolve(host);
} catch (IOException e) {
if (e instanceof UnknownHostException) throw (UnknownHostException) e;
throw new UnknownHostException("Unable to resolve address " + e.getMessage());
}
}
});
return client;
}

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.util.net;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.util.Log;
import org.apache.http.conn.util.InetAddressUtils;
@ -39,8 +40,14 @@ import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.Type;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import twitter4j.http.HostAddressResolver;
@ -48,7 +55,7 @@ import static android.text.TextUtils.isEmpty;
public class TwidereHostAddressResolver implements Constants, HostAddressResolver {
private static final String RESOLVER_LOGTAG = "TwidereHostAddressResolver";
private static final String RESOLVER_LOGTAG = "Twidere.Host";
private static final String DEFAULT_DNS_SERVER_ADDRESS = "8.8.8.8";
@ -76,111 +83,125 @@ public class TwidereHostAddressResolver implements Constants, HostAddressResolve
mHostCache.remove(host);
}
@NonNull
@Override
public String resolve(final String host) throws IOException {
if (host == null) return null;
if (isValidIpAddress(host)) return null;
public InetAddress[] resolve(@NonNull final String host) throws IOException {
return resolveInternal(host, host);
}
private InetAddress[] resolveInternal(String originalHost, String host) throws IOException {
if (isValidIpAddress(host)) return fromAddressString(originalHost, host);
// First, I'll try to load address cached.
if (mHostCache.containsKey(host)) {
final InetAddress[] hostAddr = mHostCache.get(host);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Got cached address " + mHostCache.get(host) + " for host " + host);
Log.d(RESOLVER_LOGTAG, "Got cached " + Arrays.toString(hostAddr));
}
return mHostCache.get(host);
return hostAddr;
}
// Then I'll try to load from custom host mapping.
// Stupid way to find top domain, but really fast.
if (mHostMapping.contains(host)) {
final String mappedAddr = mHostMapping.getString(host, null);
mHostCache.put(host, mappedAddr);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Got mapped address " + mappedAddr + " for host " + host);
if (mappedAddr != null) {
final InetAddress[] hostAddr = fromAddressString(originalHost, mappedAddr);
mHostCache.put(originalHost, hostAddr);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Got mapped " + Arrays.toString(hostAddr));
}
return hostAddr;
}
return mappedAddr;
}
mSystemHosts.reloadIfNeeded();
if (mSystemHosts.contains(host)) {
final String hostAddr = mSystemHosts.getAddress(host);
mHostCache.put(host, hostAddr);
final InetAddress[] hostAddr = fromAddressString(originalHost, mSystemHosts.getAddress(host));
mHostCache.put(originalHost, hostAddr);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Got mapped address " + hostAddr + " for host " + host);
Log.d(RESOLVER_LOGTAG, "Got hosts " + Arrays.toString(hostAddr));
}
return hostAddr;
}
final String customMappedHost = findHost(host);
if (customMappedHost != null) {
mHostCache.put(host, customMappedHost);
final InetAddress[] hostAddr = fromAddressString(originalHost, customMappedHost);
mHostCache.put(originalHost, hostAddr);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Got mapped address " + customMappedHost + " for host " + host);
}
return customMappedHost;
return hostAddr;
}
initDns();
// Use TCP DNS Query if enabled.
if (mDns != null && mPreferences.getBoolean(KEY_TCP_DNS_QUERY, false)) {
final Resolver dns = getResolver();
if (dns != null && mPreferences.getBoolean(KEY_TCP_DNS_QUERY, false)) {
final Lookup lookup = new Lookup(new Name(host), Type.A, DClass.IN);
final Record[] records;
lookup.setResolver(mDns);
lookup.setResolver(dns);
lookup.run();
final int result = lookup.getResult();
if (result != Lookup.SUCCESSFUL) {
throw new IOException("Could not find " + host);
}
records = lookup.getAnswers();
String hostAddr = null;
final ArrayList<InetAddress> resolvedAddresses = new ArrayList<>();
// Test each IP address resolved.
for (final Record record : records) {
if (record instanceof ARecord) {
final InetAddress ipv4Addr = ((ARecord) record).getAddress();
if (ipv4Addr.isReachable(300)) {
hostAddr = ipv4Addr.getHostAddress();
}
resolvedAddresses.add(InetAddress.getByAddress(originalHost, ipv4Addr.getAddress()));
// if (ipv4Addr.isReachable(300)) {
// hostAddr = ipv4Addr.getHostAddress();
// }
} else if (record instanceof AAAARecord) {
final InetAddress ipv6Addr = ((AAAARecord) record).getAddress();
if (ipv6Addr.isReachable(300)) {
hostAddr = ipv6Addr.getHostAddress();
}
resolvedAddresses.add(InetAddress.getByAddress(originalHost, ipv6Addr.getAddress()));
// if (ipv6Addr.isReachable(300)) {
// hostAddr = ipv6Addr.getHostAddress();
// }
}
if (hostAddr != null) {
mHostCache.put(host, hostAddr);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Resolved address " + hostAddr + " for host " + host);
}
return hostAddr;
}
if (!resolvedAddresses.isEmpty()) {
final InetAddress[] hostAddr = resolvedAddresses.toArray(new InetAddress[resolvedAddresses.size()]);
mHostCache.put(originalHost, hostAddr);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Resolved " + Arrays.toString(hostAddr));
}
return hostAddr;
}
// No address is reachable, but I believe the IP is correct.
final Record record = records[0];
if (record instanceof ARecord) {
final InetAddress ipv4Addr = ((ARecord) record).getAddress();
hostAddr = ipv4Addr.getHostAddress();
} else if (record instanceof AAAARecord) {
final InetAddress ipv6Addr = ((AAAARecord) record).getAddress();
hostAddr = ipv6Addr.getHostAddress();
} else if (record instanceof CNAMERecord)
return resolve(((CNAMERecord) record).getTarget().toString());
mHostCache.put(host, hostAddr);
if (Utils.isDebugBuild()) {
Log.d(RESOLVER_LOGTAG, "Resolved address " + hostAddr + " for host " + host);
for (final Record record : records) {
if (record instanceof CNAMERecord)
return resolveInternal(originalHost, ((CNAMERecord) record).getTarget().toString());
}
return hostAddr;
}
if (Utils.isDebugBuild()) {
Log.w(RESOLVER_LOGTAG, "Resolve address " + host + " failed, using original host");
}
return host;
return InetAddress.getAllByName(host);
}
private InetAddress[] fromAddressString(String host, String address) throws UnknownHostException {
InetAddress inetAddress = InetAddress.getByName(address);
if (inetAddress instanceof Inet4Address) {
return new InetAddress[]{Inet4Address.getByAddress(host, inetAddress.getAddress())};
} else if (inetAddress instanceof Inet6Address) {
return new InetAddress[]{Inet6Address.getByAddress(host, inetAddress.getAddress())};
}
throw new UnknownHostException("Bad address " + host + " = " + address);
}
private String findHost(final String host) {
for (final String rule : mHostMapping.getAll().keySet()) {
if (hostMatches(host, rule)) return mHostMapping.getString(rule, null);
for (final Entry<String, ?> entry : mHostMapping.getAll().entrySet()) {
if (hostMatches(host, entry.getKey())) return (String) entry.getValue();
}
return null;
}
private void initDns() throws IOException {
if (mDns != null) return;
private Resolver getResolver() throws IOException {
if (mDns != null) return mDns;
mDns = new SimpleResolver(mDnsAddress);
mDns.setTCP(true);
return mDns;
}
private static boolean hostMatches(final String host, final String rule) {
@ -194,7 +215,7 @@ public class TwidereHostAddressResolver implements Constants, HostAddressResolve
return InetAddressUtils.isIPv4Address(address) || InetAddressUtils.isIPv6Address(address);
}
private static class HostCache extends LinkedHashMap<String, String> {
private static class HostCache extends LinkedHashMap<String, InetAddress[]> {
private static final long serialVersionUID = -9216545511009449147L;
@ -203,7 +224,7 @@ public class TwidereHostAddressResolver implements Constants, HostAddressResolve
}
@Override
public String put(final String key, final String value) {
public InetAddress[] put(final String key, final InetAddress... value) {
if (value == null) return null;
return super.put(key, value);
}

View File

@ -15,6 +15,7 @@ import android.widget.TextView;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.iface.ContentCardClickListener;
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter;
import org.mariotaku.twidere.model.ParcelableLocation;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableStatus.CursorIndices;
@ -37,9 +38,8 @@ import twitter4j.TranslationResult;
import static org.mariotaku.twidere.util.Utils.getUserTypeIconRes;
/**
*
* IDE gives me warning if I don't change default comment, so I write this XD
*
* <p/>
* Created by mariotaku on 14/11/19.
*/
public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
@ -217,7 +217,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick
} else {
favoriteCountView.setText(null);
}
displayExtraTypeIcon(status.card_name, status.media != null ? status.media.length : 0);
displayExtraTypeIcon(status.card_name, status.media, status.location);
}
public void displayStatus(@NonNull Cursor cursor, @NonNull CursorIndices indices,
@ -253,7 +253,10 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick
final String in_reply_to_screen_name = cursor.getString(indices.in_reply_to_user_screen_name);
final String card_name = cursor.getString(indices.card_name);
final ParcelableMedia[] media = SimpleValueSerializer.fromSerializedString(cursor.getString(indices.media), ParcelableMedia.SIMPLE_CREATOR);
final ParcelableMedia[] media = SimpleValueSerializer.fromSerializedString(
cursor.getString(indices.media), ParcelableMedia.SIMPLE_CREATOR);
final ParcelableLocation location = ParcelableLocation.fromString(
cursor.getString(indices.location));
if (retweet_id > 0) {
final String retweetedBy = UserColorNameUtils.getDisplayName(context, retweeted_by_id,
@ -348,7 +351,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick
} else {
favoriteCountView.setText(null);
}
displayExtraTypeIcon(card_name, media != null ? media.length : 0);
displayExtraTypeIcon(card_name, media, location);
}
public CardView getCardView() {
@ -421,7 +424,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick
setTextSize(adapter.getTextSize());
}
private void displayExtraTypeIcon(String cardName, int mediaLength) {
private void displayExtraTypeIcon(String cardName, ParcelableMedia[] media, ParcelableLocation location) {
if (TwitterCardUtils.CARD_NAME_AUDIO.equals(cardName)) {
extraTypeView.setImageResource(R.drawable.ic_action_music);
extraTypeView.setVisibility(View.VISIBLE);
@ -431,9 +434,12 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick
} else if (TwitterCardUtils.CARD_NAME_PLAYER.equals(cardName)) {
extraTypeView.setImageResource(R.drawable.ic_action_play_circle);
extraTypeView.setVisibility(View.VISIBLE);
} else if (mediaLength > 0) {
} else if (media != null && media.length > 0) {
extraTypeView.setImageResource(R.drawable.ic_action_gallery);
extraTypeView.setVisibility(View.VISIBLE);
} else if (location != null && location.isValid()) {
extraTypeView.setImageResource(R.drawable.ic_action_location);
extraTypeView.setVisibility(View.VISIBLE);
} else {
extraTypeView.setVisibility(View.GONE);
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#808080"/>
<item android:state_window_focused="false" android:color="#808080"/>
<item android:state_pressed="true" android:color="#808080"/>
<item android:state_selected="true" android:color="#323232"/>
<item android:color="#808080"/>
<!-- not selected -->
</selector>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#b0b0b0"/>
<item android:state_window_focused="false" android:color="#808080"/>
<item android:state_pressed="true" android:color="#808080"/>
<item android:state_selected="true" android:color="#808080"/>
<item android:color="#969696"/> <!-- not selected -->
</selector>

View File

@ -46,6 +46,7 @@
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="?cardItemBackgroundColor"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="false"
@ -200,6 +201,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/element_spacing_normal"
app:cardBackgroundColor="?cardItemBackgroundColor"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="false"
@ -300,6 +302,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/element_spacing_normal"
app:cardBackgroundColor="?cardItemBackgroundColor"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="false"

View File

@ -217,7 +217,8 @@
android:paddingLeft="@dimen/element_spacing_normal"
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_reply"/>
app:iabActivatedColor="@color/highlight_reply"
app:iabColor="?android:textColorTertiary"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
android:id="@+id/retweet_count"
@ -229,7 +230,8 @@
android:paddingLeft="@dimen/element_spacing_normal"
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_retweet"/>
app:iabActivatedColor="@color/highlight_retweet"
app:iabColor="?android:textColorTertiary"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
android:id="@+id/favorite_count"
@ -241,7 +243,8 @@
android:paddingLeft="@dimen/element_spacing_normal"
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_favorite"/>
app:iabActivatedColor="@color/highlight_favorite"
app:iabColor="?android:textColorTertiary"/>
</LinearLayout>

View File

@ -196,7 +196,8 @@
android:paddingLeft="@dimen/element_spacing_normal"
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_reply"/>
app:iabActivatedColor="@color/highlight_reply"
app:iabColor="?android:textColorTertiary"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
android:id="@+id/retweet_count"
@ -209,7 +210,8 @@
android:paddingLeft="@dimen/element_spacing_normal"
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_retweet"/>
app:iabActivatedColor="@color/highlight_retweet"
app:iabColor="?android:textColorTertiary"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
android:id="@+id/favorite_count"
@ -222,7 +224,8 @@
android:paddingLeft="@dimen/element_spacing_normal"
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_favorite"/>
app:iabActivatedColor="@color/highlight_favorite"
app:iabColor="?android:textColorTertiary"/>
</LinearLayout>

View File

@ -25,41 +25,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
<fragment
android:id="@+id/right_drawer"
class="org.mariotaku.twidere.fragment.support.QuickMenuFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<fragment
android:id="@+id/right_drawer"
class="org.mariotaku.twidere.fragment.support.QuickMenuFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_quick_menu"/>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/element_spacing_normal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:rotation="95.0"
android:text=": )"
android:textColor="@android:color/white"
android:textSize="48sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:gravity="center"
android:text="Something awesome will appear soon"
android:textColor="@android:color/white"/>
</LinearLayout>
tools:layout="@layout/fragment_quick_menu"/>
</org.mariotaku.twidere.view.RightDrawerFrameLayout>

View File

@ -17,68 +17,73 @@
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<com.sothree.slidinguppanel.SlidingUpPanelLayout
android:id="@+id/activities_drawer"
<org.mariotaku.twidere.view.ExtendedFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
app:umanoDragView="@+id/activities_header_title"
app:umanoPanelHeight="@dimen/header_height_quick_menu">
android:id="@+id/quick_menu_fragment"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="@+id/quick_menu_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<LinearLayout
android:id="@+id/activities_container"
<com.sothree.slidinguppanel.SlidingUpPanelLayout
android:id="@+id/activities_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/activities_header"
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="@dimen/header_height_quick_menu"
android:orientation="horizontal">
<TextView
android:id="@+id/activities_header_title"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/notifications"
android:textAllCaps="true"
android:textAppearance="?android:textAppearanceSmall"
android:textStyle="bold"/>
<ImageButton
android:id="@+id/activities_config_button"
android:layout_width="@dimen/element_size_normal"
android:layout_height="match_parent"
android:layout_weight="0"
android:background="?android:selectableItemBackground"
android:contentDescription="@string/customize"
android:src="@drawable/ic_action_settings"
android:text="@string/notifications"/>
</LinearLayout>
android:gravity="bottom"
app:umanoDragView="@+id/activities_header_title"
app:umanoPanelHeight="@dimen/header_height_quick_menu">
<FrameLayout
android:id="@+id/activities_content"
android:id="@+id/quick_menu_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
android:layout_height="match_parent">
</com.sothree.slidinguppanel.SlidingUpPanelLayout>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<LinearLayout
android:id="@+id/activities_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/activities_header"
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="@dimen/header_height_quick_menu"
android:orientation="horizontal">
<TextView
android:id="@+id/activities_header_title"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/notifications"
android:textAllCaps="true"
android:textAppearance="?android:textAppearanceSmall"
android:textStyle="bold"/>
<ImageButton
android:id="@+id/activities_config_button"
android:layout_width="@dimen/element_size_normal"
android:layout_height="match_parent"
android:layout_weight="0"
android:background="?android:selectableItemBackground"
android:contentDescription="@string/customize"
android:src="@drawable/ic_action_settings"
android:text="@string/notifications"/>
</LinearLayout>
<FrameLayout
android:id="@+id/activities_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
</com.sothree.slidinguppanel.SlidingUpPanelLayout>
</org.mariotaku.twidere.view.ExtendedFrameLayout>

View File

@ -21,6 +21,7 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -56,7 +57,8 @@
android:layout_marginBottom="@dimen/element_spacing_mlarge"
android:layout_marginTop="@dimen/element_spacing_mlarge"
app:sivBorder="true"
app:sivBorderWidth="2dp"/>
app:sivBorderWidth="2dp"
tools:src="@drawable/profile_image_nyan_sakamoto"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/other_accounts_list"
@ -86,23 +88,33 @@
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorPrimary"
android:textStyle="bold"/>
android:textStyle="bold"
tools:text="Name"/>
<org.mariotaku.twidere.view.themed.ThemedTextView
android:id="@+id/screen_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceSmall"/>
android:singleLine="true"
android:textAppearance="?android:textAppearanceSmall"
tools:text="\@username"/>
</LinearLayout>
<org.mariotaku.twidere.view.themed.ThemedSwitch
android:id="@+id/toggle"
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"/>
android:layout_height="@dimen/element_size_normal">
<org.mariotaku.twidere.view.TwidereActionMenuView
android:id="@+id/toggle_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</LinearLayout>
</RelativeLayout>
</FrameLayout>

View File

@ -34,10 +34,11 @@
android:clickable="true"
android:paddingLeft="@dimen/element_spacing_small"
android:paddingRight="@dimen/element_spacing_small"
android:layout_marginBottom="@dimen/element_spacing_minus_normal"
tools:visiblity="visible">
<Space
android:layout_width="@dimen/icon_size_card_list_item"
android:layout_width="@dimen/element_spacing_large"
android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_profile_image_detail_page"/>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@id/select_account"
android:icon="@drawable/ic_action_user"
android:title="@string/select_account"
app:actionProviderClass="org.mariotaku.twidere.menu.SupportAccountActionProvider"
app:showAsAction="always"/>
</menu>

View File

@ -32,7 +32,7 @@
android:id="@id/share"
android:icon="@drawable/ic_action_share"
android:title="@string/share"
app:actionProviderClass="org.mariotaku.twidere.menu.StatusShareProvider"
app:actionProviderClass="org.mariotaku.twidere.menu.SupportStatusShareProvider"
app:showAsAction="always"/>
<item
android:id="@id/copy"

View File

@ -727,5 +727,7 @@
<string name="research_tsinghua_spice">Tsinghua Spice</string>
<string name="unknown_location">Unknown location</string>
<string name="ellipsis"></string>
<string name="designed_by">Designed by</string>
<string name="designer_name">Uucky Lee</string>
</resources>

View File

@ -25,6 +25,8 @@
<!--<item name="android:colorPrimaryDark">@color/material_light_blue_700</item>-->
<!--<item name="android:colorAccent">@color/material_light_blue_a200</item>-->
<!--<item name="android:colorAccent">#ccc</item>-->
<item name="android:textColorTertiary">@color/tertiary_text_mtrl_dark</item>
<item name="android:textColorTertiaryInverse">@color/tertiary_text_mtrl_light</item>
</style>
<style name="Theme.Compat.Base.NoActionBar" parent="Theme.AppCompat.NoActionBar">
@ -32,6 +34,8 @@
<!--<item name="android:colorPrimaryDark">@color/material_light_blue_700</item>-->
<!--<item name="android:colorAccent">@color/material_light_blue_a200</item>-->
<!--<item name="android:colorAccent">#ccc</item>-->
<item name="android:textColorTertiary">@color/tertiary_text_mtrl_dark</item>
<item name="android:textColorTertiaryInverse">@color/tertiary_text_mtrl_light</item>
</style>
<style name="Theme.Compat.Base.Dialog" parent="Theme.AppCompat.Dialog">
@ -39,6 +43,8 @@
<!--<item name="android:colorPrimaryDark">@color/material_light_blue_700</item>-->
<!--<item name="android:colorAccent">@color/material_light_blue_a200</item>-->
<!--<item name="android:colorAccent">#ccc</item>-->
<item name="android:textColorTertiary">@color/tertiary_text_mtrl_dark</item>
<item name="android:textColorTertiaryInverse">@color/tertiary_text_mtrl_light</item>
</style>
<style name="Theme.Compat.Base.Light" parent="Theme.AppCompat.Light">
@ -46,6 +52,8 @@
<!--<item name="android:colorPrimaryDark">@color/material_light_blue_700</item>-->
<!--<item name="android:colorAccent">@color/material_light_blue_a200</item>-->
<!--<item name="android:colorAccent">#aaa</item>-->
<item name="android:textColorTertiary">@color/tertiary_text_mtrl_light</item>
<item name="android:textColorTertiaryInverse">@color/tertiary_text_mtrl_dark</item>
</style>
<style name="Theme.Compat.Base.Light.DarkActionBar" parent="Theme.AppCompat.Light.DarkActionBar">
@ -53,6 +61,8 @@
<!--<item name="android:colorPrimaryDark">@color/material_light_blue_700</item>-->
<!--<item name="android:colorAccent">@color/material_light_blue_a200</item>-->
<!--<item name="android:colorAccent">#aaa</item>-->
<item name="android:textColorTertiary">@color/tertiary_text_mtrl_light</item>
<item name="android:textColorTertiaryInverse">@color/tertiary_text_mtrl_dark</item>
</style>
<style name="Theme.Compat.Base.Light.NoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
@ -60,6 +70,8 @@
<!--<item name="android:colorPrimaryDark">@color/material_light_blue_700</item>-->
<!--<item name="android:colorAccent">@color/material_light_blue_a200</item>-->
<!--<item name="android:colorAccent">#aaa</item>-->
<item name="android:textColorTertiary">@color/tertiary_text_mtrl_light</item>
<item name="android:textColorTertiaryInverse">@color/tertiary_text_mtrl_dark</item>
</style>
<style name="Theme.Compat.Base.Light.Dialog" parent="Theme.AppCompat.Light.Dialog">
@ -67,5 +79,7 @@
<!--<item name="android:colorPrimaryDark">@color/material_light_blue_700</item>-->
<!--<item name="android:colorAccent">@color/material_light_blue_a200</item>-->
<!--<item name="android:colorAccent">#aaa</item>-->
<item name="android:textColorTertiary">@color/tertiary_text_mtrl_light</item>
<item name="android:textColorTertiaryInverse">@color/tertiary_text_mtrl_dark</item>
</style>
</resources>

View File

@ -20,6 +20,13 @@
android:action="android.intent.action.VIEW"
android:data="twidere://user?user_id=57610574&amp;finish_only=true"/>
</Preference>
<Preference
android:summary="@string/designer_name"
android:title="@string/designed_by">
<intent
android:action="android.intent.action.VIEW"
android:data="twidere://user?user_id=1062473329&amp;finish_only=true"/>
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:key="cat_donate"