This commit is contained in:
Mariotaku Lee 2016-12-08 15:33:13 +08:00
parent 7bd7a151aa
commit 556e08a5f2
11 changed files with 400 additions and 412 deletions

View File

@ -6,12 +6,10 @@ import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import org.mariotaku.twidere.model.SyncAuthInfo;
interface IDataSyncService {
SyncAuthInfo getAuthInfo();
String getAuthInfo();
Intent getAuthRequestIntent(in SyncAuthInfo info);
Intent getAuthRequestIntent(in String info);
void onPerformSync(in SyncAuthInfo info, in Bundle extras, in SyncResult syncResult);
void onPerformSync(in String info, in Bundle extras, in SyncResult syncResult);
}

View File

@ -1,22 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2016 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.model;
parcelable SyncAuthInfo;

View File

@ -307,5 +307,6 @@ public interface SharedPreferenceConstants {
String KEY_DRAWER_TOGGLE = "drawer_toggle";
String KEY_MEDIA_LINK_COUNTS_IN_STATUS = "media_link_counts_in_status";
String KEY_DROPBOX_ACCESS_TOKEN = "dropbox_access_token";
}

View File

@ -3,14 +3,25 @@ package org.mariotaku.twidere.activity
import android.os.Bundle
import com.dropbox.core.android.Auth
import org.mariotaku.twidere.Constants.DROPBOX_APP_KEY
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_DROPBOX_ACCESS_TOKEN
/**
* Created by mariotaku on 2016/12/7.
*/
class DropboxAuthStarterActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Auth.startOAuth2Authentication(this, DROPBOX_APP_KEY)
}
override fun onResume() {
super.onResume()
val oauthToken = Auth.getOAuth2Token()
if (oauthToken != null) {
preferences.edit().putString(KEY_DROPBOX_ACCESS_TOKEN, oauthToken).apply()
}
finish()
}
}

View File

@ -5,9 +5,11 @@ import android.content.Intent
import android.content.SyncResult
import android.os.Bundle
import android.os.IBinder
import org.mariotaku.ktextension.convert
import org.mariotaku.twidere.IDataSyncService
import org.mariotaku.twidere.activity.DropboxAuthStarterActivity
import org.mariotaku.twidere.model.SyncAuthInfo
import org.mariotaku.twidere.util.JsonSerializer
import java.lang.ref.WeakReference
/**
@ -39,15 +41,18 @@ class DropboxDataSyncService : Service() {
internal class ServiceInterface(val service: WeakReference<DropboxDataSyncService>) : IDataSyncService.Stub() {
override fun getAuthInfo(): SyncAuthInfo? {
return service.get().getAuthInfo()
override fun getAuthInfo(): String? {
val info = service.get().getAuthInfo() ?: return null
return JsonSerializer.serialize(info)
}
override fun getAuthRequestIntent(info: SyncAuthInfo?): Intent {
override fun getAuthRequestIntent(infoJson: String?): Intent {
val info = infoJson?.convert { JsonSerializer.parse(infoJson, SyncAuthInfo::class.java) }
return service.get().getAuthRequestIntent(info)
}
override fun onPerformSync(info: SyncAuthInfo, extras: Bundle?, syncResult: SyncResult) {
override fun onPerformSync(infoJson: String, extras: Bundle?, syncResult: SyncResult) {
val info = infoJson.convert { JsonSerializer.parse(infoJson, SyncAuthInfo::class.java) }!!
service.get().onPerformSync(info, extras, syncResult)
}

View File

@ -1,315 +0,0 @@
package org.mariotaku.twidere.activity;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Pair;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.util.BugReporter;
import org.mariotaku.twidere.util.IntentUtils;
import org.mariotaku.twidere.util.Utils;
import java.util.List;
public class WebLinkHandlerActivity extends Activity implements Constants {
@SuppressWarnings("SpellCheckingInspection")
public static final String[] TWITTER_RESERVED_PATHS = {"about", "account", "accounts", "activity", "all",
"announcements", "anywhere", "api_rules", "api_terms", "apirules", "apps", "auth", "badges", "blog",
"business", "buttons", "contacts", "devices", "direct_messages", "download", "downloads",
"edit_announcements", "faq", "favorites", "find_sources", "find_users", "followers", "following",
"friend_request", "friendrequest", "friends", "goodies", "help", "home", "im_account", "inbox",
"invitations", "invite", "jobs", "list", "login", "logo", "logout", "me", "mentions", "messages",
"mockview", "newtwitter", "notifications", "nudge", "oauth", "phoenix_search", "positions", "privacy",
"public_timeline", "related_tweets", "replies", "retweeted_of_mine", "retweets", "retweets_by_others",
"rules", "saved_searches", "search", "sent", "settings", "share", "signup", "signin", "similar_to",
"statistics", "terms", "tos", "translate", "trends", "tweetbutton", "twttr", "update_discoverability",
"users", "welcome", "who_to_follow", "widgets", "zendesk_auth", "media_signup"};
@SuppressWarnings("SpellCheckingInspection")
public static final String[] FANFOU_RESERVED_PATHS = {"home", "privatemsg", "finder", "browse",
"search", "settings", "message", "mentions", "favorites", "friends", "followers",
"sharer", "photo", "album", "paipai", "q", "userview", "dialogue"};
private static final String AUTHORITY_TWITTER_COM = "twitter.com";
private static Uri regulateTwitterUri(Uri data) {
final String encodedFragment = data.getEncodedFragment();
if (encodedFragment != null && encodedFragment.startsWith("!/")) {
return regulateTwitterUri(Uri.parse("https://twitter.com" + encodedFragment.substring(1)));
}
final Uri.Builder builder = data.buildUpon();
builder.scheme("https");
builder.authority(AUTHORITY_TWITTER_COM);
return builder.build();
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final PackageManager packageManager = getPackageManager();
final Intent intent = getIntent();
intent.setExtrasClassLoader(TwidereApplication.class.getClassLoader());
final Uri uri = intent.getData();
if (uri == null || uri.getHost() == null) {
finish();
return;
}
final Pair<Intent, Boolean> handled;
switch (uri.getHost()) {
case "twitter.com":
case "www.twitter.com":
case "mobile.twitter.com": {
handled = handleTwitterLink(regulateTwitterUri(uri));
break;
}
case "fanfou.com": {
handled = handleFanfouLink(uri);
break;
}
default: {
handled = Pair.create(null, false);
break;
}
}
if (handled.first != null) {
handled.first.putExtras(intent);
startActivity(handled.first);
} else {
if (!handled.second) {
BugReporter.logException(new TwitterLinkException("Unable to handle twitter uri " + uri));
}
final Intent fallbackIntent = new Intent(Intent.ACTION_VIEW, uri);
fallbackIntent.addCategory(Intent.CATEGORY_BROWSABLE);
fallbackIntent.setPackage(IntentUtils.getDefaultBrowserPackage(this, uri, false));
final ComponentName componentName = fallbackIntent.resolveActivity(packageManager);
if (componentName == null) {
final Intent targetIntent = new Intent(Intent.ACTION_VIEW, uri);
targetIntent.addCategory(Intent.CATEGORY_BROWSABLE);
startActivity(Intent.createChooser(targetIntent, getString(R.string.open_in_browser)));
} else if (!TextUtils.equals(getPackageName(), componentName.getPackageName())) {
startActivity(fallbackIntent);
} else {
// TODO show error
}
}
finish();
}
@Override
protected void onStart() {
super.onStart();
setVisible(true);
}
@NonNull
private Pair<Intent, Boolean> handleFanfouLink(final Uri uri) {
final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.size() > 0) {
switch (pathSegments.get(0)) {
case "statuses": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_STATUS);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM);
builder.appendQueryParameter(QUERY_PARAM_STATUS_ID, pathSegments.get(1));
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
default: {
if (!ArrayUtils.contains(FANFOU_RESERVED_PATHS, pathSegments.get(0))) {
if (pathSegments.size() == 1) {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM);
final UserKey userKey = new UserKey(pathSegments.get(0), USER_TYPE_FANFOU_COM);
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, userKey.toString());
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
}
return Pair.create(null, false);
}
}
}
return Pair.create(null, false);
}
@NonNull
private Pair<Intent, Boolean> handleTwitterLink(final Uri uri) {
final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.size() > 0) {
switch (pathSegments.get(0)) {
case "i": {
return getIUriIntent(uri, pathSegments);
}
case "intent": {
return getTwitterIntentUriIntent(uri, pathSegments);
}
case "share": {
final Intent handledIntent = new Intent(this, ComposeActivity.class);
handledIntent.setAction(Intent.ACTION_SEND);
final String text = uri.getQueryParameter("text");
final String url = uri.getQueryParameter("url");
handledIntent.putExtra(Intent.EXTRA_TEXT, Utils.getShareStatus(this, text, url));
return Pair.create(handledIntent, true);
}
case "search": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_SEARCH);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_QUERY, uri.getQueryParameter("q"));
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
case "following": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_FRIENDS);
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, UserKey.SELF_REFERENCE.toString());
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
case "followers": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_FOLLOWERS);
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, UserKey.SELF_REFERENCE.toString());
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
case "favorites": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_FAVORITES);
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, UserKey.SELF_REFERENCE.toString());
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
default: {
if (ArrayUtils.contains(TWITTER_RESERVED_PATHS, pathSegments.get(0))) {
return Pair.create(null, true);
}
return handleUserSpecificPageIntent(uri, pathSegments, pathSegments.get(0));
}
}
}
return Pair.create(null, false);
}
@NonNull
private Pair<Intent, Boolean> handleUserSpecificPageIntent(Uri uri, List<String> pathSegments, String screenName) {
final int segsSize = pathSegments.size();
if (segsSize == 1) {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
} else if (segsSize == 2) {
switch (pathSegments.get(1)) {
case "following": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_FRIENDS);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
case "followers": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_FOLLOWERS);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
case "favorites": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_FAVORITES);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
default: {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_LIST);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
builder.appendQueryParameter(QUERY_PARAM_LIST_NAME, pathSegments.get(1));
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
}
} else if (segsSize >= 3) {
final long def = -1;
if ("status".equals(pathSegments.get(1)) && NumberUtils.toLong(pathSegments.get(2), def) != -1) {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_STATUS);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_STATUS_ID, pathSegments.get(2));
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
} else {
switch (pathSegments.get(2)) {
case "members": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_LIST_MEMBERS);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
builder.appendQueryParameter(QUERY_PARAM_LIST_NAME, pathSegments.get(1));
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
case "subscribers": {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_TWIDERE);
builder.authority(AUTHORITY_USER_LIST_SUBSCRIBERS);
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM);
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName);
builder.appendQueryParameter(QUERY_PARAM_LIST_NAME, pathSegments.get(1));
return Pair.create(new Intent(Intent.ACTION_VIEW, builder.build()), true);
}
}
}
}
return Pair.create(null, false);
}
private Pair<Intent, Boolean> getTwitterIntentUriIntent(Uri uri, List<String> pathSegments) {
if (pathSegments.size() < 2) return Pair.create(null, false);
switch (pathSegments.get(1)) {
case "tweet": {
final Intent handledIntent = new Intent(this, ComposeActivity.class);
handledIntent.setAction(Intent.ACTION_SEND);
final String text = uri.getQueryParameter("text");
final String url = uri.getQueryParameter("url");
handledIntent.putExtra(Intent.EXTRA_TEXT, Utils.getShareStatus(this, text, url));
return Pair.create(handledIntent, true);
}
}
return Pair.create(null, false);
}
private Pair<Intent, Boolean> getIUriIntent(Uri uri, List<String> pathSegments) {
return Pair.create(null, false);
}
private class TwitterLinkException extends Exception {
public TwitterLinkException(final String s) {
super(s);
}
}
}

View File

@ -53,6 +53,7 @@ import android.widget.Toast
import com.bluelinelabs.logansquare.LoganSquare
import com.rengwuxian.materialedittext.MaterialEditText
import kotlinx.android.synthetic.main.activity_sign_in.*
import org.mariotaku.ktextension.set
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.TwitterOAuth
@ -88,6 +89,7 @@ import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.OAuthPasswordAuthenticator.*
import org.mariotaku.twidere.util.view.ConsumerKeySecretValidator
import java.lang.ref.WeakReference
import java.util.*
class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
@ -730,37 +732,49 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
internal data class SignInResponse(
val alreadyLoggedIn: Boolean,
@Credentials.Type val authType: String = Credentials.Type.EMPTY,
@Credentials.Type val credsType: String = Credentials.Type.EMPTY,
val credentials: Credentials,
val user: ParcelableUser,
val color: Int = 0,
val accountType: Pair<String, String>? = null
val accountType: Pair<String, String>
) {
fun writeAccountInfo(am: AccountManager, account: Account) {
am.setUserData(account, ACCOUNT_USER_DATA_KEY, user.key.toString())
am.setUserData(account, ACCOUNT_USER_DATA_USER, LoganSquare.serialize(user))
private fun writeAccountInfo(map: MutableMap<String, String?>) {
map[ACCOUNT_USER_DATA_KEY] = user.key.toString()
map[ACCOUNT_USER_DATA_TYPE] = accountType.first
map[ACCOUNT_USER_DATA_CREDS_TYPE] = credsType
am.setUserData(account, ACCOUNT_USER_DATA_COLOR, toHexColor(color))
am.setUserData(account, ACCOUNT_USER_DATA_ACTIVATED, true.toString())
map[ACCOUNT_USER_DATA_ACTIVATED] = true.toString()
map[ACCOUNT_USER_DATA_COLOR] = toHexColor(color)
map[ACCOUNT_USER_DATA_USER] = LoganSquare.serialize(user)
map[ACCOUNT_USER_DATA_EXTRAS] = accountType.second
}
private fun writeAuthToken(am: AccountManager, account: Account) {
am.setAuthToken(account, ACCOUNT_AUTH_TOKEN_TYPE, LoganSquare.serialize(credentials))
if (accountType != null) {
am.setUserData(account, ACCOUNT_USER_DATA_TYPE, accountType.first)
am.setUserData(account, ACCOUNT_USER_DATA_EXTRAS, accountType.second)
}
}
fun updateAccount(am: AccountManager) {
val account = AccountUtils.findByAccountKey(am, user.key) ?: return
writeAccountInfo(am, account)
val map: MutableMap<String, String?> = HashMap()
writeAccountInfo(map)
for ((k, v) in map) {
am.setUserData(account, k, v)
}
writeAuthToken(am, account)
}
fun insertAccount(am: AccountManager) {
fun insertAccount(am: AccountManager): Account {
val account = Account(UserKey(user.screen_name, user.key.host).toString(), ACCOUNT_TYPE)
am.addAccountExplicitly(account, null, null)
writeAccountInfo(am, account)
val map: MutableMap<String, String?> = HashMap()
writeAccountInfo(map)
val userData = Bundle()
for ((k, v) in map) {
userData[k] = v
}
am.addAccountExplicitly(account, null, userData)
return account
}
}

View File

@ -0,0 +1,291 @@
package org.mariotaku.twidere.activity
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.util.Pair
import org.apache.commons.lang3.ArrayUtils
import org.mariotaku.ktextension.toLong
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.app.TwidereApplication
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.util.BugReporter
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.Utils
class WebLinkHandlerActivity : Activity(), Constants {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val packageManager = packageManager
val intent = intent
intent.setExtrasClassLoader(TwidereApplication::class.java.classLoader)
val uri = intent.data
if (uri == null || uri.host == null) {
finish()
return
}
val handled: Pair<Intent, Boolean>
when (uri.host) {
"twitter.com", "www.twitter.com", "mobile.twitter.com" -> {
handled = handleTwitterLink(regulateTwitterUri(uri))
}
"fanfou.com" -> {
handled = handleFanfouLink(uri)
}
else -> {
handled = Pair.create<Intent, Boolean>(null, false)
}
}
if (handled.first != null) {
handled.first.putExtras(intent)
startActivity(handled.first)
} else {
if (!handled.second) {
BugReporter.logException(TwitterLinkException("Unable to handle twitter uri " + uri))
}
val fallbackIntent = Intent(Intent.ACTION_VIEW, uri)
fallbackIntent.addCategory(Intent.CATEGORY_BROWSABLE)
fallbackIntent.`package` = IntentUtils.getDefaultBrowserPackage(this, uri, false)
val componentName = fallbackIntent.resolveActivity(packageManager)
if (componentName == null) {
val targetIntent = Intent(Intent.ACTION_VIEW, uri)
targetIntent.addCategory(Intent.CATEGORY_BROWSABLE)
startActivity(Intent.createChooser(targetIntent, getString(R.string.open_in_browser)))
} else if (!TextUtils.equals(packageName, componentName.packageName)) {
startActivity(fallbackIntent)
} else {
// TODO show error
}
}
finish()
}
override fun onStart() {
super.onStart()
setVisible(true)
}
private fun handleFanfouLink(uri: Uri): Pair<Intent, Boolean> {
val pathSegments = uri.pathSegments
if (pathSegments.size > 0) {
when (pathSegments[0]) {
"statuses" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_STATUS)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM)
builder.appendQueryParameter(QUERY_PARAM_STATUS_ID, pathSegments[1])
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
else -> {
if (!ArrayUtils.contains(FANFOU_RESERVED_PATHS, pathSegments[0])) {
if (pathSegments.size == 1) {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM)
val userKey = UserKey(pathSegments[0], USER_TYPE_FANFOU_COM)
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, userKey.toString())
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
}
return Pair.create<Intent, Boolean>(null, false)
}
}
}
return Pair.create<Intent, Boolean>(null, false)
}
private fun handleTwitterLink(uri: Uri): Pair<Intent, Boolean> {
val pathSegments = uri.pathSegments
if (pathSegments.size > 0) {
when (pathSegments[0]) {
"i" -> {
return getIUriIntent(uri, pathSegments)
}
"intent" -> {
return getTwitterIntentUriIntent(uri, pathSegments)
}
"share" -> {
val handledIntent = Intent(this, ComposeActivity::class.java)
handledIntent.action = Intent.ACTION_SEND
val text = uri.getQueryParameter("text")
val url = uri.getQueryParameter("url")
handledIntent.putExtra(Intent.EXTRA_TEXT, Utils.getShareStatus(this, text, url))
return Pair.create(handledIntent, true)
}
"search" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_SEARCH)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_QUERY, uri.getQueryParameter("q"))
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
"hashtag" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_SEARCH)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_QUERY, "#${uri.lastPathSegment}")
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
"following" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_FRIENDS)
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, UserKey.SELF_REFERENCE.toString())
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
"followers" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_FOLLOWERS)
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, UserKey.SELF_REFERENCE.toString())
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
"favorites" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_FAVORITES)
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, UserKey.SELF_REFERENCE.toString())
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
else -> {
if (ArrayUtils.contains(TWITTER_RESERVED_PATHS, pathSegments[0])) {
return Pair.create<Intent, Boolean>(null, true)
}
return handleUserSpecificPageIntent(uri, pathSegments, pathSegments[0])
}
}
}
return Pair.create<Intent, Boolean>(null, false)
}
private fun handleUserSpecificPageIntent(uri: Uri, pathSegments: List<String>, screenName: String): Pair<Intent, Boolean> {
val segsSize = pathSegments.size
if (segsSize == 1) {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName)
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
} else if (segsSize == 2) {
when (pathSegments[1]) {
"following" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_FRIENDS)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName)
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
"followers" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_FOLLOWERS)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName)
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
"favorites" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_FAVORITES)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName)
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
else -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_LIST)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName)
builder.appendQueryParameter(QUERY_PARAM_LIST_NAME, pathSegments[1])
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
}
} else if (segsSize >= 3) {
if ("status" == pathSegments[1] && pathSegments[2].toLong(-1) != -1L) {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_STATUS)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_STATUS_ID, pathSegments[2])
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
} else {
when (pathSegments[2]) {
"members" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_LIST_MEMBERS)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName)
builder.appendQueryParameter(QUERY_PARAM_LIST_NAME, pathSegments[1])
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
"subscribers" -> {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER_LIST_SUBSCRIBERS)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
builder.appendQueryParameter(QUERY_PARAM_SCREEN_NAME, screenName)
builder.appendQueryParameter(QUERY_PARAM_LIST_NAME, pathSegments[1])
return Pair.create(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
}
}
}
return Pair.create<Intent, Boolean>(null, false)
}
private fun getTwitterIntentUriIntent(uri: Uri, pathSegments: List<String>): Pair<Intent, Boolean> {
if (pathSegments.size < 2) return Pair.create<Intent, Boolean>(null, false)
when (pathSegments[1]) {
"tweet" -> {
val handledIntent = Intent(this, ComposeActivity::class.java)
handledIntent.action = Intent.ACTION_SEND
val text = uri.getQueryParameter("text")
val url = uri.getQueryParameter("url")
handledIntent.putExtra(Intent.EXTRA_TEXT, Utils.getShareStatus(this, text, url))
return Pair.create(handledIntent, true)
}
}
return Pair.create<Intent, Boolean>(null, false)
}
private fun getIUriIntent(uri: Uri, pathSegments: List<String>): Pair<Intent, Boolean> {
return Pair.create<Intent, Boolean>(null, false)
}
private inner class TwitterLinkException(s: String) : Exception(s)
companion object {
val TWITTER_RESERVED_PATHS = arrayOf("about", "account", "accounts", "activity", "all", "announcements", "anywhere", "api_rules", "api_terms", "apirules", "apps", "auth", "badges", "blog", "business", "buttons", "contacts", "devices", "direct_messages", "download", "downloads", "edit_announcements", "faq", "favorites", "find_sources", "find_users", "followers", "following", "friend_request", "friendrequest", "friends", "goodies", "help", "home", "im_account", "inbox", "invitations", "invite", "jobs", "list", "login", "logo", "logout", "me", "mentions", "messages", "mockview", "newtwitter", "notifications", "nudge", "oauth", "phoenix_search", "positions", "privacy", "public_timeline", "related_tweets", "replies", "retweeted_of_mine", "retweets", "retweets_by_others", "rules", "saved_searches", "search", "sent", "settings", "share", "signup", "signin", "similar_to", "statistics", "terms", "tos", "translate", "trends", "tweetbutton", "twttr", "update_discoverability", "users", "welcome", "who_to_follow", "widgets", "zendesk_auth", "media_signup")
val FANFOU_RESERVED_PATHS = arrayOf("home", "privatemsg", "finder", "browse", "search", "settings", "message", "mentions", "favorites", "friends", "followers", "sharer", "photo", "album", "paipai", "q", "userview", "dialogue")
private val AUTHORITY_TWITTER_COM = "twitter.com"
private fun regulateTwitterUri(data: Uri): Uri {
val encodedFragment = data.encodedFragment
if (encodedFragment != null && encodedFragment.startsWith("!/")) {
return regulateTwitterUri(Uri.parse("https://twitter.com" + encodedFragment.substring(1)))
}
val builder = data.buildUpon()
builder.scheme("https")
builder.authority(AUTHORITY_TWITTER_COM)
return builder.build()
}
}
}

View File

@ -28,10 +28,9 @@ import android.view.View
import android.view.ViewGroup
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_MEDIA_PREVIEW_STYLE
import org.mariotaku.twidere.extension.getActionName
import org.mariotaku.twidere.model.Draft
import org.mariotaku.twidere.model.DraftCursorIndices
import org.mariotaku.twidere.model.ParcelableMediaUpdate
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
@ -47,7 +46,11 @@ class DraftsAdapter(context: Context) : SimpleCursorAdapter(context, R.layout.li
private val mediaLoadingHandler: MediaLoadingHandler
private val mediaPreviewStyle: Int
private var mTextSize: Float = 0.toFloat()
var textSize: Float = 0f
set(value) {
field = value
notifyDataSetChanged()
}
private var indices: DraftCursorIndices? = null
init {
@ -57,17 +60,18 @@ class DraftsAdapter(context: Context) : SimpleCursorAdapter(context, R.layout.li
}
override fun bindView(view: View, context: Context, cursor: Cursor) {
val draft = indices?.newObject(cursor) ?: return
val holder = view.tag as DraftViewHolder
val indices = indices!!
val accountKeys = UserKey.arrayOf(cursor.getString(indices.account_keys))
val text = cursor.getString(indices.text)
val mediaUpdates = JsonSerializer.parseArray(cursor.getString(indices.media), ParcelableMediaUpdate::class.java)
val timestamp = cursor.getLong(indices.timestamp)
val actionType: String = cursor.getString(indices.action_type) ?: Draft.Action.UPDATE_STATUS
val actionName = getActionName(context, actionType)
val accountKeys = draft.account_keys
val text = draft.text
val mediaUpdates = draft.media
val timestamp = draft.timestamp
val actionType: String = draft.action_type ?: Draft.Action.UPDATE_STATUS
val actionName = draft.getActionName(context)
holder.media_preview_container.setStyle(mediaPreviewStyle)
when (actionType) {
Draft.Action.UPDATE_STATUS, Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2, Draft.Action.REPLY, Draft.Action.QUOTE -> {
Draft.Action.UPDATE_STATUS, Draft.Action.UPDATE_STATUS_COMPAT_1,
Draft.Action.UPDATE_STATUS_COMPAT_2, Draft.Action.REPLY, Draft.Action.QUOTE -> {
val media = ParcelableMediaUtils.fromMediaUpdates(mediaUpdates)
holder.media_preview_container.visibility = View.VISIBLE
holder.media_preview_container.displayMedia(media, imageLoader, null, -1, null,
@ -82,7 +86,7 @@ class DraftsAdapter(context: Context) : SimpleCursorAdapter(context, R.layout.li
} else {
holder.content.drawEnd()
}
holder.setTextSize(mTextSize)
holder.setTextSize(textSize)
val emptyContent = TextUtils.isEmpty(text)
if (emptyContent) {
holder.text.setText(R.string.empty_content)
@ -108,10 +112,6 @@ class DraftsAdapter(context: Context) : SimpleCursorAdapter(context, R.layout.li
return view
}
fun setTextSize(text_size: Float) {
mTextSize = text_size
}
override fun swapCursor(c: Cursor?): Cursor? {
val old = super.swapCursor(c)
if (c != null) {
@ -124,23 +124,4 @@ class DraftsAdapter(context: Context) : SimpleCursorAdapter(context, R.layout.li
cursor.moveToPosition(position)
return indices!!.newObject(cursor)
}
private fun getActionName(context: Context, actionType: String): String? {
if (TextUtils.isEmpty(actionType)) return context.getString(R.string.update_status)
when (actionType) {
Draft.Action.UPDATE_STATUS, Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2 -> {
return context.getString(R.string.update_status)
}
Draft.Action.REPLY -> {
return context.getString(R.string.reply)
}
Draft.Action.QUOTE -> {
return context.getString(R.string.quote)
}
Draft.Action.SEND_DIRECT_MESSAGE, Draft.Action.SEND_DIRECT_MESSAGE_COMPAT -> {
return context.getString(R.string.send_direct_message)
}
}
return null
}
}

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.extension
import android.content.Context
import android.net.Uri
import android.text.TextUtils
import com.bluelinelabs.logansquare.LoganSquare
import com.nostra13.universalimageloader.utils.IoUtils
import org.apache.james.mime4j.dom.Header
@ -18,6 +19,7 @@ import org.apache.james.mime4j.util.MimeUtil
import org.mariotaku.ktextension.convert
import org.mariotaku.ktextension.toInt
import org.mariotaku.ktextension.toString
import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.model.getMimeType
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.Draft.Action
@ -47,7 +49,9 @@ fun Draft.writeMimeMessageTo(context: Context, st: OutputStream) {
val message = builder.newMessage() as AbstractMessage
message.date = Date(this.timestamp)
message.subject = this.getActionName(context)
message.setFrom(this.account_keys?.map { Mailbox(it.id, it.host) })
message.setTo(message.from)
if (message.header == null) {
message.header = HeaderImpl()
}
@ -97,6 +101,25 @@ fun Draft.readMimeMessageFrom(context: Context, st: InputStream) {
parser.parse(st)
}
fun Draft.getActionName(context: Context): String? {
if (TextUtils.isEmpty(action_type)) return context.getString(R.string.update_status)
when (action_type) {
Draft.Action.UPDATE_STATUS, Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2 -> {
return context.getString(R.string.update_status)
}
Draft.Action.REPLY -> {
return context.getString(R.string.reply)
}
Draft.Action.QUOTE -> {
return context.getString(R.string.quote)
}
Draft.Action.SEND_DIRECT_MESSAGE, Draft.Action.SEND_DIRECT_MESSAGE_COMPAT -> {
return context.getString(R.string.send_direct_message)
}
}
return null
}
private class DraftContentHandler(private val context: Context, private val draft: Draft) : SimpleContentHandler() {
private val processingStack = Stack<SimpleContentHandler>()
private val mediaList: MutableList<ParcelableMediaUpdate> = ArrayList()

View File

@ -73,8 +73,7 @@ import java.util.*
class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemClickListener, MultiChoiceModeListener {
private var adapter: DraftsAdapter? = null
private lateinit var adapter: DraftsAdapter
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.action_multi_select_drafts, menu)
@ -101,7 +100,7 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
for (i in 0 until checked.size()) {
val position = checked.keyAt(i)
if (checked.valueAt(i)) {
list.add(adapter!!.getDraft(position))
list.add(adapter.getDraft(position))
}
}
if (sendDrafts(list)) {
@ -116,7 +115,7 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
for (i in 0 until checked.size()) {
val position = checked.keyAt(i)
if (checked.valueAt(i)) {
drafts.add(adapter!!.getDraft(position))
drafts.add(adapter.getDraft(position))
}
}
task {
@ -157,12 +156,12 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
}
override fun onLoadFinished(loader: Loader<Cursor?>, cursor: Cursor?) {
adapter!!.swapCursor(cursor)
adapter.swapCursor(cursor)
setListShown(true)
}
override fun onLoaderReset(loader: Loader<Cursor?>) {
adapter!!.swapCursor(null)
adapter.swapCursor(null)
}
override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long,
@ -171,7 +170,7 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
}
override fun onItemClick(view: AdapterView<*>, child: View, position: Int, id: Long) {
val item = adapter!!.getDraft(position)
val item = adapter.getDraft(position)
if (TextUtils.isEmpty(item.action_type)) {
editDraft(item)
return
@ -183,14 +182,16 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater!!.inflate(R.layout.fragment_drafts, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_drafts, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
adapter = DraftsAdapter(activity)
adapter!!.setTextSize(preferences.getInt(KEY_TEXT_SIZE, getDefaultTextSize(activity)).toFloat())
adapter = DraftsAdapter(activity).apply {
textSize = preferences.getInt(KEY_TEXT_SIZE, getDefaultTextSize(activity)).toFloat()
}
listView.adapter = adapter
listView.emptyView = emptyView
listView.onItemClickListener = this
@ -212,9 +213,9 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
}
fun setListShown(listShown: Boolean) {
listContainer!!.visibility = if (listShown) View.VISIBLE else View.GONE
progressContainer!!.visibility = if (listShown) View.GONE else View.VISIBLE
emptyView!!.visibility = if (listShown && adapter!!.isEmpty) View.VISIBLE else View.GONE
listContainer.visibility = if (listShown) View.VISIBLE else View.GONE
progressContainer.visibility = if (listShown) View.GONE else View.VISIBLE
emptyView.visibility = if (listShown && adapter.isEmpty) View.VISIBLE else View.GONE
}
private fun editDraft(draft: Draft) {
@ -242,7 +243,7 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
if (item.account_keys?.isEmpty() ?: true || recipientId == null) {
continue@loop
}
val accountId = item.account_keys!![0]
val accountId = item.account_keys?.firstOrNull()
val imageUri = item.media?.firstOrNull()?.uri
twitterWrapper.sendDirectMessageAsync(accountId, recipientId, item.text, imageUri)
}