1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-09 08:18:44 +01:00

migrating to kotlin

This commit is contained in:
Mariotaku Lee 2016-07-04 09:31:17 +08:00
parent c1326955a7
commit d8608d8cbf
36 changed files with 2523 additions and 2797 deletions

View File

@ -164,7 +164,7 @@ dependencies {
compile 'com.github.mariotaku.SQLiteQB:library:0.9.6'
compile 'com.github.mariotaku.ObjectCursor:core:0.9.9'
compile 'com.github.mariotaku:MultiValueSwitch:0.9.6'
compile 'com.github.mariotaku:AbstractTask:0.9.2'
compile 'com.github.mariotaku:AbstractTask:0.9.3'
compile 'com.github.mariotaku.CommonsLibrary:parcel:0.9.8'
compile 'com.github.mariotaku.CommonsLibrary:io:0.9.8'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

View File

@ -171,13 +171,13 @@ public final class MediaViewerActivity extends BaseActivity implements IExtended
CacheDownloadMediaViewerFragment f = (CacheDownloadMediaViewerFragment) object;
final boolean running = f.getLoaderManager().hasRunningLoaders();
final boolean downloaded = f.hasDownloadedData();
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.refresh, !running && !downloaded);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.share, !running && downloaded);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.save, !running && downloaded);
MenuUtils.INSTANCE.setItemAvailability(menu, R.id.refresh, !running && !downloaded);
MenuUtils.INSTANCE.setItemAvailability(menu, R.id.share, !running && downloaded);
MenuUtils.INSTANCE.setItemAvailability(menu, R.id.save, !running && downloaded);
} else {
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.refresh, false);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.share, true);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.save, false);
MenuUtils.INSTANCE.setItemAvailability(menu, R.id.refresh, false);
MenuUtils.INSTANCE.setItemAvailability(menu, R.id.share, true);
MenuUtils.INSTANCE.setItemAvailability(menu, R.id.save, false);
}
return true;
}

View File

@ -37,7 +37,6 @@ import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.Referral
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API
import org.mariotaku.twidere.fragment.CursorActivitiesFragment
import org.mariotaku.twidere.fragment.UserFragment
import org.mariotaku.twidere.model.ParcelableActivity
import org.mariotaku.twidere.model.ParcelableActivityCursorIndices
import org.mariotaku.twidere.model.ParcelableMedia
@ -55,7 +54,10 @@ import java.lang.ref.WeakReference
/**
* Created by mariotaku on 15/1/3.
*/
class ParcelableActivitiesAdapter(context: Context, private val mIsByFriends: Boolean) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context), IActivitiesAdapter<List<ParcelableActivity>> {
class ParcelableActivitiesAdapter(
context: Context,
private val byFriends: Boolean
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context), IActivitiesAdapter<List<ParcelableActivity>> {
private val inflater: LayoutInflater
override val mediaLoadingHandler: MediaLoadingHandler
@ -155,7 +157,7 @@ class ParcelableActivitiesAdapter(context: Context, private val mIsByFriends: Bo
}
protected fun bindTitleSummaryViewHolder(holder: ActivityTitleSummaryViewHolder, position: Int) {
holder.displayActivity(getActivity(position)!!, mIsByFriends)
holder.displayActivity(getActivity(position)!!, byFriends)
}
fun getData(): List<ParcelableActivity>? {

View File

@ -44,6 +44,9 @@ import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_GAP
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_STATUS
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_TITLE_SUMMARY
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.ReadPositionTag
@ -361,7 +364,7 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
if (activity == null) return
val lm = layoutManager ?: return
val view = lm.findViewByPosition(position) ?: return
if (lm.getItemViewType(view) != ParcelableActivitiesAdapter.ITEM_VIEW_TYPE_STATUS) {
if (lm.getItemViewType(view) != ITEM_VIEW_TYPE_STATUS) {
return
}
recyclerView.showContextMenuForChild(view)
@ -397,7 +400,7 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
}
}
}
task.setResultHandler(recyclerView)
task.setCallback(recyclerView)
TaskStarter.execute(task)
bus.register(mStatusesBusCallback)
}
@ -519,7 +522,7 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
val contextMenuInfo = menuInfo as ExtendedRecyclerView.ContextMenuInfo?
val position = contextMenuInfo!!.position
when (adapter!!.getItemViewType(position)) {
ParcelableActivitiesAdapter.ITEM_VIEW_TYPE_STATUS -> {
ITEM_VIEW_TYPE_STATUS -> {
val status = getActivityStatus(position) ?: return
inflater.inflate(R.menu.action_status, menu)
MenuUtils.setupForStatus(context, preferences, menu, status,
@ -535,7 +538,7 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
val position = contextMenuInfo.position
when (adapter!!.getItemViewType(position)) {
ParcelableActivitiesAdapter.ITEM_VIEW_TYPE_STATUS -> {
ITEM_VIEW_TYPE_STATUS -> {
val status = getActivityStatus(position) ?: return false
if (item.itemId == R.id.share) {
val shareIntent = Utils.createStatusShareIntent(activity, status)
@ -552,20 +555,32 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
}
override fun createItemDecoration(context: Context, recyclerView: RecyclerView, layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
val adapter = adapter
val itemDecoration = DividerItemDecoration(context,
(recyclerView.layoutManager as LinearLayoutManager).orientation)
override fun createItemDecoration(context: Context, recyclerView: RecyclerView,
layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
val adapter = adapter!!
val itemDecoration = object : DividerItemDecoration(context,
(recyclerView.layoutManager as LinearLayoutManager).orientation) {
override fun isDividerEnabled(childPos: Int): Boolean {
when (adapter.getItemViewType(childPos)) {
ITEM_VIEW_TYPE_STATUS, ITEM_VIEW_TYPE_TITLE_SUMMARY, ITEM_VIEW_TYPE_GAP -> {
return true
}
else -> {
return false
}
}
}
}
val res = context.resources
if (adapter!!.profileImageEnabled) {
if (adapter.profileImageEnabled) {
val decorPaddingLeft = res.getDimensionPixelSize(R.dimen.element_spacing_normal) * 2 + res.getDimensionPixelSize(R.dimen.icon_size_status_profile_image)
itemDecoration.setPadding { position, rect ->
val itemViewType = adapter.getItemViewType(position)
var nextItemIsStatus = false
if (position < adapter.itemCount - 1) {
nextItemIsStatus = adapter.getItemViewType(position + 1) == ParcelableActivitiesAdapter.ITEM_VIEW_TYPE_STATUS
nextItemIsStatus = adapter.getItemViewType(position + 1) == ITEM_VIEW_TYPE_STATUS
}
if (nextItemIsStatus && itemViewType == ParcelableActivitiesAdapter.ITEM_VIEW_TYPE_STATUS) {
if (nextItemIsStatus && itemViewType == ITEM_VIEW_TYPE_STATUS) {
rect.left = decorPaddingLeft
} else {
rect.left = 0

View File

@ -410,7 +410,7 @@ abstract class AbsStatusesFragment protected constructor() : AbsContentListRecyc
}
}
}
task.setResultHandler(recyclerView)
task.setCallback(recyclerView)
TaskStarter.execute(task)
bus.register(statusesBusCallback)
}

View File

@ -1,919 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.NavigationView;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.ActionMenuView.OnMenuItemClickListener;
import android.support.v7.widget.FixedLinearLayoutManager;
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.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewSwitcher;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.ComposeActivity;
import org.mariotaku.twidere.activity.HomeActivity;
import org.mariotaku.twidere.activity.PlusServiceDashboardActivity;
import org.mariotaku.twidere.activity.QuickSearchBarActivity;
import org.mariotaku.twidere.activity.SettingsActivity;
import org.mariotaku.twidere.annotation.CustomTabType;
import org.mariotaku.twidere.annotation.Referral;
import org.mariotaku.twidere.menu.AccountToggleProvider;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.SupportTabSpec;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.util.ParcelableAccountUtils;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
import org.mariotaku.twidere.util.CompareUtils;
import org.mariotaku.twidere.util.DataStoreUtils;
import org.mariotaku.twidere.util.IntentUtils;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.TransitionUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.content.SupportFragmentReloadCursorObserver;
import org.mariotaku.twidere.view.ShapedImageView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AccountsDashboardFragment extends BaseSupportFragment implements LoaderCallbacks<Cursor>,
OnSharedPreferenceChangeListener, OnClickListener, KeyboardShortcutCallback,
NavigationView.OnNavigationItemSelectedListener {
private final Rect mSystemWindowsInsets = new Rect();
private ContentResolver mResolver;
private AccountSelectorAdapter mAccountsAdapter;
private NavigationView mNavigationView;
private View mAccountSelectorView;
private RecyclerView mAccountsSelector;
private ViewSwitcher mAccountProfileBannerView;
private ImageView mFloatingProfileImageSnapshotView;
private ShapedImageView mAccountProfileImageView;
private TextView mAccountProfileNameView, mAccountProfileScreenNameView;
private ActionMenuView mAccountsToggleMenu;
private View mAccountProfileContainer;
private View mNoAccountContainer;
private AccountToggleProvider mAccountActionProvider;
private final SupportFragmentReloadCursorObserver mReloadContentObserver = new SupportFragmentReloadCursorObserver(
this, 0, this) {
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
final ContentResolver cr = getContentResolver();
if (cr == null) return;
final Cursor c = cr.query(Accounts.CONTENT_URI, Accounts.COLUMNS, null, null, Accounts.SORT_POSITION);
try {
updateAccountProviderData(c);
} finally {
Utils.closeSilently(c);
}
super.onChange(selfChange, uri);
}
};
private boolean mSwitchAccountAnimationPlaying;
private boolean mUseStarsForLikes;
private boolean mLoaderInitialized;
@NonNull
public UserKey[] getActivatedAccountIds() {
if (mAccountActionProvider != null) {
return mAccountActionProvider.getActivatedAccountIds();
}
return DataStoreUtils.getActivatedAccountKeys(getActivity());
}
@Override
public boolean handleKeyboardShortcutSingle(@NonNull final KeyboardShortcutsHandler handler,
final int keyCode, @NonNull final KeyEvent event, int metaState) {
return false;
}
@Override
public boolean isKeyboardShortcutHandled(@NonNull KeyboardShortcutsHandler handler, int keyCode, @NonNull KeyEvent event, int metaState) {
final String action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState);
return ACTION_NAVIGATION_PREVIOUS.equals(action) || ACTION_NAVIGATION_NEXT.equals(action);
}
@Override
public boolean handleKeyboardShortcutRepeat(@NonNull final KeyboardShortcutsHandler handler,
final int keyCode, final int repeatCount,
@NonNull final KeyEvent event, int metaState) {
final String action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState);
if (action == null) return false;
final int offset;
switch (action) {
case ACTION_NAVIGATION_PREVIOUS: {
offset = -1;
break;
}
case ACTION_NAVIGATION_NEXT: {
offset = 1;
break;
}
default: {
return false;
}
}
// final int selectedItem = mNavigationView.getSelectedItemPosition();
// final int count = mNavigationView.getCount();
// int resultPosition;
// if (!mNavigationView.isFocused() || selectedItem == ListView.INVALID_POSITION) {
// resultPosition = firstVisiblePosition;
// } else {
// resultPosition = selectedItem + offset;
// while (resultPosition >= 0 && resultPosition < count && !mAdapter.isEnabled(resultPosition)) {
// resultPosition += offset;
// }
// }
// final View focusedChild = mNavigationView.getFocusedChild();
// if (focusedChild == null) {
// mNavigationView.requestChildFocus(mNavigationView.getChildAt(0), null);
// }
// if (resultPosition >= 0 && resultPosition < count) {
// mNavigationView.setSelection(resultPosition);
// }
return true;
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_SETTINGS: {
if (data == null) return;
final FragmentActivity activity = getActivity();
if (data.getBooleanExtra(EXTRA_CHANGED, false)) {
activity.recreate();
}
return;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onResume() {
super.onResume();
updateDefaultAccountState();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.profileContainer: {
final ParcelableAccount account = mAccountsAdapter.getSelectedAccount();
if (account == null) return;
final FragmentActivity activity = getActivity();
if (account.account_user != null) {
IntentUtils.openUserProfile(activity, account.account_user, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE);
} else {
IntentUtils.openUserProfile(activity, account.account_key,
account.account_key, account.screen_name, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE);
}
break;
}
}
}
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
return new CursorLoader(getActivity(), Accounts.CONTENT_URI, Accounts.COLUMNS, null, null, Accounts.SORT_POSITION);
}
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
updateAccountProviderData(data);
}
private void updateAccountProviderData(@Nullable final Cursor cursor) {
if (cursor == null) return;
final Menu menu = mAccountsToggleMenu.getMenu();
mAccountActionProvider = (AccountToggleProvider) MenuItemCompat.getActionProvider(menu.findItem(R.id.select_account));
final ParcelableAccount[] accounts = ParcelableAccountUtils.getAccounts(cursor);
if (accounts.length > 0) {
mNoAccountContainer.setVisibility(View.GONE);
mAccountProfileContainer.setVisibility(View.VISIBLE);
} else {
mNoAccountContainer.setVisibility(View.VISIBLE);
mAccountProfileContainer.setVisibility(View.INVISIBLE);
}
UserKey defaultId = null;
for (ParcelableAccount account : accounts) {
if (account.is_activated) {
defaultId = account.account_key;
break;
}
}
mUseStarsForLikes = preferences.getBoolean(KEY_I_WANT_MY_STARS_BACK);
mAccountsAdapter.setAccounts(accounts);
UserKey accountKey = UserKey.valueOf(preferences.getString(KEY_DEFAULT_ACCOUNT_KEY, null));
if (accountKey == null) {
accountKey = defaultId;
}
ParcelableAccount selectedAccount = null;
for (ParcelableAccount account : accounts) {
if (account.account_key.maybeEquals(accountKey)) {
selectedAccount = account;
break;
}
}
mAccountsAdapter.setSelectedAccount(selectedAccount);
if (mAccountActionProvider != null) {
mAccountActionProvider.setExclusive(false);
mAccountActionProvider.setAccounts(accounts);
}
updateAccountActions();
ParcelableAccount currentAccount = mAccountsAdapter.getSelectedAccount();
if (currentAccount != null) {
displayAccountBanner(currentAccount);
displayCurrentAccount(null);
}
updateDefaultAccountState();
}
@Override
public void onLoaderReset(final Loader<Cursor> loader) {
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
if (KEY_DEFAULT_ACCOUNT_KEY.equals(key)) {
updateDefaultAccountState();
}
}
@Override
protected void fitSystemWindows(Rect insets) {
mSystemWindowsInsets.set(insets);
updateSystemWindowsInsets();
}
private void updateSystemWindowsInsets() {
if (mAccountProfileContainer == null) return;
final Rect insets = mSystemWindowsInsets;
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mResolver = getContentResolver();
final View view = getView();
assert view != null;
final Context context = view.getContext();
final LayoutInflater inflater = getLayoutInflater(savedInstanceState);
mAccountsAdapter = new AccountSelectorAdapter(inflater, this);
final LinearLayoutManager layoutManager = new FixedLinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
layoutManager.setStackFromEnd(true);
mAccountsSelector.setLayoutManager(layoutManager);
mAccountsSelector.setAdapter(mAccountsAdapter);
mAccountsSelector.setItemAnimator(null);
final SupportMenuInflater menuInflater = new SupportMenuInflater(context);
menuInflater.inflate(R.menu.action_dashboard_timeline_toggle, mAccountsToggleMenu.getMenu());
mAccountsToggleMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getGroupId() != AccountToggleProvider.MENU_GROUP) {
switch (item.getItemId()) {
case R.id.compose: {
final ParcelableAccount account = mAccountsAdapter.getSelectedAccount();
if (account == null) return true;
final Intent composeIntent = new Intent(INTENT_ACTION_COMPOSE);
composeIntent.setClass(getActivity(), ComposeActivity.class);
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, account.account_key);
startActivity(composeIntent);
return true;
}
}
return false;
}
final ParcelableAccount[] accounts = mAccountActionProvider.getAccounts();
final ParcelableAccount account = accounts[item.getOrder()];
final ContentValues values = new ContentValues();
final boolean newActivated = !account.is_activated;
mAccountActionProvider.setAccountActivated(account.account_key, newActivated);
values.put(Accounts.IS_ACTIVATED, newActivated);
final String where = Expression.equalsArgs(Accounts.ACCOUNT_KEY).getSQL();
final String[] whereArgs = {account.account_key.toString()};
mResolver.update(Accounts.CONTENT_URI, values, where, whereArgs);
return true;
}
});
mAccountProfileContainer.setOnClickListener(this);
mAccountProfileBannerView.setInAnimation(getContext(), android.R.anim.fade_in);
mAccountProfileBannerView.setOutAnimation(getContext(), android.R.anim.fade_out);
mAccountProfileBannerView.setFactory(new ViewSwitcher.ViewFactory() {
@Override
public View makeView() {
return inflater.inflate(R.layout.layout_account_dashboard_profile_image,
mAccountProfileBannerView, false);
}
});
mNavigationView.setNavigationItemSelectedListener(this);
preferences.registerOnSharedPreferenceChangeListener(this);
loadAccounts();
updateSystemWindowsInsets();
}
public void loadAccounts() {
if (!mLoaderInitialized) {
mLoaderInitialized = true;
getLoaderManager().initLoader(0, null, this);
} else {
getLoaderManager().restartLoader(0, null, this);
}
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_accounts_dashboard, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavigationView = (NavigationView) view.findViewById(R.id.navigation_view);
mAccountSelectorView = mNavigationView.getHeaderView(0);
mAccountsSelector = (RecyclerView) mAccountSelectorView.findViewById(R.id.otherAccountsList);
mAccountProfileContainer = mAccountSelectorView.findViewById(R.id.profileContainer);
mNoAccountContainer = mAccountSelectorView.findViewById(R.id.noAccountContainer);
mAccountProfileImageView = (ShapedImageView) mAccountSelectorView.findViewById(R.id.profileImage);
mAccountProfileBannerView = (ViewSwitcher) mAccountSelectorView.findViewById(R.id.accountProfileBanner);
mFloatingProfileImageSnapshotView = (ImageView) mAccountSelectorView.findViewById(R.id.floatingProfileImageSnapshot);
mAccountProfileNameView = (TextView) mAccountSelectorView.findViewById(R.id.name);
mAccountProfileScreenNameView = (TextView) mAccountSelectorView.findViewById(R.id.screenName);
mAccountsToggleMenu = (ActionMenuView) mAccountSelectorView.findViewById(R.id.accountDashboardMenu);
}
@Override
public void onStart() {
super.onStart();
final ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(Accounts.CONTENT_URI, true, mReloadContentObserver);
getLoaderManager().restartLoader(0, null, this);
}
@Override
public void onStop() {
final ContentResolver resolver = getContentResolver();
resolver.unregisterContentObserver(mReloadContentObserver);
super.onStop();
}
void updateAccountActions() {
final HomeActivity activity = (HomeActivity) getActivity();
if (activity == null) return;
final List<SupportTabSpec> tabs = activity.getTabs();
final ParcelableAccount account = mAccountsAdapter.getSelectedAccount();
if (account == null) return;
boolean hasDmTab = false, hasInteractionsTab = false;
for (SupportTabSpec tab : tabs) {
if (tab.type == null) continue;
switch (tab.type) {
case CustomTabType.DIRECT_MESSAGES: {
if (!hasDmTab) {
hasDmTab = hasAccountInTab(tab, account.account_key, account.is_activated);
}
break;
}
case CustomTabType.NOTIFICATIONS_TIMELINE: {
if (!hasInteractionsTab) {
hasInteractionsTab = hasAccountInTab(tab, account.account_key, account.is_activated);
}
break;
}
}
}
final Menu menu = mNavigationView.getMenu();
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.interactions, !hasInteractionsTab);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.messages, !hasDmTab);
if (mUseStarsForLikes) {
MenuUtils.Companion.setMenuItemTitle(menu, R.id.favorites, R.string.favorites);
MenuUtils.Companion.setMenuItemIcon(menu, R.id.favorites, R.drawable.ic_action_star);
} else {
MenuUtils.Companion.setMenuItemTitle(menu, R.id.favorites, R.string.likes);
MenuUtils.Companion.setMenuItemIcon(menu, R.id.favorites, R.drawable.ic_action_heart);
}
boolean hasLists = false, hasGroups = false, hasPublicTimeline = false;
switch (ParcelableAccountUtils.getAccountType(account)) {
case ParcelableAccount.Type.TWITTER: {
hasLists = true;
break;
}
case ParcelableAccount.Type.STATUSNET: {
hasGroups = true;
break;
}
case ParcelableAccount.Type.FANFOU: {
hasPublicTimeline = true;
break;
}
}
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.groups, hasGroups);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.lists, hasLists);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.public_timeline, hasPublicTimeline);
}
private boolean hasAccountInTab(SupportTabSpec tab, UserKey accountId, boolean isActivated) {
if (tab.args == null) return false;
final UserKey[] accountKeys = Utils.getAccountKeys(getContext(), tab.args);
if (accountKeys == null) return isActivated;
return ArrayUtils.contains(accountKeys, accountId);
}
private void closeAccountsDrawer() {
final Activity activity = getActivity();
if (activity instanceof HomeActivity) {
((HomeActivity) activity).closeAccountsDrawer();
}
}
private void getLocationOnScreen(View view, RectF rectF) {
final int[] location = new int[2];
view.getLocationOnScreen(location);
rectF.set(location[0], location[1], location[0] + view.getWidth(), location[1] + view.getHeight());
}
private void onAccountSelected(AccountProfileImageViewHolder holder, @NonNull final ParcelableAccount account) {
if (mSwitchAccountAnimationPlaying) return;
final ImageView snapshotView = mFloatingProfileImageSnapshotView;
final ShapedImageView profileImageView = mAccountProfileImageView;
final ShapedImageView clickedImageView = holder.getIconView();
// Reset snapshot view position
snapshotView.setPivotX(0);
snapshotView.setPivotY(0);
snapshotView.setTranslationX(0);
snapshotView.setTranslationY(0);
final Matrix matrix = new Matrix();
final RectF sourceBounds = new RectF(), destBounds = new RectF(), snapshotBounds = new RectF();
getLocationOnScreen(clickedImageView, sourceBounds);
getLocationOnScreen(profileImageView, destBounds);
getLocationOnScreen(snapshotView, snapshotBounds);
final float finalScale = destBounds.width() / sourceBounds.width();
final Bitmap snapshotBitmap = TransitionUtils.createViewBitmap(clickedImageView, matrix,
new RectF(0, 0, sourceBounds.width(), sourceBounds.height()));
final ViewGroup.LayoutParams lp = snapshotView.getLayoutParams();
lp.width = clickedImageView.getWidth();
lp.height = clickedImageView.getHeight();
snapshotView.setLayoutParams(lp);
// Copied from MaterialNavigationDrawer: https://github.com/madcyph3r/AdvancedMaterialDrawer/
AnimatorSet set = new AnimatorSet();
set.play(ObjectAnimator.ofFloat(snapshotView, View.TRANSLATION_X,
sourceBounds.left - snapshotBounds.left, destBounds.left - snapshotBounds.left))
.with(ObjectAnimator.ofFloat(snapshotView, View.TRANSLATION_Y,
sourceBounds.top - snapshotBounds.top, destBounds.top - snapshotBounds.top))
.with(ObjectAnimator.ofFloat(snapshotView, View.SCALE_X, 1, finalScale))
.with(ObjectAnimator.ofFloat(snapshotView, View.SCALE_Y, 1, finalScale))
.with(ObjectAnimator.ofFloat(profileImageView, View.ALPHA, 1, 0))
.with(ObjectAnimator.ofFloat(clickedImageView, View.SCALE_X, 0, 1))
.with(ObjectAnimator.ofFloat(clickedImageView, View.SCALE_Y, 0, 1));
final long animationTransition = 400;
set.setDuration(animationTransition);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListener() {
private Drawable clickedDrawable;
private int[] clickedColors;
@Override
public void onAnimationStart(Animator animation) {
snapshotView.setVisibility(View.VISIBLE);
snapshotView.setImageBitmap(snapshotBitmap);
final Drawable profileDrawable = profileImageView.getDrawable();
clickedDrawable = clickedImageView.getDrawable();
clickedColors = clickedImageView.getBorderColors();
final ParcelableAccount oldSelectedAccount = mAccountsAdapter.getSelectedAccount();
if (oldSelectedAccount == null) return;
mediaLoader.displayDashboardProfileImage(clickedImageView,
oldSelectedAccount, profileDrawable);
clickedImageView.setBorderColors(profileImageView.getBorderColors());
displayAccountBanner(account);
mSwitchAccountAnimationPlaying = true;
}
@Override
public void onAnimationEnd(Animator animation) {
finishAnimation();
}
@Override
public void onAnimationCancel(Animator animation) {
finishAnimation();
}
@Override
public void onAnimationRepeat(Animator animation) {
}
private void finishAnimation() {
final Editor editor = preferences.edit();
editor.putString(KEY_DEFAULT_ACCOUNT_KEY, account.account_key.toString());
editor.apply();
mAccountsAdapter.setSelectedAccount(account);
updateAccountActions();
displayCurrentAccount(clickedDrawable);
snapshotView.setVisibility(View.INVISIBLE);
snapshotView.setImageDrawable(null);
profileImageView.setImageDrawable(clickedDrawable);
profileImageView.setBorderColors(clickedColors);
profileImageView.setAlpha(1f);
clickedImageView.setScaleX(1);
clickedImageView.setScaleY(1);
clickedImageView.setAlpha(1f);
mSwitchAccountAnimationPlaying = false;
}
});
set.start();
}
protected void displayAccountBanner(@NonNull ParcelableAccount account) {
final int bannerWidth = mAccountProfileBannerView.getWidth();
final Resources res = getResources();
final int defWidth = res.getDisplayMetrics().widthPixels;
final int width = bannerWidth > 0 ? bannerWidth : defWidth;
final ImageView bannerView = (ImageView) mAccountProfileBannerView.getNextView();
if (bannerView.getDrawable() == null || !CompareUtils.objectEquals(account, bannerView.getTag())) {
mediaLoader.displayProfileBanner(bannerView, account, width);
bannerView.setTag(account);
} else {
mediaLoader.cancelDisplayTask(bannerView);
}
}
private void displayCurrentAccount(Drawable profileImageSnapshot) {
final ParcelableAccount account = mAccountsAdapter.getSelectedAccount();
if (account == null) {
return;
}
mAccountProfileNameView.setText(account.name);
mAccountProfileScreenNameView.setText(String.format("@%s", account.screen_name));
mediaLoader.displayDashboardProfileImage(mAccountProfileImageView, account,
profileImageSnapshot);
mAccountProfileImageView.setBorderColors(account.color);
mAccountProfileBannerView.showNext();
}
private void updateDefaultAccountState() {
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
final ParcelableAccount account = mAccountsAdapter.getSelectedAccount();
if (account == null) return false;
switch (item.getItemId()) {
case R.id.search: {
final Intent intent = new Intent(getActivity(), QuickSearchBarActivity.class);
intent.putExtra(EXTRA_ACCOUNT_KEY, account.account_key);
startActivity(intent);
closeAccountsDrawer();
break;
}
case R.id.compose: {
final Intent composeIntent = new Intent(INTENT_ACTION_COMPOSE);
composeIntent.setClass(getActivity(), ComposeActivity.class);
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, account.account_key);
startActivity(composeIntent);
break;
}
case R.id.favorites: {
IntentUtils.openUserFavorites(getActivity(), account.account_key,
account.account_key, account.screen_name);
break;
}
case R.id.lists: {
IntentUtils.openUserLists(getActivity(), account.account_key,
account.account_key, account.screen_name);
break;
}
case R.id.groups: {
IntentUtils.openUserGroups(getActivity(), account.account_key,
account.account_key, account.screen_name);
break;
}
case R.id.public_timeline: {
IntentUtils.openPublicTimeline(getActivity(), account.account_key);
break;
}
case R.id.messages: {
IntentUtils.openDirectMessages(getActivity(), account.account_key);
break;
}
case R.id.interactions: {
IntentUtils.openInteractions(getActivity(), account.account_key);
break;
}
case R.id.edit: {
IntentUtils.openProfileEditor(getActivity(), account.account_key);
break;
}
case R.id.accounts: {
IntentUtils.openAccountsManager(getActivity());
closeAccountsDrawer();
break;
}
case R.id.drafts: {
IntentUtils.openDrafts(getActivity());
closeAccountsDrawer();
break;
}
case R.id.filters: {
IntentUtils.openFilters(getActivity());
closeAccountsDrawer();
break;
}
case R.id.plus_service: {
final Intent intent = new Intent(getActivity(), PlusServiceDashboardActivity.class);
startActivity(intent);
closeAccountsDrawer();
break;
}
case R.id.settings: {
final Intent intent = new Intent(getActivity(), SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityForResult(intent, REQUEST_SETTINGS);
closeAccountsDrawer();
break;
}
}
return false;
}
public void setStatusBarHeight(int height) {
final int top = Utils.getInsetsTopWithoutActionBarHeight(getActivity(), height);
mAccountProfileContainer.setPadding(0, top, 0, 0);
}
public RecyclerView getAccountsSelector() {
return mAccountsSelector;
}
static class AccountProfileImageViewHolder extends ViewHolder implements OnClickListener {
private final AccountSelectorAdapter adapter;
private final ShapedImageView icon;
public AccountProfileImageViewHolder(AccountSelectorAdapter adapter, View itemView) {
super(itemView);
this.adapter = adapter;
itemView.setOnClickListener(this);
icon = (ShapedImageView) itemView.findViewById(android.R.id.icon);
}
public ShapedImageView getIconView() {
return icon;
}
@Override
public void onClick(View v) {
adapter.dispatchItemSelected(this);
}
}
private static class AccountSelectorAdapter extends Adapter<AccountProfileImageViewHolder> {
private final LayoutInflater mInflater;
private final MediaLoaderWrapper mImageLoader;
private final AccountsDashboardFragment mFragment;
private ParcelableAccount[] mInternalAccounts;
AccountSelectorAdapter(LayoutInflater inflater, AccountsDashboardFragment fragment) {
mInflater = inflater;
mImageLoader = fragment.mediaLoader;
mFragment = fragment;
setHasStableIds(true);
}
private static int indexOfAccount(List<ParcelableAccount> accounts, UserKey accountId) {
for (int i = 0, j = accounts.size(); i < j; i++) {
if (accounts.get(i).account_key.equals(accountId)) return i;
}
return -1;
}
public ParcelableAccount getAdapterAccount(int adapterPosition) {
if (mInternalAccounts == null || mInternalAccounts.length < 1) {
return null;
}
return mInternalAccounts[adapterPosition + 1];
}
@Nullable
public ParcelableAccount getSelectedAccount() {
if (mInternalAccounts == null || mInternalAccounts.length == 0) {
return null;
}
return mInternalAccounts[0];
}
@Nullable
public UserKey getSelectedAccountKey() {
final ParcelableAccount selectedAccount = getSelectedAccount();
if (selectedAccount == null) return null;
return selectedAccount.account_key;
}
public void setSelectedAccount(@Nullable ParcelableAccount account) {
final ParcelableAccount selectedAccount = getSelectedAccount();
if (selectedAccount == null || account == null) return;
swap(account, selectedAccount);
}
@Override
public AccountProfileImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = mInflater.inflate(R.layout.adapter_item_dashboard_account, parent, false);
return new AccountProfileImageViewHolder(this, view);
}
@Override
public void onBindViewHolder(AccountProfileImageViewHolder holder, int position) {
final ParcelableAccount account = getAdapterAccount(position);
mImageLoader.displayDashboardProfileImage(holder.icon, account, null);
holder.icon.setBorderColor(account.color);
}
@Override
public long getItemId(int position) {
return getAdapterAccount(position).hashCode();
}
@Override
public int getItemCount() {
if (mInternalAccounts == null || mInternalAccounts.length == 0) return 0;
return mInternalAccounts.length - 1;
}
public void setAccounts(ParcelableAccount[] accounts) {
if (accounts != null) {
final ParcelableAccount[] previousAccounts = mInternalAccounts;
mInternalAccounts = new ParcelableAccount[accounts.length];
int tempIdx = 0;
final List<ParcelableAccount> tempList = new ArrayList<>();
Collections.addAll(tempList, accounts);
if (previousAccounts != null) {
for (ParcelableAccount previousAccount : previousAccounts) {
final int idx = indexOfAccount(tempList, previousAccount.account_key);
if (idx >= 0) {
mInternalAccounts[tempIdx++] = tempList.remove(idx);
}
}
}
for (ParcelableAccount account : tempList) {
mInternalAccounts[tempIdx++] = account;
}
} else {
mInternalAccounts = null;
}
notifyDataSetChanged();
}
private void dispatchItemSelected(AccountProfileImageViewHolder holder) {
mFragment.onAccountSelected(holder, getAdapterAccount(holder.getAdapterPosition()));
}
public ParcelableAccount[] getAccounts() {
return mInternalAccounts;
}
private void swap(@NonNull ParcelableAccount from, @NonNull ParcelableAccount to) {
int fromIdx = -1, toIdx = -1;
for (int i = 0, j = mInternalAccounts.length; i < j; i++) {
final ParcelableAccount account = mInternalAccounts[i];
if (from.id == account.id) {
fromIdx = i;
}
if (to.id == account.id) {
toIdx = i;
}
}
if (fromIdx < 0 || toIdx < 0) return;
final ParcelableAccount temp = mInternalAccounts[toIdx];
mInternalAccounts[toIdx] = mInternalAccounts[fromIdx];
mInternalAccounts[fromIdx] = temp;
notifyDataSetChanged();
}
}
public static class OptionItem {
private final int name, icon, id;
OptionItem(final int name, final int icon, final int id) {
this.name = name;
this.icon = icon;
this.id = id;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof OptionItem)) return false;
final OptionItem other = (OptionItem) obj;
if (icon != other.icon) return false;
if (id != other.id) return false;
return name == other.name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + icon;
result = prime * result + id;
result = prime * result + name;
return result;
}
@Override
public String toString() {
return "AccountOption{name=" + name + ", icon=" + icon + ", id=" + id + "}";
}
}
}

View File

@ -0,0 +1,769 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.animation.Animator
import android.animation.Animator.AnimatorListener
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.database.Cursor
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.support.design.widget.NavigationView
import android.support.v4.app.LoaderManager.LoaderCallbacks
import android.support.v4.content.CursorLoader
import android.support.v4.content.Loader
import android.support.v4.view.MenuItemCompat
import android.support.v7.view.SupportMenuInflater
import android.support.v7.widget.ActionMenuView
import android.support.v7.widget.ActionMenuView.OnMenuItemClickListener
import android.support.v7.widget.FixedLinearLayoutManager
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.*
import android.view.View.OnClickListener
import android.view.animation.DecelerateInterpolator
import android.widget.ImageView
import android.widget.TextView
import android.widget.ViewSwitcher
import org.apache.commons.lang3.ArrayUtils
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.ktextension.setMenuItemIcon
import org.mariotaku.ktextension.setMenuItemTitle
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.activity.*
import org.mariotaku.twidere.annotation.CustomTabType
import org.mariotaku.twidere.annotation.Referral
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
import org.mariotaku.twidere.menu.AccountToggleProvider
import org.mariotaku.twidere.model.ParcelableAccount
import org.mariotaku.twidere.model.SupportTabSpec
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.ParcelableAccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback
import org.mariotaku.twidere.util.content.SupportFragmentReloadCursorObserver
import org.mariotaku.twidere.view.ShapedImageView
import java.util.*
class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Cursor>, OnSharedPreferenceChangeListener, OnClickListener, KeyboardShortcutCallback, NavigationView.OnNavigationItemSelectedListener {
private val mSystemWindowsInsets = Rect()
private var mResolver: ContentResolver? = null
private var mAccountsAdapter: AccountSelectorAdapter? = null
private var mNavigationView: NavigationView? = null
private var mAccountSelectorView: View? = null
var accountsSelector: RecyclerView? = null
private set
private var mAccountProfileBannerView: ViewSwitcher? = null
private var mFloatingProfileImageSnapshotView: ImageView? = null
private var mAccountProfileImageView: ShapedImageView? = null
private var mAccountProfileNameView: TextView? = null
private var mAccountProfileScreenNameView: TextView? = null
private var mAccountsToggleMenu: ActionMenuView? = null
private var mAccountProfileContainer: View? = null
private var mNoAccountContainer: View? = null
private var mAccountActionProvider: AccountToggleProvider? = null
private val mReloadContentObserver = object : SupportFragmentReloadCursorObserver(
this, 0, this) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
val cr = contentResolver
val c = cr.query(Accounts.CONTENT_URI, Accounts.COLUMNS, null, null, Accounts.SORT_POSITION)
try {
updateAccountProviderData(c)
} finally {
Utils.closeSilently(c)
}
super.onChange(selfChange, uri)
}
}
private var mSwitchAccountAnimationPlaying: Boolean = false
private var mUseStarsForLikes: Boolean = false
private var mLoaderInitialized: Boolean = false
val activatedAccountIds: Array<UserKey>
get() {
if (mAccountActionProvider != null) {
return mAccountActionProvider!!.activatedAccountIds
}
return DataStoreUtils.getActivatedAccountKeys(activity)
}
override fun handleKeyboardShortcutSingle(handler: KeyboardShortcutsHandler,
keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
return false
}
override fun isKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
val action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState)
return ACTION_NAVIGATION_PREVIOUS == action || ACTION_NAVIGATION_NEXT == action
}
override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler,
keyCode: Int, repeatCount: Int,
event: KeyEvent, metaState: Int): Boolean {
val action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState) ?: return false
val offset: Int
when (action) {
ACTION_NAVIGATION_PREVIOUS -> {
offset = -1
}
ACTION_NAVIGATION_NEXT -> {
offset = 1
}
else -> {
return false
}
}
// final int selectedItem = mNavigationView.getSelectedItemPosition();
// final int count = mNavigationView.getCount();
// int resultPosition;
// if (!mNavigationView.isFocused() || selectedItem == ListView.INVALID_POSITION) {
// resultPosition = firstVisiblePosition;
// } else {
// resultPosition = selectedItem + offset;
// while (resultPosition >= 0 && resultPosition < count && !mAdapter.isEnabled(resultPosition)) {
// resultPosition += offset;
// }
// }
// final View focusedChild = mNavigationView.getFocusedChild();
// if (focusedChild == null) {
// mNavigationView.requestChildFocus(mNavigationView.getChildAt(0), null);
// }
// if (resultPosition >= 0 && resultPosition < count) {
// mNavigationView.setSelection(resultPosition);
// }
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_SETTINGS -> {
if (data == null) return
val activity = activity
if (data.getBooleanExtra(EXTRA_CHANGED, false)) {
activity.recreate()
}
return
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onResume() {
super.onResume()
updateDefaultAccountState()
}
override fun onClick(v: View) {
when (v.id) {
R.id.profileContainer -> {
val account = mAccountsAdapter!!.selectedAccount ?: return
val activity = activity
if (account.account_user != null) {
IntentUtils.openUserProfile(activity, account.account_user!!, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE)
} else {
IntentUtils.openUserProfile(activity, account.account_key,
account.account_key, account.screen_name, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE)
}
}
}
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?> {
return CursorLoader(activity, Accounts.CONTENT_URI, Accounts.COLUMNS, null, null, Accounts.SORT_POSITION)
}
override fun onLoadFinished(loader: Loader<Cursor?>, data: Cursor?) {
updateAccountProviderData(data)
}
private fun updateAccountProviderData(cursor: Cursor?) {
if (cursor == null) return
val menu = mAccountsToggleMenu!!.menu
mAccountActionProvider = MenuItemCompat.getActionProvider(menu.findItem(R.id.select_account)) as AccountToggleProvider
val accounts = ParcelableAccountUtils.getAccounts(cursor)
if (accounts.size > 0) {
mNoAccountContainer!!.visibility = View.GONE
mAccountProfileContainer!!.visibility = View.VISIBLE
} else {
mNoAccountContainer!!.visibility = View.VISIBLE
mAccountProfileContainer!!.visibility = View.INVISIBLE
}
var defaultId: UserKey? = null
for (account in accounts) {
if (account.is_activated) {
defaultId = account.account_key
break
}
}
mUseStarsForLikes = preferences.getBoolean(KEY_I_WANT_MY_STARS_BACK)
mAccountsAdapter!!.accounts = accounts
var accountKey = UserKey.valueOf(preferences.getString(KEY_DEFAULT_ACCOUNT_KEY, null))
if (accountKey == null) {
accountKey = defaultId
}
var selectedAccount: ParcelableAccount? = null
for (account in accounts) {
if (account.account_key.maybeEquals(accountKey)) {
selectedAccount = account
break
}
}
mAccountsAdapter!!.selectedAccount = selectedAccount
if (mAccountActionProvider != null) {
mAccountActionProvider!!.isExclusive = false
mAccountActionProvider!!.accounts = accounts
}
updateAccountActions()
val currentAccount = mAccountsAdapter!!.selectedAccount
if (currentAccount != null) {
displayAccountBanner(currentAccount)
displayCurrentAccount(null)
}
updateDefaultAccountState()
}
override fun onLoaderReset(loader: Loader<Cursor?>) {
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (KEY_DEFAULT_ACCOUNT_KEY == key) {
updateDefaultAccountState()
}
}
override fun fitSystemWindows(insets: Rect) {
mSystemWindowsInsets.set(insets)
updateSystemWindowsInsets()
}
private fun updateSystemWindowsInsets() {
if (mAccountProfileContainer == null) return
val insets = mSystemWindowsInsets
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mResolver = contentResolver
val view = view!!
val context = view.context
val inflater = getLayoutInflater(savedInstanceState)
mAccountsAdapter = AccountSelectorAdapter(inflater, this)
val layoutManager = FixedLinearLayoutManager(context)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
layoutManager.stackFromEnd = true
accountsSelector!!.layoutManager = layoutManager
accountsSelector!!.adapter = mAccountsAdapter
accountsSelector!!.itemAnimator = null
val menuInflater = SupportMenuInflater(context)
menuInflater.inflate(R.menu.action_dashboard_timeline_toggle, mAccountsToggleMenu!!.menu)
mAccountsToggleMenu!!.setOnMenuItemClickListener(OnMenuItemClickListener { item ->
if (item.groupId != AccountToggleProvider.MENU_GROUP) {
when (item.itemId) {
R.id.compose -> {
val account = mAccountsAdapter!!.selectedAccount ?: return@OnMenuItemClickListener true
val composeIntent = Intent(INTENT_ACTION_COMPOSE)
composeIntent.setClass(activity, ComposeActivity::class.java)
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, account.account_key)
startActivity(composeIntent)
return@OnMenuItemClickListener true
}
}
return@OnMenuItemClickListener false
}
val accounts = mAccountActionProvider!!.accounts
val account = accounts[item.order]
val values = ContentValues()
val newActivated = !account.is_activated
mAccountActionProvider!!.setAccountActivated(account.account_key, newActivated)
values.put(Accounts.IS_ACTIVATED, newActivated)
val where = Expression.equalsArgs(Accounts.ACCOUNT_KEY).sql
val whereArgs = arrayOf(account.account_key.toString())
mResolver!!.update(Accounts.CONTENT_URI, values, where, whereArgs)
true
})
mAccountProfileContainer!!.setOnClickListener(this)
mAccountProfileBannerView!!.setInAnimation(getContext(), android.R.anim.fade_in)
mAccountProfileBannerView!!.setOutAnimation(getContext(), android.R.anim.fade_out)
mAccountProfileBannerView!!.setFactory {
inflater.inflate(R.layout.layout_account_dashboard_profile_image,
mAccountProfileBannerView, false)
}
mNavigationView!!.setNavigationItemSelectedListener(this)
preferences.registerOnSharedPreferenceChangeListener(this)
loadAccounts()
updateSystemWindowsInsets()
}
fun loadAccounts() {
if (!mLoaderInitialized) {
mLoaderInitialized = true
loaderManager.initLoader(0, null, this)
} else {
loaderManager.restartLoader(0, null, this)
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater!!.inflate(R.layout.fragment_accounts_dashboard, container, false)
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mNavigationView = view!!.findViewById(R.id.navigation_view) as NavigationView
mAccountSelectorView = mNavigationView!!.getHeaderView(0)
accountsSelector = mAccountSelectorView!!.findViewById(R.id.otherAccountsList) as RecyclerView
mAccountProfileContainer = mAccountSelectorView!!.findViewById(R.id.profileContainer)
mNoAccountContainer = mAccountSelectorView!!.findViewById(R.id.noAccountContainer)
mAccountProfileImageView = mAccountSelectorView!!.findViewById(R.id.profileImage) as ShapedImageView
mAccountProfileBannerView = mAccountSelectorView!!.findViewById(R.id.accountProfileBanner) as ViewSwitcher
mFloatingProfileImageSnapshotView = mAccountSelectorView!!.findViewById(R.id.floatingProfileImageSnapshot) as ImageView
mAccountProfileNameView = mAccountSelectorView!!.findViewById(R.id.name) as TextView
mAccountProfileScreenNameView = mAccountSelectorView!!.findViewById(R.id.screenName) as TextView
mAccountsToggleMenu = mAccountSelectorView!!.findViewById(R.id.accountDashboardMenu) as ActionMenuView
}
override fun onStart() {
super.onStart()
val resolver = contentResolver
resolver.registerContentObserver(Accounts.CONTENT_URI, true, mReloadContentObserver)
loaderManager.restartLoader(0, null, this)
}
override fun onStop() {
val resolver = contentResolver
resolver.unregisterContentObserver(mReloadContentObserver)
super.onStop()
}
internal fun updateAccountActions() {
val activity = activity as HomeActivity ?: return
val tabs = activity.tabs
val account = mAccountsAdapter!!.selectedAccount ?: return
var hasDmTab = false
var hasInteractionsTab = false
for (tab in tabs) {
if (tab.type == null) continue
when (tab.type) {
CustomTabType.DIRECT_MESSAGES -> {
if (!hasDmTab) {
hasDmTab = hasAccountInTab(tab, account.account_key, account.is_activated)
}
}
CustomTabType.NOTIFICATIONS_TIMELINE -> {
if (!hasInteractionsTab) {
hasInteractionsTab = hasAccountInTab(tab, account.account_key, account.is_activated)
}
}
}
}
val menu = mNavigationView!!.menu
menu.setItemAvailability(R.id.interactions, !hasInteractionsTab)
menu.setItemAvailability(R.id.messages, !hasDmTab)
if (mUseStarsForLikes) {
menu.setMenuItemTitle(R.id.favorites, R.string.favorites)
menu.setMenuItemIcon(R.id.favorites, R.drawable.ic_action_star)
} else {
menu.setMenuItemTitle(R.id.favorites, R.string.likes)
menu.setMenuItemIcon(R.id.favorites, R.drawable.ic_action_heart)
}
var hasLists = false
var hasGroups = false
var hasPublicTimeline = false
when (ParcelableAccountUtils.getAccountType(account)) {
ParcelableAccount.Type.TWITTER -> {
hasLists = true
}
ParcelableAccount.Type.STATUSNET -> {
hasGroups = true
}
ParcelableAccount.Type.FANFOU -> {
hasPublicTimeline = true
}
}
MenuUtils.setItemAvailability(menu, R.id.groups, hasGroups)
MenuUtils.setItemAvailability(menu, R.id.lists, hasLists)
MenuUtils.setItemAvailability(menu, R.id.public_timeline, hasPublicTimeline)
}
private fun hasAccountInTab(tab: SupportTabSpec, accountId: UserKey, isActivated: Boolean): Boolean {
if (tab.args == null) return false
val accountKeys = Utils.getAccountKeys(context, tab.args) ?: return isActivated
return ArrayUtils.contains(accountKeys, accountId)
}
private fun closeAccountsDrawer() {
val activity = activity
if (activity is HomeActivity) {
activity.closeAccountsDrawer()
}
}
private fun getLocationOnScreen(view: View, rectF: RectF) {
val location = IntArray(2)
view.getLocationOnScreen(location)
rectF.set(location[0].toFloat(), location[1].toFloat(), (location[0] + view.width).toFloat(), (location[1] + view.height).toFloat())
}
private fun onAccountSelected(holder: AccountProfileImageViewHolder, account: ParcelableAccount) {
if (mSwitchAccountAnimationPlaying) return
val snapshotView = mFloatingProfileImageSnapshotView!!
val profileImageView = mAccountProfileImageView!!
val clickedImageView = holder.iconView
// Reset snapshot view position
snapshotView.pivotX = 0f
snapshotView.pivotY = 0f
snapshotView.translationX = 0f
snapshotView.translationY = 0f
val matrix = Matrix()
val sourceBounds = RectF()
val destBounds = RectF()
val snapshotBounds = RectF()
getLocationOnScreen(clickedImageView, sourceBounds)
getLocationOnScreen(profileImageView, destBounds)
getLocationOnScreen(snapshotView, snapshotBounds)
val finalScale = destBounds.width() / sourceBounds.width()
val snapshotBitmap = TransitionUtils.createViewBitmap(clickedImageView, matrix,
RectF(0f, 0f, sourceBounds.width(), sourceBounds.height()))
val lp = snapshotView.layoutParams
lp.width = clickedImageView.width
lp.height = clickedImageView.height
snapshotView.layoutParams = lp
// Copied from MaterialNavigationDrawer: https://github.com/madcyph3r/AdvancedMaterialDrawer/
val set = AnimatorSet()
set.play(ObjectAnimator.ofFloat(snapshotView, View.TRANSLATION_X, sourceBounds.left - snapshotBounds.left, destBounds.left - snapshotBounds.left))
.with(ObjectAnimator.ofFloat(snapshotView, View.TRANSLATION_Y, sourceBounds.top - snapshotBounds.top, destBounds.top - snapshotBounds.top))
.with(ObjectAnimator.ofFloat<View>(snapshotView, View.SCALE_X, 1f, finalScale))
.with(ObjectAnimator.ofFloat<View>(snapshotView, View.SCALE_Y, 1f, finalScale))
.with(ObjectAnimator.ofFloat<View>(profileImageView, View.ALPHA, 1f, 0f))
.with(ObjectAnimator.ofFloat<View>(clickedImageView, View.SCALE_X, 0f, 1f))
.with(ObjectAnimator.ofFloat<View>(clickedImageView, View.SCALE_Y, 0f, 1f))
val animationTransition: Long = 400
set.duration = animationTransition
set.interpolator = DecelerateInterpolator()
set.addListener(object : AnimatorListener {
private var clickedDrawable: Drawable? = null
private var clickedColors: IntArray? = null
override fun onAnimationStart(animation: Animator) {
snapshotView.visibility = View.VISIBLE
snapshotView.setImageBitmap(snapshotBitmap)
val profileDrawable = profileImageView!!.drawable
clickedDrawable = clickedImageView.drawable
clickedColors = clickedImageView.borderColors
val oldSelectedAccount = mAccountsAdapter!!.selectedAccount ?: return
mediaLoader.displayDashboardProfileImage(clickedImageView,
oldSelectedAccount, profileDrawable)
clickedImageView.setBorderColors(*profileImageView.borderColors)
displayAccountBanner(account)
mSwitchAccountAnimationPlaying = true
}
override fun onAnimationEnd(animation: Animator) {
finishAnimation()
}
override fun onAnimationCancel(animation: Animator) {
finishAnimation()
}
override fun onAnimationRepeat(animation: Animator) {
}
private fun finishAnimation() {
val editor = preferences.edit()
editor.putString(KEY_DEFAULT_ACCOUNT_KEY, account.account_key.toString())
editor.apply()
mAccountsAdapter!!.selectedAccount = account
updateAccountActions()
displayCurrentAccount(clickedDrawable)
snapshotView.visibility = View.INVISIBLE
snapshotView.setImageDrawable(null)
profileImageView!!.setImageDrawable(clickedDrawable)
profileImageView.setBorderColors(*clickedColors!!)
profileImageView.alpha = 1f
clickedImageView.scaleX = 1f
clickedImageView.scaleY = 1f
clickedImageView.alpha = 1f
mSwitchAccountAnimationPlaying = false
}
})
set.start()
}
protected fun displayAccountBanner(account: ParcelableAccount) {
val bannerWidth = mAccountProfileBannerView!!.width
val res = resources
val defWidth = res.displayMetrics.widthPixels
val width = if (bannerWidth > 0) bannerWidth else defWidth
val bannerView = mAccountProfileBannerView!!.nextView as ImageView
if (bannerView.drawable == null || !CompareUtils.objectEquals(account, bannerView.tag)) {
mediaLoader.displayProfileBanner(bannerView, account, width)
bannerView.tag = account
} else {
mediaLoader.cancelDisplayTask(bannerView)
}
}
private fun displayCurrentAccount(profileImageSnapshot: Drawable?) {
val account = mAccountsAdapter!!.selectedAccount ?: return
mAccountProfileNameView!!.text = account.name
mAccountProfileScreenNameView!!.text = String.format("@%s", account.screen_name)
mediaLoader.displayDashboardProfileImage(mAccountProfileImageView!!, account,
profileImageSnapshot)
mAccountProfileImageView!!.setBorderColors(account.color)
mAccountProfileBannerView!!.showNext()
}
private fun updateDefaultAccountState() {
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val account = mAccountsAdapter!!.selectedAccount ?: return false
when (item.itemId) {
R.id.search -> {
val intent = Intent(activity, QuickSearchBarActivity::class.java)
intent.putExtra(EXTRA_ACCOUNT_KEY, account.account_key)
startActivity(intent)
closeAccountsDrawer()
}
R.id.compose -> {
val composeIntent = Intent(INTENT_ACTION_COMPOSE)
composeIntent.setClass(activity, ComposeActivity::class.java)
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, account.account_key)
startActivity(composeIntent)
}
R.id.favorites -> {
IntentUtils.openUserFavorites(activity, account.account_key,
account.account_key, account.screen_name)
}
R.id.lists -> {
IntentUtils.openUserLists(activity, account.account_key,
account.account_key, account.screen_name)
}
R.id.groups -> {
IntentUtils.openUserGroups(activity, account.account_key,
account.account_key, account.screen_name)
}
R.id.public_timeline -> {
IntentUtils.openPublicTimeline(activity, account.account_key)
}
R.id.messages -> {
IntentUtils.openDirectMessages(activity, account.account_key)
}
R.id.interactions -> {
IntentUtils.openInteractions(activity, account.account_key)
}
R.id.edit -> {
IntentUtils.openProfileEditor(activity, account.account_key)
}
R.id.accounts -> {
IntentUtils.openAccountsManager(activity)
closeAccountsDrawer()
}
R.id.drafts -> {
IntentUtils.openDrafts(activity)
closeAccountsDrawer()
}
R.id.filters -> {
IntentUtils.openFilters(activity)
closeAccountsDrawer()
}
R.id.plus_service -> {
val intent = Intent(activity, PlusServiceDashboardActivity::class.java)
startActivity(intent)
closeAccountsDrawer()
}
R.id.settings -> {
val intent = Intent(activity, SettingsActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
startActivityForResult(intent, REQUEST_SETTINGS)
closeAccountsDrawer()
}
}
return false
}
fun setStatusBarHeight(height: Int) {
val top = Utils.getInsetsTopWithoutActionBarHeight(activity, height)
mAccountProfileContainer!!.setPadding(0, top, 0, 0)
}
internal class AccountProfileImageViewHolder(val adapter: AccountSelectorAdapter, itemView: View) : ViewHolder(itemView), OnClickListener {
val iconView: ShapedImageView
init {
itemView.setOnClickListener(this)
iconView = itemView.findViewById(android.R.id.icon) as ShapedImageView
}
override fun onClick(v: View) {
adapter.dispatchItemSelected(this)
}
}
internal class AccountSelectorAdapter(
private val inflater: LayoutInflater,
private val fragment: AccountsDashboardFragment
) : Adapter<AccountProfileImageViewHolder>() {
private val mediaLoader: MediaLoaderWrapper
var accounts: Array<ParcelableAccount>? = null
set(value) {
if (value != null) {
val previousAccounts = accounts
if (previousAccounts != null) {
val tmpList = arrayListOf(*value)
val tmpResult = ArrayList<ParcelableAccount>()
previousAccounts.forEach { previousAccount ->
val prefIndexOfTmp = tmpList.indexOfFirst { previousAccount == it }
if (prefIndexOfTmp >= 0) {
tmpResult.add(tmpList.removeAt(prefIndexOfTmp))
}
}
tmpResult.addAll(tmpList)
field = tmpResult.toTypedArray()
} else {
field = value
}
} else {
field = null
}
notifyDataSetChanged()
}
init {
mediaLoader = fragment.mediaLoader
setHasStableIds(true)
}
private fun indexOfAccount(accounts: List<ParcelableAccount>, accountId: UserKey): Int {
var i = 0
val j = accounts.size
while (i < j) {
if (accounts[i].account_key == accountId) return i
i++
}
return -1
}
fun getAdapterAccount(adapterPosition: Int): ParcelableAccount? {
if (accounts == null || accounts!!.size < 1) {
return null
}
return accounts!![adapterPosition + 1]
}
var selectedAccount: ParcelableAccount?
get() {
if (accounts == null || accounts!!.size == 0) {
return null
}
return accounts!![0]
}
set(account) {
val selectedAccount = selectedAccount
if (selectedAccount == null || account == null) return
swap(account, selectedAccount)
}
val selectedAccountKey: UserKey?
get() {
val selectedAccount = selectedAccount ?: return null
return selectedAccount.account_key
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountProfileImageViewHolder {
val view = inflater.inflate(R.layout.adapter_item_dashboard_account, parent, false)
return AccountProfileImageViewHolder(this, view)
}
override fun onBindViewHolder(holder: AccountProfileImageViewHolder, position: Int) {
val account = getAdapterAccount(position)
mediaLoader.displayDashboardProfileImage(holder.iconView, account!!, null)
holder.iconView.setBorderColor(account.color)
}
override fun getItemId(position: Int): Long {
return getAdapterAccount(position)!!.hashCode().toLong()
}
override fun getItemCount(): Int {
if (accounts == null || accounts!!.size == 0) return 0
return accounts!!.size - 1
}
fun dispatchItemSelected(holder: AccountProfileImageViewHolder) {
fragment.onAccountSelected(holder, getAdapterAccount(holder.adapterPosition)!!)
}
private fun swap(from: ParcelableAccount, to: ParcelableAccount) {
val accounts = accounts ?: return
val fromIdx = accounts.indexOfFirst { it == from }
val toIdx = accounts.indexOfFirst { it == to }
if (fromIdx < 0 || toIdx < 0) return
val temp = accounts[toIdx]
accounts[toIdx] = accounts[fromIdx]
accounts[fromIdx] = temp
notifyDataSetChanged()
}
}
data class OptionItem(val name: Int, val icon: Int, val id: Int)
}

View File

@ -1,201 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
import javax.inject.Inject;
public class BaseListFragment extends ListFragment implements Constants, OnScrollListener, RefreshScrollTopInterface {
@Inject
protected AsyncTwitterWrapper mTwitterWrapper;
@Inject
protected SharedPreferencesWrapper mPreferences;
private boolean mActivityFirstCreated;
private boolean mIsInstanceStateSaved;
private boolean mReachedBottom, mNotReachedBottomBefore = true;
@SuppressWarnings("deprecation")
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
GeneralComponentHelper.build(activity).inject(this);
}
public final TwidereApplication getApplication() {
return TwidereApplication.Companion.getInstance(getActivity());
}
public final ContentResolver getContentResolver() {
final Activity activity = getActivity();
if (activity != null) return activity.getContentResolver();
return null;
}
public final SharedPreferences getSharedPreferences(final String name, final int mode) {
final Activity activity = getActivity();
if (activity != null) return activity.getSharedPreferences(name, mode);
return null;
}
public final Object getSystemService(final String name) {
final Activity activity = getActivity();
if (activity != null) return activity.getSystemService(name);
return null;
}
public final int getTabPosition() {
final Bundle args = getArguments();
return args != null ? args.getInt(EXTRA_TAB_POSITION, -1) : -1;
}
public void invalidateOptionsMenu() {
final Activity activity = getActivity();
if (activity == null) return;
activity.invalidateOptionsMenu();
}
public boolean isActivityFirstCreated() {
return mActivityFirstCreated;
}
public boolean isInstanceStateSaved() {
return mIsInstanceStateSaved;
}
public boolean isReachedBottom() {
return mReachedBottom;
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mIsInstanceStateSaved = savedInstanceState != null;
final ListView lv = getListView();
lv.setOnScrollListener(this);
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivityFirstCreated = true;
}
@Override
public void onDestroy() {
super.onDestroy();
mActivityFirstCreated = true;
}
public void onPostStart() {
}
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
final boolean reached = firstVisibleItem + visibleItemCount >= totalItemCount
&& totalItemCount >= visibleItemCount;
if (mReachedBottom != reached) {
mReachedBottom = reached;
if (mReachedBottom && mNotReachedBottomBefore) {
mNotReachedBottomBefore = false;
return;
}
if (mReachedBottom && getListAdapter().getCount() > visibleItemCount) {
onReachedBottom();
}
}
}
@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
}
@Override
public void onStart() {
super.onStart();
onPostStart();
}
@Override
public void onStop() {
mActivityFirstCreated = false;
super.onStop();
}
public void registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter) {
final Activity activity = getActivity();
if (activity == null) return;
activity.registerReceiver(receiver, filter);
}
@Override
public boolean scrollToStart() {
Utils.scrollListToTop(getListView());
return true;
}
public void setProgressBarIndeterminateVisibility(final boolean visible) {
final Activity activity = getActivity();
if (activity == null) return;
activity.setProgressBarIndeterminateVisibility(visible);
}
@Override
public void setSelection(final int position) {
Utils.scrollListToPosition(getListView(), position);
}
@Override
public boolean triggerRefresh() {
return false;
}
public void unregisterReceiver(final BroadcastReceiver receiver) {
final Activity activity = getActivity();
if (activity == null) return;
activity.unregisterReceiver(receiver);
}
protected void onReachedBottom() {
}
}

View File

@ -0,0 +1,157 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.content.ContentResolver
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.support.v4.app.ListFragment
import android.widget.AbsListView
import android.widget.AbsListView.OnScrollListener
import org.mariotaku.twidere.app.TwidereApplication
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_TAB_POSITION
import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface
import org.mariotaku.twidere.util.AsyncTwitterWrapper
import org.mariotaku.twidere.util.SharedPreferencesWrapper
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import javax.inject.Inject
open class BaseListFragment : ListFragment(), OnScrollListener, RefreshScrollTopInterface {
@Inject
lateinit var twitterWrapper: AsyncTwitterWrapper
@Inject
lateinit var preferences: SharedPreferencesWrapper
var activityFirstCreated: Boolean = false
private set
var instanceStateSaved: Boolean = false
private set
var reachedBottom: Boolean = false
private set
private var notReachedBottomBefore = true
override fun onAttach(context: Context) {
super.onAttach(context)
GeneralComponentHelper.build(context).inject(this)
}
val application: TwidereApplication
get() = TwidereApplication.getInstance(activity)
val contentResolver: ContentResolver?
get() {
val activity = activity
if (activity != null) return activity.contentResolver
return null
}
fun getSharedPreferences(name: String, mode: Int): SharedPreferences? {
val activity = activity
if (activity != null) return activity.getSharedPreferences(name, mode)
return null
}
fun getSystemService(name: String): Any? {
val activity = activity
if (activity != null) return activity.getSystemService(name)
return null
}
val tabPosition: Int
get() {
val args = arguments
return if (args != null) args.getInt(EXTRA_TAB_POSITION, -1) else -1
}
fun invalidateOptionsMenu() {
val activity = activity ?: return
activity.invalidateOptionsMenu()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
instanceStateSaved = savedInstanceState != null
val lv = listView
lv.setOnScrollListener(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityFirstCreated = true
}
override fun onDestroy() {
super.onDestroy()
activityFirstCreated = true
}
fun onPostStart() {
}
override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int,
totalItemCount: Int) {
val reached = firstVisibleItem + visibleItemCount >= totalItemCount && totalItemCount >= visibleItemCount
if (reachedBottom != reached) {
reachedBottom = reached
if (reachedBottom && notReachedBottomBefore) {
notReachedBottomBefore = false
return
}
if (reachedBottom && listAdapter.count > visibleItemCount) {
onReachedBottom()
}
}
}
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
}
override fun onStart() {
super.onStart()
onPostStart()
}
override fun onStop() {
activityFirstCreated = false
super.onStop()
}
override fun scrollToStart(): Boolean {
Utils.scrollListToTop(listView)
return true
}
override fun setSelection(position: Int) {
Utils.scrollListToPosition(listView, position)
}
override fun triggerRefresh(): Boolean {
return false
}
protected fun onReachedBottom() {
}
}

View File

@ -1,175 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ListView;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.ExtensionsAdapter;
import org.mariotaku.twidere.loader.ExtensionsListLoader;
import org.mariotaku.twidere.loader.ExtensionsListLoader.ExtensionInfo;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.PermissionsManager;
import java.util.List;
public class ExtensionsListFragment extends BaseListFragment implements LoaderCallbacks<List<ExtensionInfo>> {
private ExtensionsAdapter mAdapter;
private PackageManager mPackageManager;
private PermissionsManager mPermissionsManager;
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mPackageManager = getActivity().getPackageManager();
mPermissionsManager = new PermissionsManager(getActivity());
mAdapter = new ExtensionsAdapter(getActivity());
setListAdapter(mAdapter);
final ListView listView = getListView();
listView.setOnCreateContextMenuListener(this);
getLoaderManager().initLoader(0, null, this);
setListShown(false);
}
@Override
public void onStop() {
super.onStop();
}
@Override
public Loader<List<ExtensionInfo>> onCreateLoader(final int id, final Bundle args) {
return new ExtensionsListLoader(getActivity(), mPackageManager);
}
@Override
public void onLoadFinished(final Loader<List<ExtensionInfo>> loader, final List<ExtensionInfo> data) {
mAdapter.setData(data);
setListShown(true);
}
@Override
public void onLoaderReset(final Loader<List<ExtensionInfo>> loader) {
mAdapter.setData(null);
}
@Override
public void onListItemClick(final ListView l, final View v, final int position, final long id) {
openSettings(mAdapter.getItem(position));
}
@Override
public void onResume() {
super.onResume();
mAdapter.notifyDataSetChanged();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
final MenuInflater inflater = new MenuInflater(v.getContext());
inflater.inflate(R.menu.action_extension, menu);
final AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
final ExtensionInfo extensionInfo = mAdapter.getItem(adapterMenuInfo.position);
if (extensionInfo.pname != null && extensionInfo.settings != null) {
final Intent intent = new Intent(INTENT_ACTION_EXTENSION_SETTINGS);
intent.setClassName(extensionInfo.pname, extensionInfo.settings);
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.settings, mPackageManager.queryIntentActivities(intent, 0).size() == 1);
} else {
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.settings, false);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
final ExtensionInfo extensionInfo = mAdapter.getItem(adapterMenuInfo.position);
switch (item.getItemId()) {
case R.id.settings: {
openSettings(extensionInfo);
break;
}
case R.id.delete: {
uninstallExtension(extensionInfo);
break;
}
case R.id.revoke: {
mPermissionsManager.revoke(extensionInfo.pname);
mAdapter.notifyDataSetChanged();
break;
}
default: {
return false;
}
}
return true;
}
private boolean openSettings(final ExtensionInfo info) {
final Intent intent = new Intent(INTENT_ACTION_EXTENSION_SETTINGS);
intent.setPackage(info.pname);
if (info.settings != null) {
intent.setClassName(info.pname, info.settings);
} else {
final PackageManager pm = getActivity().getPackageManager();
final List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
if (activities.isEmpty()) {
return false;
}
final ResolveInfo resolveInfo = activities.get(0);
intent.setClassName(info.pname, resolveInfo.activityInfo.name);
}
try {
startActivity(intent);
} catch (final Exception e) {
Log.w(LOGTAG, e);
return false;
}
return true;
}
private boolean uninstallExtension(final ExtensionInfo info) {
if (info == null) return false;
final Uri packageUri = Uri.parse("package:" + info.pname);
final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
try {
startActivity(uninstallIntent);
} catch (final Exception e) {
Log.w(LOGTAG, e);
return false;
}
return true;
}
}

View File

@ -0,0 +1,162 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.LoaderManager.LoaderCallbacks
import android.support.v4.content.Loader
import android.util.Log
import android.view.ContextMenu
import android.view.ContextMenu.ContextMenuInfo
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView.AdapterContextMenuInfo
import android.widget.ListView
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.adapter.ExtensionsAdapter
import org.mariotaku.twidere.constant.IntentConstants
import org.mariotaku.twidere.loader.ExtensionsListLoader
import org.mariotaku.twidere.loader.ExtensionsListLoader.ExtensionInfo
import org.mariotaku.twidere.util.PermissionsManager
class ExtensionsListFragment : BaseListFragment(), LoaderCallbacks<List<ExtensionInfo>> {
private var adapter: ExtensionsAdapter? = null
private var packageManager: PackageManager? = null
private var permissionsManager: PermissionsManager? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
packageManager = activity.packageManager
permissionsManager = PermissionsManager(activity)
adapter = ExtensionsAdapter(activity)
listAdapter = adapter
listView.setOnCreateContextMenuListener(this)
loaderManager.initLoader(0, null, this)
setListShown(false)
}
override fun onStop() {
super.onStop()
}
override fun onCreateLoader(id: Int, args: Bundle): Loader<List<ExtensionInfo>> {
return ExtensionsListLoader(activity, packageManager)
}
override fun onLoadFinished(loader: Loader<List<ExtensionInfo>>, data: List<ExtensionInfo>) {
adapter!!.setData(data)
setListShown(true)
}
override fun onLoaderReset(loader: Loader<List<ExtensionInfo>>) {
adapter!!.setData(null)
}
override fun onListItemClick(l: ListView?, v: View?, position: Int, id: Long) {
openSettings(adapter!!.getItem(position))
}
override fun onResume() {
super.onResume()
adapter!!.notifyDataSetChanged()
}
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo) {
val inflater = MenuInflater(v.context)
inflater.inflate(R.menu.action_extension, menu)
val adapterMenuInfo = menuInfo as AdapterContextMenuInfo
val extensionInfo = adapter!!.getItem(adapterMenuInfo.position)
if (extensionInfo.pname != null && extensionInfo.settings != null) {
val intent = Intent(IntentConstants.INTENT_ACTION_EXTENSION_SETTINGS)
intent.setClassName(extensionInfo.pname, extensionInfo.settings)
menu.setItemAvailability(R.id.settings, packageManager!!.queryIntentActivities(intent, 0).size == 1)
} else {
menu.setItemAvailability(R.id.settings, false)
}
}
override fun onContextItemSelected(item: MenuItem?): Boolean {
val adapterMenuInfo = item!!.menuInfo as AdapterContextMenuInfo
val extensionInfo = adapter!!.getItem(adapterMenuInfo.position)
when (item.itemId) {
R.id.settings -> {
openSettings(extensionInfo)
}
R.id.delete -> {
uninstallExtension(extensionInfo)
}
R.id.revoke -> {
permissionsManager!!.revoke(extensionInfo.pname)
adapter!!.notifyDataSetChanged()
}
else -> {
return false
}
}
return true
}
private fun openSettings(info: ExtensionInfo): Boolean {
val intent = Intent(IntentConstants.INTENT_ACTION_EXTENSION_SETTINGS)
intent.setPackage(info.pname)
if (info.settings != null) {
intent.setClassName(info.pname, info.settings)
} else {
val pm = activity.packageManager
val activities = pm.queryIntentActivities(intent, 0)
if (activities.isEmpty()) {
return false
}
val resolveInfo = activities[0]
intent.setClassName(info.pname, resolveInfo.activityInfo.name)
}
try {
startActivity(intent)
} catch (e: Exception) {
Log.w(LOGTAG, e)
return false
}
return true
}
private fun uninstallExtension(info: ExtensionInfo?): Boolean {
if (info == null) return false
val packageUri = Uri.parse("package:${info.pname}")
val uninstallIntent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri)
try {
startActivity(uninstallIntent)
} catch (e: Exception) {
Log.w(LOGTAG, e)
return false
}
return true
}
}

View File

@ -1,321 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnShowListener;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import org.apache.commons.lang3.StringUtils;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.ArrayAdapter;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import java.util.Map;
import static android.text.TextUtils.isEmpty;
public class HostMappingsListFragment extends BaseListFragment implements MultiChoiceModeListener,
OnSharedPreferenceChangeListener {
private static final String EXTRA_EDIT_MODE = "edit_mode";
private static final String EXTRA_HOST = "host";
private static final String EXTRA_ADDRESS = "address";
private static final String EXTRA_EXCLUDED = "excluded";
private ListView mListView;
private HostMappingAdapter mAdapter;
private SharedPreferencesWrapper mHostMapping;
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
mHostMapping = SharedPreferencesWrapper.getInstance(getActivity(),
HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mHostMapping.registerOnSharedPreferenceChangeListener(this);
mAdapter = new HostMappingAdapter(getActivity());
setListAdapter(mAdapter);
mListView = getListView();
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(this);
reloadHostMappings();
}
@Override
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
mode.getMenuInflater().inflate(R.menu.action_multi_select_items, menu);
return true;
}
@Override
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
updateTitle(mode);
return true;
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
switch (item.getItemId()) {
case R.id.delete: {
final SharedPreferences.Editor editor = mHostMapping.edit();
final SparseBooleanArray array = mListView.getCheckedItemPositions();
if (array == null) return false;
for (int i = 0, size = array.size(); i < size; i++) {
if (array.valueAt(i)) {
editor.remove(mAdapter.getItem(i));
}
}
editor.apply();
reloadHostMappings();
break;
}
default: {
return false;
}
}
mode.finish();
return true;
}
@Override
public void onDestroyActionMode(final ActionMode mode) {
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.menu_host_mapping, menu);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
final String host = mAdapter.getItem(position);
final String address = mAdapter.getAddress(host);
final Bundle args = new Bundle();
args.putString(EXTRA_HOST, host);
args.putString(EXTRA_ADDRESS, address);
args.putBoolean(EXTRA_EXCLUDED, StringUtils.equals(host, address));
args.putBoolean(EXTRA_EDIT_MODE, true);
final DialogFragment df = new AddMappingDialogFragment();
df.setArguments(args);
df.show(getFragmentManager(), "add_mapping");
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.add: {
final DialogFragment df = new AddMappingDialogFragment();
df.show(getFragmentManager(), "add_mapping");
break;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onItemCheckedStateChanged(final ActionMode mode, final int position, final long id,
final boolean checked) {
updateTitle(mode);
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
reloadHostMappings();
}
public void reloadHostMappings() {
if (mAdapter == null) return;
mAdapter.reload();
}
private void updateTitle(final ActionMode mode) {
if (mListView == null || mode == null || getActivity() == null) return;
final int count = mListView.getCheckedItemCount();
mode.setTitle(getResources().getQuantityString(R.plurals.Nitems_selected, count, count));
}
public static class AddMappingDialogFragment extends BaseDialogFragment implements OnClickListener,
TextWatcher, OnCheckedChangeListener {
private EditText mEditHost, mEditAddress;
private CheckBox mCheckExclude;
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
updateButton();
}
@Override
public void afterTextChanged(final Editable s) {
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateAddressField();
updateButton();
}
@Override
public void onClick(final DialogInterface dialog, final int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE: {
final String host = ParseUtils.parseString(mEditHost.getText());
final String address = mCheckExclude.isChecked() ? host : ParseUtils.parseString(mEditAddress.getText());
if (isEmpty(host) || isEmpty(address)) return;
final SharedPreferences prefs = getContext().getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME,
Context.MODE_PRIVATE);
final SharedPreferences.Editor editor = prefs.edit();
editor.putString(host, address);
editor.apply();
break;
}
}
}
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setView(R.layout.dialog_host_mapping);
builder.setTitle(R.string.add_host_mapping);
builder.setPositiveButton(android.R.string.ok, this);
builder.setNegativeButton(android.R.string.cancel, null);
final AlertDialog dialog = builder.create();
dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
AlertDialog alertDialog = (AlertDialog) dialog;
mEditHost = (EditText) alertDialog.findViewById(R.id.host);
mEditAddress = (EditText) alertDialog.findViewById(R.id.address);
mCheckExclude = (CheckBox) alertDialog.findViewById(R.id.exclude);
mEditHost.addTextChangedListener(AddMappingDialogFragment.this);
mEditAddress.addTextChangedListener(AddMappingDialogFragment.this);
mCheckExclude.setOnCheckedChangeListener(AddMappingDialogFragment.this);
final Bundle args = getArguments();
if (args != null) {
mEditHost.setEnabled(!args.getBoolean(EXTRA_EDIT_MODE, false));
if (savedInstanceState == null) {
mEditHost.setText(args.getCharSequence(EXTRA_HOST));
mEditAddress.setText(args.getCharSequence(EXTRA_ADDRESS));
mCheckExclude.setChecked(args.getBoolean(EXTRA_EXCLUDED));
}
}
updateButton();
}
});
return dialog;
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
outState.putCharSequence(EXTRA_HOST, mEditHost.getText());
outState.putCharSequence(EXTRA_ADDRESS, mEditAddress.getText());
outState.putCharSequence(EXTRA_EXCLUDED, mEditAddress.getText());
super.onSaveInstanceState(outState);
}
private void updateAddressField() {
mEditAddress.setVisibility(mCheckExclude.isChecked() ? View.GONE : View.VISIBLE);
}
private void updateButton() {
final AlertDialog dialog = (AlertDialog) getDialog();
if (dialog == null) return;
final boolean hostValid = !isEmpty(mEditHost.getText());
final boolean addressValid = !isEmpty(mEditAddress.getText()) || mCheckExclude.isChecked();
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setEnabled(hostValid && addressValid);
}
}
static class HostMappingAdapter extends ArrayAdapter<String> {
private final SharedPreferences mHostMapping;
public HostMappingAdapter(final Context context) {
super(context, android.R.layout.simple_list_item_activated_2);
mHostMapping = context.getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View view = super.getView(position, convertView, parent);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
final String key = getItem(position);
text1.setText(key);
final String value = getAddress(key);
if (StringUtils.equals(key, value)) {
text2.setText(R.string.excluded);
} else {
text2.setText(value);
}
return view;
}
public void reload() {
clear();
final Map<String, ?> all = mHostMapping.getAll();
addAll(all.keySet());
}
public String getAddress(String key) {
return mHostMapping.getString(key, null);
}
}
}

View File

@ -0,0 +1,280 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.content.DialogInterface.OnClickListener
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.Editable
import android.text.TextUtils.isEmpty
import android.text.TextWatcher
import android.view.*
import android.widget.*
import android.widget.AbsListView.MultiChoiceModeListener
import android.widget.CompoundButton.OnCheckedChangeListener
import org.apache.commons.lang3.StringUtils
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.HOST_MAPPING_PREFERENCES_NAME
import org.mariotaku.twidere.adapter.ArrayAdapter
import org.mariotaku.twidere.util.ParseUtils
import org.mariotaku.twidere.util.SharedPreferencesWrapper
class HostMappingsListFragment : BaseListFragment(), MultiChoiceModeListener, OnSharedPreferenceChangeListener {
private var mAdapter: HostMappingAdapter? = null
private var mHostMapping: SharedPreferencesWrapper? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mHostMapping = SharedPreferencesWrapper.getInstance(activity,
Constants.HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE)
mHostMapping!!.registerOnSharedPreferenceChangeListener(this)
mAdapter = HostMappingAdapter(activity)
listAdapter = mAdapter
listView.choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL
listView.setMultiChoiceModeListener(this)
reloadHostMappings()
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.action_multi_select_items, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
updateTitle(mode)
return true
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
when (item.itemId) {
R.id.delete -> {
val editor = mHostMapping!!.edit()
val array = listView!!.checkedItemPositions ?: return false
var i = 0
val size = array.size()
while (i < size) {
if (array.valueAt(i)) {
editor.remove(mAdapter!!.getItem(i))
}
i++
}
editor.apply()
reloadHostMappings()
}
else -> {
return false
}
}
mode.finish()
return true
}
override fun onDestroyActionMode(mode: ActionMode) {
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater!!.inflate(R.menu.menu_host_mapping, menu)
}
override fun onListItemClick(l: ListView?, v: View?, position: Int, id: Long) {
val host = mAdapter!!.getItem(position)
val address = mAdapter!!.getAddress(host)
val args = Bundle()
args.putString(EXTRA_HOST, host)
args.putString(EXTRA_ADDRESS, address)
args.putBoolean(EXTRA_EXCLUDED, StringUtils.equals(host, address))
args.putBoolean(EXTRA_EDIT_MODE, true)
val df = AddMappingDialogFragment()
df.arguments = args
df.show(fragmentManager, "add_mapping")
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.add -> {
val df = AddMappingDialogFragment()
df.show(fragmentManager, "add_mapping")
}
}
return super.onOptionsItemSelected(item)
}
override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long,
checked: Boolean) {
updateTitle(mode)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
reloadHostMappings()
}
fun reloadHostMappings() {
if (mAdapter == null) return
mAdapter!!.reload()
}
private fun updateTitle(mode: ActionMode?) {
if (listView == null || mode == null || activity == null) return
val count = listView!!.checkedItemCount
mode.title = resources.getQuantityString(R.plurals.Nitems_selected, count, count)
}
class AddMappingDialogFragment : BaseDialogFragment(), OnClickListener, TextWatcher, OnCheckedChangeListener {
private var mEditHost: EditText? = null
private var mEditAddress: EditText? = null
private var mCheckExclude: CheckBox? = null
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
updateButton()
}
override fun afterTextChanged(s: Editable) {
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
updateAddressField()
updateButton()
}
override fun onClick(dialog: DialogInterface, which: Int) {
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val host = ParseUtils.parseString(mEditHost!!.text)
val address = if (mCheckExclude!!.isChecked) host else ParseUtils.parseString(mEditAddress!!.text)
if (isEmpty(host) || isEmpty(address)) return
val prefs = context.getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME,
Context.MODE_PRIVATE)
val editor = prefs.edit()
editor.putString(host, address)
editor.apply()
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = activity
val builder = AlertDialog.Builder(context)
builder.setView(R.layout.dialog_host_mapping)
builder.setTitle(R.string.add_host_mapping)
builder.setPositiveButton(android.R.string.ok, this)
builder.setNegativeButton(android.R.string.cancel, null)
val dialog = builder.create()
dialog.setOnShowListener { dialog ->
val alertDialog = dialog as AlertDialog
mEditHost = alertDialog.findViewById(R.id.host) as EditText?
mEditAddress = alertDialog.findViewById(R.id.address) as EditText?
mCheckExclude = alertDialog.findViewById(R.id.exclude) as CheckBox?
mEditHost!!.addTextChangedListener(this@AddMappingDialogFragment)
mEditAddress!!.addTextChangedListener(this@AddMappingDialogFragment)
mCheckExclude!!.setOnCheckedChangeListener(this@AddMappingDialogFragment)
val args = arguments
if (args != null) {
mEditHost!!.isEnabled = !args.getBoolean(EXTRA_EDIT_MODE, false)
if (savedInstanceState == null) {
mEditHost!!.setText(args.getCharSequence(EXTRA_HOST))
mEditAddress!!.setText(args.getCharSequence(EXTRA_ADDRESS))
mCheckExclude!!.isChecked = args.getBoolean(EXTRA_EXCLUDED)
}
}
updateButton()
}
return dialog
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putCharSequence(EXTRA_HOST, mEditHost!!.text)
outState.putCharSequence(EXTRA_ADDRESS, mEditAddress!!.text)
outState.putCharSequence(EXTRA_EXCLUDED, mEditAddress!!.text)
super.onSaveInstanceState(outState)
}
private fun updateAddressField() {
mEditAddress!!.visibility = if (mCheckExclude!!.isChecked) View.GONE else View.VISIBLE
}
private fun updateButton() {
val dialog = dialog as AlertDialog ?: return
val hostValid = !isEmpty(mEditHost!!.text)
val addressValid = !isEmpty(mEditAddress!!.text) || mCheckExclude!!.isChecked
val positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
positiveButton.isEnabled = hostValid && addressValid
}
}
internal class HostMappingAdapter(context: Context) : ArrayAdapter<String>(context, android.R.layout.simple_list_item_activated_2) {
private val mHostMapping: SharedPreferences
init {
mHostMapping = context.getSharedPreferences(Constants.HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE)
}
override fun getView(position: Int, convertView: View, parent: ViewGroup): View {
val view = super.getView(position, convertView, parent)
val text1 = view.findViewById(android.R.id.text1) as TextView
val text2 = view.findViewById(android.R.id.text2) as TextView
val key = getItem(position)
text1.text = key
val value = getAddress(key)
if (StringUtils.equals(key, value)) {
text2.setText(R.string.excluded)
} else {
text2.text = value
}
return view
}
fun reload() {
clear()
val all = mHostMapping.all
addAll(all.keys)
}
fun getAddress(key: String): String {
return mHostMapping.getString(key, null)
}
}
companion object {
private val EXTRA_EDIT_MODE = "edit_mode"
private val EXTRA_HOST = "host"
private val EXTRA_ADDRESS = "address"
private val EXTRA_EXCLUDED = "excluded"
}
}

View File

@ -324,7 +324,7 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
override fun onPrepareOptionsMenu(menu: Menu?) {
super.onPrepareOptionsMenu(menu)
MenuUtils.setMenuItemAvailability(menu, R.id.delete_all, recipient != null && Utils.isOfficialCredentials(activity, account!!))
MenuUtils.setItemAvailability(menu, R.id.delete_all, recipient != null && Utils.isOfficialCredentials(activity, account!!))
updateRecipientInfo()
}

View File

@ -1,365 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.PopupMenu;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.twitter.Validator;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.DummyItemAdapter;
import org.mariotaku.twidere.model.Draft;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableCredentials;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableStatusUpdate;
import org.mariotaku.twidere.model.util.ParcelableAccountUtils;
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils;
import org.mariotaku.twidere.service.BackgroundOperationService;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.EditTextEnterHandler;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.TwidereBugReporter;
import org.mariotaku.twidere.util.TwidereValidator;
import org.mariotaku.twidere.view.ComposeEditText;
import org.mariotaku.twidere.view.StatusTextCountView;
import org.mariotaku.twidere.view.holder.StatusViewHolder;
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder;
import static org.mariotaku.twidere.util.Utils.isMyRetweet;
public class RetweetQuoteDialogFragment extends BaseDialogFragment {
public static final String FRAGMENT_TAG = "retweet_quote";
private static final boolean SHOW_PROTECTED_CONFIRM = Boolean.parseBoolean("false");
private PopupMenu mPopupMenu;
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
final Context context = builder.getContext();
final ParcelableStatus status = getStatus();
assert status != null;
final ParcelableCredentials credentials = ParcelableCredentialsUtils.getCredentials(getContext(),
status.account_key);
assert credentials != null;
builder.setView(R.layout.dialog_status_quote_retweet);
builder.setTitle(R.string.retweet_quote_confirm_title);
builder.setPositiveButton(R.string.retweet, null);
builder.setNegativeButton(android.R.string.cancel, null);
builder.setNeutralButton(R.string.quote, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final Intent intent = new Intent(INTENT_ACTION_QUOTE);
final Menu menu = mPopupMenu.getMenu();
final MenuItem quoteOriginalStatus = menu.findItem(R.id.quote_original_status);
intent.putExtra(EXTRA_STATUS, status);
intent.putExtra(EXTRA_QUOTE_ORIGINAL_STATUS, quoteOriginalStatus.isChecked());
startActivity(intent);
}
});
final Dialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
final AlertDialog dialog = (AlertDialog) dialogInterface;
final View itemContent = dialog.findViewById(R.id.itemContent);
final StatusTextCountView textCountView = (StatusTextCountView) dialog.findViewById(R.id.comment_text_count);
final View itemMenu = dialog.findViewById(R.id.itemMenu);
final View actionButtons = dialog.findViewById(R.id.actionButtons);
final View commentContainer = dialog.findViewById(R.id.comment_container);
final ComposeEditText editComment = (ComposeEditText) dialog.findViewById(R.id.edit_comment);
final View commentMenu = dialog.findViewById(R.id.comment_menu);
assert itemContent != null && textCountView != null && itemMenu != null
&& actionButtons != null && commentContainer != null && editComment != null
&& commentMenu != null;
final DummyItemAdapter adapter = new DummyItemAdapter(context);
adapter.setShouldShowAccountsColor(true);
final IStatusViewHolder holder = new StatusViewHolder(adapter, itemContent);
holder.displayStatus(status, false, true);
textCountView.setMaxLength(TwidereValidator.getTextLimit(credentials));
itemMenu.setVisibility(View.GONE);
actionButtons.setVisibility(View.GONE);
itemContent.setFocusable(false);
final boolean useQuote = useQuote(!status.user_is_protected, credentials);
commentContainer.setVisibility(useQuote ? View.VISIBLE : View.GONE);
editComment.setAccountKey(status.account_key);
final boolean sendByEnter = preferences.getBoolean(KEY_QUICK_SEND);
final EditTextEnterHandler enterHandler = EditTextEnterHandler.attach(editComment, new EditTextEnterHandler.EnterListener() {
@Override
public boolean shouldCallListener() {
return true;
}
@Override
public boolean onHitEnter() {
final ParcelableStatus status = getStatus();
if (status == null) return false;
if (retweetOrQuote(credentials, status, SHOW_PROTECTED_CONFIRM)) {
dismiss();
return true;
}
return false;
}
}, sendByEnter);
enterHandler.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
updateTextCount(getDialog(), s, status, credentials);
}
@Override
public void afterTextChanged(Editable s) {
}
});
mPopupMenu = new PopupMenu(context, commentMenu, Gravity.NO_GRAVITY,
R.attr.actionOverflowMenuStyle, 0);
commentMenu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPopupMenu.show();
}
});
commentMenu.setOnTouchListener(mPopupMenu.getDragToOpenListener());
mPopupMenu.inflate(R.menu.menu_dialog_comment);
final Menu menu = mPopupMenu.getMenu();
MenuUtils.Companion.setMenuItemAvailability(menu, R.id.quote_original_status,
status.retweet_id != null || status.quoted_id != null);
mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.isCheckable()) {
item.setChecked(!item.isChecked());
return true;
}
return false;
}
});
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean dismissDialog = false;
if (editComment.length() > 0) {
dismissDialog = retweetOrQuote(credentials, status, SHOW_PROTECTED_CONFIRM);
} else if (isMyRetweet(status)) {
twitterWrapper.cancelRetweetAsync(status.account_key, status.id, status.my_retweet_id);
dismissDialog = true;
} else if (useQuote(!status.user_is_protected, credentials)) {
dismissDialog = retweetOrQuote(credentials, status, SHOW_PROTECTED_CONFIRM);
} else {
TwidereBugReporter.logException(new IllegalStateException(status.toString()));
}
if (dismissDialog) {
dismiss();
}
}
});
updateTextCount(dialog, editComment.getText(), status, credentials);
}
});
return dialog;
}
private void updateTextCount(DialogInterface dialog, CharSequence s, ParcelableStatus status, ParcelableCredentials credentials) {
if (!(dialog instanceof AlertDialog)) return;
final AlertDialog alertDialog = (AlertDialog) dialog;
final Button positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
if (positiveButton == null) return;
if (s.length() > 0) {
positiveButton.setText(R.string.comment);
positiveButton.setEnabled(true);
} else if (isMyRetweet(status)) {
positiveButton.setText(R.string.cancel_retweet);
positiveButton.setEnabled(true);
} else if (useQuote(false, credentials)) {
positiveButton.setText(R.string.retweet);
positiveButton.setEnabled(true);
} else {
positiveButton.setText(R.string.retweet);
positiveButton.setEnabled(!status.user_is_protected);
}
final StatusTextCountView textCountView = (StatusTextCountView) alertDialog.findViewById(R.id.comment_text_count);
assert textCountView != null;
textCountView.setTextCount(validator.getTweetLength(s.toString()));
}
private ParcelableStatus getStatus() {
final Bundle args = getArguments();
if (!args.containsKey(EXTRA_STATUS)) return null;
return args.getParcelable(EXTRA_STATUS);
}
@CheckResult
private boolean retweetOrQuote(ParcelableAccount account, ParcelableStatus status,
boolean showProtectedConfirmation) {
AsyncTwitterWrapper twitter = twitterWrapper;
final Dialog dialog = getDialog();
if (dialog == null || twitter == null) return false;
final EditText editComment = (EditText) dialog.findViewById(R.id.edit_comment);
if (useQuote(editComment.length() > 0, account)) {
final Menu menu = mPopupMenu.getMenu();
final MenuItem itemQuoteOriginalStatus = menu.findItem(R.id.quote_original_status);
final Uri statusLink;
final boolean quoteOriginalStatus = itemQuoteOriginalStatus.isChecked();
String commentText;
final ParcelableStatusUpdate update = new ParcelableStatusUpdate();
update.accounts = new ParcelableAccount[]{account};
final String editingComment = String.valueOf(editComment.getText());
switch (ParcelableAccountUtils.getAccountType(account)) {
case ParcelableAccount.Type.FANFOU: {
if (!status.is_quote || !quoteOriginalStatus) {
if (status.user_is_protected && showProtectedConfirmation) {
QuoteProtectedStatusWarnFragment.show(this, account, status);
return false;
}
update.repost_status_id = status.id;
commentText = getString(R.string.fanfou_repost_format, editingComment,
status.user_screen_name, status.text_plain);
} else {
if (status.quoted_user_is_protected && showProtectedConfirmation) {
return false;
}
commentText = getString(R.string.fanfou_repost_format, editingComment,
status.quoted_user_screen_name, status.quoted_text_plain);
update.repost_status_id = status.quoted_id;
}
if (commentText.length() > Validator.MAX_TWEET_LENGTH) {
commentText = commentText.substring(0, Math.max(Validator.MAX_TWEET_LENGTH,
editingComment.length()));
}
break;
}
default: {
if (!status.is_quote || !quoteOriginalStatus) {
statusLink = LinkCreator.getStatusWebLink(status);
} else {
statusLink = LinkCreator.getQuotedStatusWebLink(status);
}
update.attachment_url = statusLink.toString();
commentText = editingComment;
break;
}
}
update.text = commentText;
update.is_possibly_sensitive = status.is_possibly_sensitive;
BackgroundOperationService.updateStatusesAsync(getContext(), Draft.Action.QUOTE, update);
} else {
twitter.retweetStatusAsync(status.account_key, status.id);
}
return true;
}
private boolean useQuote(boolean preCondition, ParcelableAccount account) {
return preCondition || ParcelableAccount.Type.FANFOU.equals(account.account_type);
}
public static RetweetQuoteDialogFragment show(final FragmentManager fm, final ParcelableStatus status) {
final Bundle args = new Bundle();
args.putParcelable(EXTRA_STATUS, status);
final RetweetQuoteDialogFragment f = new RetweetQuoteDialogFragment();
f.setArguments(args);
f.show(fm, FRAGMENT_TAG);
return f;
}
public static class QuoteProtectedStatusWarnFragment extends BaseDialogFragment implements
DialogInterface.OnClickListener {
@Override
public void onClick(final DialogInterface dialog, final int which) {
final RetweetQuoteDialogFragment fragment = (RetweetQuoteDialogFragment) getParentFragment();
switch (which) {
case DialogInterface.BUTTON_POSITIVE: {
final Bundle args = getArguments();
ParcelableAccount account = args.getParcelable(EXTRA_ACCOUNT);
ParcelableStatus status = args.getParcelable(EXTRA_STATUS);
if (fragment.retweetOrQuote(account, status, false)) {
fragment.dismiss();
}
break;
}
}
}
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(R.string.quote_protected_status_warning_message);
builder.setPositiveButton(R.string.send_anyway, this);
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
public static QuoteProtectedStatusWarnFragment show(RetweetQuoteDialogFragment pf,
ParcelableAccount account,
ParcelableStatus status) {
QuoteProtectedStatusWarnFragment f = new QuoteProtectedStatusWarnFragment();
Bundle args = new Bundle();
args.putParcelable(EXTRA_ACCOUNT, account);
args.putParcelable(EXTRA_STATUS, status);
f.setArguments(args);
f.show(pf.getChildFragmentManager(), "quote_protected_status_warning");
return f;
}
}
}

View File

@ -0,0 +1,320 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.annotation.CheckResult
import android.support.v4.app.FragmentManager
import android.support.v7.app.AlertDialog
import android.support.v7.widget.PopupMenu
import android.text.Editable
import android.text.TextWatcher
import android.view.Gravity
import android.view.View
import android.widget.EditText
import com.twitter.Validator
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.DummyItemAdapter
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_QUICK_SEND
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.util.ParcelableAccountUtils
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils
import org.mariotaku.twidere.service.BackgroundOperationService
import org.mariotaku.twidere.util.EditTextEnterHandler
import org.mariotaku.twidere.util.LinkCreator
import org.mariotaku.twidere.util.TwidereBugReporter
import org.mariotaku.twidere.util.TwidereValidator
import org.mariotaku.twidere.util.Utils.isMyRetweet
import org.mariotaku.twidere.view.ComposeEditText
import org.mariotaku.twidere.view.StatusTextCountView
import org.mariotaku.twidere.view.holder.StatusViewHolder
class RetweetQuoteDialogFragment : BaseDialogFragment() {
private var popupMenu: PopupMenu? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context)
val context = builder.context
val status = status!!
val credentials = ParcelableCredentialsUtils.getCredentials(getContext(),
status.account_key)!!
builder.setView(R.layout.dialog_status_quote_retweet)
builder.setTitle(R.string.retweet_quote_confirm_title)
builder.setPositiveButton(R.string.retweet, null)
builder.setNegativeButton(android.R.string.cancel, null)
builder.setNeutralButton(R.string.quote) { dialog, which ->
val intent = Intent(INTENT_ACTION_QUOTE)
val menu = popupMenu!!.menu
val quoteOriginalStatus = menu.findItem(R.id.quote_original_status)
intent.putExtra(EXTRA_STATUS, status)
intent.putExtra(EXTRA_QUOTE_ORIGINAL_STATUS, quoteOriginalStatus.isChecked)
startActivity(intent)
}
val dialog = builder.create()
dialog.setOnShowListener {
val alertDialog = it as AlertDialog
val itemContent = alertDialog.findViewById(R.id.itemContent)
val textCountView = alertDialog.findViewById(R.id.comment_text_count) as StatusTextCountView?
val itemMenu = alertDialog.findViewById(R.id.itemMenu)
val actionButtons = alertDialog.findViewById(R.id.actionButtons)
val commentContainer = alertDialog.findViewById(R.id.comment_container)
val editComment = alertDialog.findViewById(R.id.edit_comment) as ComposeEditText?
val commentMenu = alertDialog.findViewById(R.id.comment_menu)
assert(itemContent != null && textCountView != null && itemMenu != null
&& actionButtons != null && commentContainer != null && editComment != null
&& commentMenu != null)
val adapter = DummyItemAdapter(context)
adapter.setShouldShowAccountsColor(true)
val holder = StatusViewHolder(adapter, itemContent!!)
holder.displayStatus(status, false, true)
textCountView!!.maxLength = TwidereValidator.getTextLimit(credentials)
itemMenu!!.visibility = View.GONE
actionButtons!!.visibility = View.GONE
itemContent.isFocusable = false
val useQuote = useQuote(!status.user_is_protected, credentials)
commentContainer!!.visibility = if (useQuote) View.VISIBLE else View.GONE
editComment!!.setAccountKey(status.account_key)
val sendByEnter = preferences.getBoolean(KEY_QUICK_SEND)
val enterHandler = EditTextEnterHandler.attach(editComment, object : EditTextEnterHandler.EnterListener {
override fun shouldCallListener(): Boolean {
return true
}
override fun onHitEnter(): Boolean {
if (retweetOrQuote(credentials, status, SHOW_PROTECTED_CONFIRM)) {
dismiss()
return true
}
return false
}
}, sendByEnter)
enterHandler.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
updateTextCount(getDialog(), s, status, credentials)
}
override fun afterTextChanged(s: Editable) {
}
})
popupMenu = PopupMenu(context, commentMenu!!, Gravity.NO_GRAVITY,
R.attr.actionOverflowMenuStyle, 0)
commentMenu.setOnClickListener(View.OnClickListener { popupMenu!!.show() })
commentMenu.setOnTouchListener(popupMenu!!.dragToOpenListener)
popupMenu!!.inflate(R.menu.menu_dialog_comment)
val menu = popupMenu!!.menu
menu.setItemAvailability(R.id.quote_original_status,
status.retweet_id != null || status.quoted_id != null)
popupMenu!!.setOnMenuItemClickListener(PopupMenu.OnMenuItemClickListener { item ->
if (item.isCheckable) {
item.isChecked = !item.isChecked
return@OnMenuItemClickListener true
}
false
})
alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
var dismissDialog = false
if (editComment.length() > 0) {
dismissDialog = retweetOrQuote(credentials, status, SHOW_PROTECTED_CONFIRM)
} else if (isMyRetweet(status)) {
twitterWrapper.cancelRetweetAsync(status.account_key, status.id, status.my_retweet_id)
dismissDialog = true
} else if (useQuote(!status.user_is_protected, credentials)) {
dismissDialog = retweetOrQuote(credentials, status, SHOW_PROTECTED_CONFIRM)
} else {
TwidereBugReporter.logException(IllegalStateException(status.toString()))
}
if (dismissDialog) {
dismiss()
}
}
updateTextCount(alertDialog, editComment.text, status, credentials)
}
return dialog
}
private fun updateTextCount(dialog: DialogInterface, s: CharSequence, status: ParcelableStatus, credentials: ParcelableCredentials) {
if (dialog !is AlertDialog) return
val positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) ?: return
if (s.length > 0) {
positiveButton.setText(R.string.comment)
positiveButton.isEnabled = true
} else if (isMyRetweet(status)) {
positiveButton.setText(R.string.cancel_retweet)
positiveButton.isEnabled = true
} else if (useQuote(false, credentials)) {
positiveButton.setText(R.string.retweet)
positiveButton.isEnabled = true
} else {
positiveButton.setText(R.string.retweet)
positiveButton.isEnabled = !status.user_is_protected
}
val textCountView = (dialog.findViewById(R.id.comment_text_count) as StatusTextCountView?)!!
textCountView.textCount = validator.getTweetLength(s.toString())
}
private val status: ParcelableStatus?
get() {
val args = arguments
if (!args.containsKey(EXTRA_STATUS)) return null
return args.getParcelable<ParcelableStatus>(EXTRA_STATUS)
}
@CheckResult
private fun retweetOrQuote(account: ParcelableAccount, status: ParcelableStatus,
showProtectedConfirmation: Boolean): Boolean {
val twitter = twitterWrapper
val dialog = dialog ?: return false
val editComment = dialog.findViewById(R.id.edit_comment) as EditText
if (useQuote(editComment.length() > 0, account)) {
val menu = popupMenu!!.menu
val itemQuoteOriginalStatus = menu.findItem(R.id.quote_original_status)
val statusLink: Uri
val quoteOriginalStatus = itemQuoteOriginalStatus.isChecked
var commentText: String
val update = ParcelableStatusUpdate()
update.accounts = arrayOf(account)
val editingComment = editComment.text.toString()
when (ParcelableAccountUtils.getAccountType(account)) {
ParcelableAccount.Type.FANFOU -> {
if (!status.is_quote || !quoteOriginalStatus) {
if (status.user_is_protected && showProtectedConfirmation) {
QuoteProtectedStatusWarnFragment.show(this, account, status)
return false
}
update.repost_status_id = status.id
commentText = getString(R.string.fanfou_repost_format, editingComment,
status.user_screen_name, status.text_plain)
} else {
if (status.quoted_user_is_protected && showProtectedConfirmation) {
return false
}
commentText = getString(R.string.fanfou_repost_format, editingComment,
status.quoted_user_screen_name, status.quoted_text_plain)
update.repost_status_id = status.quoted_id
}
if (commentText.length > Validator.MAX_TWEET_LENGTH) {
commentText = commentText.substring(0, Math.max(Validator.MAX_TWEET_LENGTH,
editingComment.length))
}
}
else -> {
if (!status.is_quote || !quoteOriginalStatus) {
statusLink = LinkCreator.getStatusWebLink(status)
} else {
statusLink = LinkCreator.getQuotedStatusWebLink(status)
}
update.attachment_url = statusLink.toString()
commentText = editingComment
}
}
update.text = commentText
update.is_possibly_sensitive = status.is_possibly_sensitive
BackgroundOperationService.updateStatusesAsync(context, Draft.Action.QUOTE, update)
} else {
twitter.retweetStatusAsync(status.account_key, status.id)
}
return true
}
private fun useQuote(preCondition: Boolean, account: ParcelableAccount): Boolean {
return preCondition || ParcelableAccount.Type.FANFOU == account.account_type
}
class QuoteProtectedStatusWarnFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
val fragment = parentFragment as RetweetQuoteDialogFragment
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val args = arguments
val account = args.getParcelable<ParcelableAccount>(EXTRA_ACCOUNT)
val status = args.getParcelable<ParcelableStatus>(EXTRA_STATUS)
if (fragment.retweetOrQuote(account, status, false)) {
fragment.dismiss()
}
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = activity
val builder = AlertDialog.Builder(context)
builder.setMessage(R.string.quote_protected_status_warning_message)
builder.setPositiveButton(R.string.send_anyway, this)
builder.setNegativeButton(android.R.string.cancel, null)
return builder.create()
}
companion object {
fun show(pf: RetweetQuoteDialogFragment,
account: ParcelableAccount,
status: ParcelableStatus): QuoteProtectedStatusWarnFragment {
val f = QuoteProtectedStatusWarnFragment()
val args = Bundle()
args.putParcelable(EXTRA_ACCOUNT, account)
args.putParcelable(EXTRA_STATUS, status)
f.arguments = args
f.show(pf.childFragmentManager, "quote_protected_status_warning")
return f
}
}
}
companion object {
val FRAGMENT_TAG = "retweet_quote"
private val SHOW_PROTECTED_CONFIRM = java.lang.Boolean.parseBoolean("false")
fun show(fm: FragmentManager, status: ParcelableStatus): RetweetQuoteDialogFragment {
val args = Bundle()
args.putParcelable(EXTRA_STATUS, status)
val f = RetweetQuoteDialogFragment()
f.arguments = args
f.show(fm, FRAGMENT_TAG)
return f
}
}
}

View File

@ -459,7 +459,7 @@ class StatusFragment : BaseSupportFragment(), LoaderCallbacks<SingleResponse<Par
}
override fun onPrepareOptionsMenu(menu: Menu?) {
MenuUtils.setMenuItemAvailability(menu, R.id.current_status, adapter!!.status != null)
MenuUtils.setItemAvailability(menu, R.id.current_status, adapter!!.status != null)
super.onPrepareOptionsMenu(menu)
}
@ -753,7 +753,7 @@ class StatusFragment : BaseSupportFragment(), LoaderCallbacks<SingleResponse<Par
val twitter = adapter.twitterWrapper
val nameFirst = adapter.nameFirst
linkClickHandler.setStatus(status)
linkClickHandler.status = status
if (status.retweet_id != null) {
val retweetedBy = UserColorNameManager.decideDisplayName(status.retweet_user_nickname,
@ -1357,11 +1357,14 @@ class StatusFragment : BaseSupportFragment(), LoaderCallbacks<SingleResponse<Par
}
}
private class DetailStatusLinkClickHandler(context: Context, manager: MultiSelectManager,
private val adapter: StatusAdapter,
preferences: SharedPreferencesWrapper) : StatusLinkClickHandler(context, manager, preferences) {
private class DetailStatusLinkClickHandler(
context: Context,
manager: MultiSelectManager,
private val adapter: StatusAdapter,
preferences: SharedPreferencesWrapper
) : StatusLinkClickHandler(context, manager, preferences) {
override fun onLinkClick(link: String, orig: String, accountKey: UserKey,
override fun onLinkClick(link: String, orig: String?, accountKey: UserKey,
extraId: Long, type: Int, sensitive: Boolean, start: Int, end: Int): Boolean {
val current = getCurrentMedia(link, extraId.toInt())
if (current != null && !current.open_browser) {

View File

@ -743,22 +743,22 @@ class UserFragment : BaseSupportFragment(), OnClickListener, OnLinkClickListener
user.name, user.screen_name, mNameFirst)
mentionItem.title = getString(R.string.mention_user_name, displayName)
}
MenuUtils.setMenuItemAvailability(menu, R.id.mention, !isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.incoming_friendships, isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.saved_searches, isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.scheduled_statuses, isMyself && MicroBlogAPIFactory.getOfficialKeyType(activity, user.account_key) == ConsumerKeyType.TWEETDECK)
MenuUtils.setMenuItemAvailability(menu, R.id.muted_users, isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.blocked_users, isMyself)
MenuUtils.setItemAvailability(menu, R.id.mention, !isMyself)
MenuUtils.setItemAvailability(menu, R.id.incoming_friendships, isMyself)
MenuUtils.setItemAvailability(menu, R.id.saved_searches, isMyself)
MenuUtils.setItemAvailability(menu, R.id.scheduled_statuses, isMyself && MicroBlogAPIFactory.getOfficialKeyType(activity, user.account_key) == ConsumerKeyType.TWEETDECK)
MenuUtils.setItemAvailability(menu, R.id.muted_users, isMyself)
MenuUtils.setItemAvailability(menu, R.id.blocked_users, isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.block, !isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.mute_user, !isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.report_spam, !isMyself)
MenuUtils.setMenuItemAvailability(menu, R.id.enable_retweets, !isMyself)
MenuUtils.setItemAvailability(menu, R.id.block, !isMyself)
MenuUtils.setItemAvailability(menu, R.id.mute_user, !isMyself)
MenuUtils.setItemAvailability(menu, R.id.report_spam, !isMyself)
MenuUtils.setItemAvailability(menu, R.id.enable_retweets, !isMyself)
if (mAccount != null) {
MenuUtils.setMenuItemAvailability(menu, R.id.add_to_list, TextUtils.equals(ParcelableAccount.Type.TWITTER,
MenuUtils.setItemAvailability(menu, R.id.add_to_list, TextUtils.equals(ParcelableAccount.Type.TWITTER,
ParcelableAccountUtils.getAccountType(mAccount!!)))
} else {
MenuUtils.setMenuItemAvailability(menu, R.id.add_to_list, false)
MenuUtils.setItemAvailability(menu, R.id.add_to_list, false)
}
val userRelationship = mRelationship
@ -769,10 +769,10 @@ class UserFragment : BaseSupportFragment(), OnClickListener, OnLinkClickListener
filterItem.isChecked = userRelationship.filtering
}
if (isMyself) {
MenuUtils.setMenuItemAvailability(menu, R.id.send_direct_message, false)
MenuUtils.setItemAvailability(menu, R.id.send_direct_message, false)
} else {
MenuUtils.setMenuItemAvailability(menu, R.id.send_direct_message, userRelationship.can_dm)
MenuUtils.setMenuItemAvailability(menu, R.id.block, true)
MenuUtils.setItemAvailability(menu, R.id.send_direct_message, userRelationship.can_dm)
MenuUtils.setItemAvailability(menu, R.id.block, true)
val blockItem = menu.findItem(R.id.block)
if (blockItem != null) {
ActionIconDrawable.setMenuHighlight(blockItem, TwidereMenuInfo(userRelationship.blocking))
@ -788,7 +788,7 @@ class UserFragment : BaseSupportFragment(), OnClickListener, OnLinkClickListener
}
}
} else {
MenuUtils.setMenuItemAvailability(menu, R.id.send_direct_message, false)
MenuUtils.setItemAvailability(menu, R.id.send_direct_message, false)
}
val intent = Intent(INTENT_ACTION_EXTENSION_OPEN_USER)
val extras = Bundle()

View File

@ -178,15 +178,15 @@ class UserListFragment : AbsToolbarTabPagesFragment(), OnClickListener, LoaderCa
override fun onPrepareOptionsMenu(menu: Menu?) {
val userList = this.userList
MenuUtils.setMenuItemAvailability(menu, R.id.info, userList != null)
MenuUtils.setItemAvailability(menu, R.id.info, userList != null)
menu!!.removeGroup(MENU_GROUP_USER_LIST_EXTENSION)
if (userList != null) {
val isMyList = userList.user_key == userList.account_key
val isFollowing = userList.is_following
MenuUtils.setMenuItemAvailability(menu, R.id.edit, isMyList)
MenuUtils.setMenuItemAvailability(menu, R.id.follow, !isMyList)
MenuUtils.setMenuItemAvailability(menu, R.id.add, isMyList)
MenuUtils.setMenuItemAvailability(menu, R.id.delete, isMyList)
MenuUtils.setItemAvailability(menu, R.id.edit, isMyList)
MenuUtils.setItemAvailability(menu, R.id.follow, !isMyList)
MenuUtils.setItemAvailability(menu, R.id.add, isMyList)
MenuUtils.setItemAvailability(menu, R.id.delete, isMyList)
val followItem = menu.findItem(R.id.follow)
if (isFollowing) {
followItem.setIcon(R.drawable.ic_action_cancel)
@ -200,10 +200,10 @@ class UserListFragment : AbsToolbarTabPagesFragment(), OnClickListener, LoaderCa
extensionsIntent.putExtra(EXTRA_USER_LIST, userList)
MenuUtils.addIntentToMenu(activity, menu, extensionsIntent, MENU_GROUP_USER_LIST_EXTENSION)
} else {
MenuUtils.setMenuItemAvailability(menu, R.id.edit, false)
MenuUtils.setMenuItemAvailability(menu, R.id.follow, false)
MenuUtils.setMenuItemAvailability(menu, R.id.add, false)
MenuUtils.setMenuItemAvailability(menu, R.id.delete, false)
MenuUtils.setItemAvailability(menu, R.id.edit, false)
MenuUtils.setItemAvailability(menu, R.id.follow, false)
MenuUtils.setItemAvailability(menu, R.id.add, false)
MenuUtils.setItemAvailability(menu, R.id.delete, false)
}
}

View File

@ -73,9 +73,9 @@ class UserListsFragment : ParcelableUserListsFragment() {
val accountId = accountKey
if (accountId == null || item == null) return
if (accountId == userId) {
MenuUtils.setMenuItemAvailability(menu, R.id.new_user_list, true)
MenuUtils.setItemAvailability(menu, R.id.new_user_list, true)
} else {
MenuUtils.setMenuItemAvailability(menu, R.id.new_user_list, Utils.isMyAccount(activity, screenName))
MenuUtils.setItemAvailability(menu, R.id.new_user_list, Utils.isMyAccount(activity, screenName))
}
}

View File

@ -393,11 +393,11 @@ class UserProfileEditorFragment : BaseSupportFragment(), OnSizeChangedListener,
val account = result.extras.getParcelable<ParcelableAccount>(EXTRA_ACCOUNT)
if (account != null) {
val task = UpdateAccountInfoTask(activity)
task.setParams(Pair(account, result.data))
task.params = Pair(account, result.data)
TaskStarter.execute(task)
}
}
callback!!.executeAfterFragmentResumed { fragment ->
callback?.executeAfterFragmentResumed { fragment ->
val f = (fragment as UserProfileEditorFragment).fragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG)
if (f is DialogFragment) {
f.dismissAllowingStateLoss()

View File

@ -192,7 +192,7 @@ public class CardPollFragment extends BaseSupportFragment implements
return null;
}
};
task.setResultHandler(CardPollFragment.this);
task.setCallback(CardPollFragment.this);
task.setParams(cardData);
TaskStarter.execute(task);
}

View File

@ -386,7 +386,7 @@ public class BackgroundOperationService extends IntentService implements Constan
}
});
task.setResultHandler(this);
task.setCallback(this);
task.setParams(Pair.create(actionType, item));
mHandler.post(new Runnable() {
@Override

View File

@ -519,7 +519,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
}
}
}.setResultHandler(bus));
}.setCallback(bus));
}
public void getActivitiesAboutMeAsync(final RefreshTaskParam param) {

View File

@ -1,78 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import org.mariotaku.twidere.fragment.PhishingLinkWarningDialogFragment;
import static org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME;
import static org.mariotaku.twidere.constant.IntentConstants.EXTRA_URI;
import static org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_PHISHING_LINK_WARNING;
public class DirectMessageOnLinkClickHandler extends OnLinkClickHandler {
private static final String[] SHORT_LINK_SERVICES = new String[]{"bit.ly", "ow.ly", "tinyurl.com", "goo.gl",
"k6.kz", "is.gd", "tr.im", "x.co", "weepp.ru"};
public DirectMessageOnLinkClickHandler(final Context context, final MultiSelectManager manager,
SharedPreferencesWrapper preferences) {
super(context, manager, preferences);
}
@Override
protected boolean isPrivateData() {
return true;
}
@Override
protected void openLink(final String link) {
if (link == null || manager != null && manager.isActive()) return;
if (!hasShortenedLinks(link)) {
super.openLink(link);
return;
}
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
if (context instanceof FragmentActivity && prefs.getBoolean(KEY_PHISHING_LINK_WARNING, true)) {
final FragmentManager fm = ((FragmentActivity) context).getSupportFragmentManager();
final DialogFragment fragment = new PhishingLinkWarningDialogFragment();
final Bundle args = new Bundle();
args.putParcelable(EXTRA_URI, Uri.parse(link));
fragment.setArguments(args);
fragment.show(fm, "phishing_link_warning");
} else {
super.openLink(link);
}
}
private boolean hasShortenedLinks(final String link) {
for (final String shortLinkService : SHORT_LINK_SERVICES) {
if (link.contains(shortLinkService)) return true;
}
return false;
}
}

View File

@ -0,0 +1,68 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.FragmentActivity
import org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_URI
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_PHISHING_LINK_WARNING
import org.mariotaku.twidere.fragment.PhishingLinkWarningDialogFragment
class DirectMessageOnLinkClickHandler(context: Context, manager: MultiSelectManager,
preferences: SharedPreferencesWrapper) : OnLinkClickHandler(context, manager, preferences) {
override val isPrivateData: Boolean
get() = true
override fun openLink(link: String) {
if (manager != null && manager.isActive) return
if (!hasShortenedLinks(link)) {
super.openLink(link)
return
}
val prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
if (context is FragmentActivity && prefs.getBoolean(KEY_PHISHING_LINK_WARNING, true)) {
val fm = context.supportFragmentManager
val fragment = PhishingLinkWarningDialogFragment()
val args = Bundle()
args.putParcelable(EXTRA_URI, Uri.parse(link))
fragment.arguments = args
fragment.show(fm, "phishing_link_warning")
} else {
super.openLink(link)
}
}
private fun hasShortenedLinks(link: String): Boolean {
for (shortLinkService in SHORT_LINK_SERVICES) {
if (link.contains(shortLinkService)) return true
}
return false
}
companion object {
private val SHORT_LINK_SERVICES = arrayOf("bit.ly", "ow.ly", "tinyurl.com", "goo.gl", "k6.kz", "is.gd", "tr.im", "x.co", "weepp.ru")
}
}

View File

@ -35,6 +35,8 @@ import android.util.Log
import android.view.ContextMenu
import android.view.Menu
import android.view.MenuItem
import org.mariotaku.ktextension.setItemChecked
import org.mariotaku.ktextension.setMenuItemIcon
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
@ -57,288 +59,275 @@ import org.mariotaku.twidere.util.menu.TwidereMenuInfo
/**
* Created by mariotaku on 15/4/12.
*/
class MenuUtils private constructor() : Constants {
companion object {
object MenuUtils {
fun setMenuItemAvailability(menu: Menu?, id: Int, available: Boolean) {
if (menu == null) return
val item = menu.findItem(id) ?: return
item.isVisible = available
item.isEnabled = available
}
fun setItemAvailability(menu: Menu?, id: Int, available: Boolean) {
if (menu == null) return
val item = menu.findItem(id) ?: return
item.isVisible = available
item.isEnabled = available
}
fun setMenuItemChecked(menu: Menu?, id: Int, checked: Boolean) {
if (menu == null) return
val item = menu.findItem(id) ?: return
item.isChecked = checked
}
fun setItemChecked(menu: Menu?, id: Int, checked: Boolean) {
menu?.setItemChecked(id, checked);
}
fun setMenuItemIcon(menu: Menu?, id: Int, @DrawableRes icon: Int) {
if (menu == null) return
val item = menu.findItem(id) ?: return
item.setIcon(icon)
}
fun setMenuItemIcon(menu: Menu?, id: Int, @DrawableRes icon: Int) {
menu?.setMenuItemIcon(id, icon);
}
fun setMenuItemShowAsActionFlags(menu: Menu?, id: Int, flags: Int) {
if (menu == null) return
val item = menu.findItem(id) ?: return
item.setShowAsActionFlags(flags)
MenuItemCompat.setShowAsAction(item, flags)
}
fun setMenuItemTitle(menu: Menu?, id: Int, @StringRes icon: Int) {
if (menu == null) return
val item = menu.findItem(id) ?: return
item.setTitle(icon)
}
fun setMenuItemTitle(menu: Menu?, id: Int, @StringRes icon: Int) {
if (menu == null) return
val item = menu.findItem(id) ?: return
item.setTitle(icon)
}
@JvmOverloads fun addIntentToMenu(context: Context?, menu: Menu?, queryIntent: Intent?,
groupId: Int = Menu.NONE) {
if (context == null || menu == null || queryIntent == null) return
val pm = context.packageManager
val res = context.resources
val density = res.displayMetrics.density
val padding = Math.round(density * 4)
val activities = pm.queryIntentActivities(queryIntent, 0)
for (info in activities) {
val intent = Intent(queryIntent)
val icon = info.loadIcon(pm)
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name)
val item = menu.add(groupId, Menu.NONE, Menu.NONE, info.loadLabel(pm))
item.intent = intent
val iw = icon.intrinsicWidth
val ih = icon.intrinsicHeight
if (iw > 0 && ih > 0) {
val iconWithPadding = PaddingDrawable(icon, padding)
iconWithPadding.setBounds(0, 0, iw, ih)
item.icon = iconWithPadding
} else {
item.icon = icon
}
}
}
fun setupForStatus(context: Context,
preferences: SharedPreferencesWrapper,
menu: Menu,
status: ParcelableStatus,
twitter: AsyncTwitterWrapper) {
val account = ParcelableCredentialsUtils.getCredentials(context,
status.account_key) ?: return
setupForStatus(context, preferences, menu, status, account, twitter)
}
@UiThread
fun setupForStatus(context: Context,
preferences: SharedPreferencesWrapper,
menu: Menu,
status: ParcelableStatus,
account: ParcelableCredentials,
twitter: AsyncTwitterWrapper) {
if (menu is ContextMenu) {
menu.setHeaderTitle(context.getString(R.string.status_menu_title_format,
UserColorNameManager.decideDisplayName(status.user_nickname, status.user_name,
status.user_screen_name, preferences.getBoolean(SharedPreferenceConstants.KEY_NAME_FIRST)),
status.text_unescaped))
}
val retweetHighlight = ContextCompat.getColor(context, R.color.highlight_retweet)
val favoriteHighlight = ContextCompat.getColor(context, R.color.highlight_favorite)
val likeHighlight = ContextCompat.getColor(context, R.color.highlight_like)
val isMyRetweet: Boolean
if (twitter.isCreatingRetweet(status.account_key, status.id)) {
isMyRetweet = true
} else if (twitter.isDestroyingStatus(status.account_key, status.id)) {
isMyRetweet = false
@JvmOverloads fun addIntentToMenu(context: Context?, menu: Menu?, queryIntent: Intent?,
groupId: Int = Menu.NONE) {
if (context == null || menu == null || queryIntent == null) return
val pm = context.packageManager
val res = context.resources
val density = res.displayMetrics.density
val padding = Math.round(density * 4)
val activities = pm.queryIntentActivities(queryIntent, 0)
for (info in activities) {
val intent = Intent(queryIntent)
val icon = info.loadIcon(pm)
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name)
val item = menu.add(groupId, Menu.NONE, Menu.NONE, info.loadLabel(pm))
item.intent = intent
val iw = icon.intrinsicWidth
val ih = icon.intrinsicHeight
if (iw > 0 && ih > 0) {
val iconWithPadding = PaddingDrawable(icon, padding)
iconWithPadding.setBounds(0, 0, iw, ih)
item.icon = iconWithPadding
} else {
isMyRetweet = status.retweeted || Utils.isMyRetweet(status)
item.icon = icon
}
val delete = menu.findItem(R.id.delete)
if (delete != null) {
delete.isVisible = Utils.isMyStatus(status)
}
val retweet = menu.findItem(R.id.retweet)
if (retweet != null) {
ActionIconDrawable.setMenuHighlight(retweet, TwidereMenuInfo(isMyRetweet, retweetHighlight))
retweet.setTitle(if (isMyRetweet) R.string.cancel_retweet else R.string.retweet)
}
val favorite = menu.findItem(R.id.favorite)
if (favorite != null) {
val isFavorite: Boolean
if (twitter.isCreatingFavorite(status.account_key, status.id)) {
isFavorite = true
} else if (twitter.isDestroyingFavorite(status.account_key, status.id)) {
isFavorite = false
} else {
isFavorite = status.is_favorite
}
val provider = MenuItemCompat.getActionProvider(favorite)
val useStar = preferences.getBoolean(SharedPreferenceConstants.KEY_I_WANT_MY_STARS_BACK)
if (provider is FavoriteItemProvider) {
provider.setIsFavorite(favorite, isFavorite)
} else {
if (useStar) {
val oldIcon = favorite.icon
if (oldIcon is ActionIconDrawable) {
val starIcon = ContextCompat.getDrawable(context, R.drawable.ic_action_star)
favorite.icon = ActionIconDrawable(starIcon, oldIcon.defaultColor)
} else {
favorite.setIcon(R.drawable.ic_action_star)
}
ActionIconDrawable.setMenuHighlight(favorite, TwidereMenuInfo(isFavorite, favoriteHighlight))
} else {
ActionIconDrawable.setMenuHighlight(favorite, TwidereMenuInfo(isFavorite, likeHighlight))
}
}
if (useStar) {
favorite.setTitle(if (isFavorite) R.string.unfavorite else R.string.favorite)
} else {
favorite.setTitle(if (isFavorite) R.string.undo_like else R.string.like)
}
}
val translate = menu.findItem(R.id.translate)
if (translate != null) {
val isOfficialKey = Utils.isOfficialCredentials(context, account)
val prefs = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
setMenuItemAvailability(menu, R.id.translate, isOfficialKey)
}
menu.removeGroup(Constants.MENU_GROUP_STATUS_EXTENSION)
Utils.addIntentToMenuForExtension(context, menu, Constants.MENU_GROUP_STATUS_EXTENSION, INTENT_ACTION_EXTENSION_OPEN_STATUS,
EXTRA_STATUS, EXTRA_STATUS_JSON, status)
val shareItem = menu.findItem(R.id.share)
val shareProvider = MenuItemCompat.getActionProvider(shareItem)
if (shareProvider is SupportStatusShareProvider) {
shareProvider.status = status
} else if (shareProvider is ShareActionProvider) {
val shareIntent = Utils.createStatusShareIntent(context, status)
shareProvider.setShareIntent(shareIntent)
} else if (shareItem.hasSubMenu()) {
val shareSubMenu = shareItem.subMenu
val shareIntent = Utils.createStatusShareIntent(context, status)
shareSubMenu.removeGroup(Constants.MENU_GROUP_STATUS_SHARE)
addIntentToMenu(context, shareSubMenu, shareIntent, Constants.MENU_GROUP_STATUS_SHARE)
} else {
val shareIntent = Utils.createStatusShareIntent(context, status)
val chooserIntent = Intent.createChooser(shareIntent, context.getString(R.string.share_status))
Utils.addCopyLinkIntent(context, chooserIntent, LinkCreator.getStatusWebLink(status))
shareItem.intent = chooserIntent
}
}
fun handleStatusClick(context: Context,
fragment: Fragment?,
fm: FragmentManager,
colorNameManager: UserColorNameManager,
twitter: AsyncTwitterWrapper,
status: ParcelableStatus,
item: MenuItem): Boolean {
when (item.itemId) {
R.id.copy -> {
if (ClipboardUtils.setText(context, status.text_plain)) {
Utils.showOkMessage(context, R.string.text_copied, false)
}
}
R.id.retweet -> {
if (Utils.isMyRetweet(status)) {
twitter.cancelRetweetAsync(status.account_key,
status.id, status.my_retweet_id)
} else {
twitter.retweetStatusAsync(status.account_key,
status.id)
}
}
R.id.quote -> {
val intent = Intent(INTENT_ACTION_QUOTE)
intent.putExtra(EXTRA_STATUS, status)
context.startActivity(intent)
}
R.id.reply -> {
val intent = Intent(INTENT_ACTION_REPLY)
intent.putExtra(EXTRA_STATUS, status)
context.startActivity(intent)
}
R.id.favorite -> {
if (status.is_favorite) {
twitter.destroyFavoriteAsync(status.account_key, status.id)
} else {
val provider = MenuItemCompat.getActionProvider(item)
if (provider is FavoriteItemProvider) {
provider.invokeItem(item,
AbsStatusesFragment.DefaultOnLikedListener(twitter, status))
} else {
twitter.createFavoriteAsync(status.account_key, status.id)
}
}
}
R.id.delete -> {
DestroyStatusDialogFragment.show(fm, status)
}
R.id.add_to_filter -> {
AddStatusFilterDialogFragment.show(fm, status)
}
R.id.set_color -> {
val intent = Intent(context, ColorPickerDialogActivity::class.java)
val color = colorNameManager.getUserColor(status.user_key)
if (color != 0) {
intent.putExtra(EXTRA_COLOR, color)
}
intent.putExtra(EXTRA_CLEAR_BUTTON, color != 0)
intent.putExtra(EXTRA_ALPHA_SLIDER, false)
if (fragment != null) {
fragment.startActivityForResult(intent, REQUEST_SET_COLOR)
} else if (context is Activity) {
context.startActivityForResult(intent, REQUEST_SET_COLOR)
}
}
R.id.clear_nickname -> {
colorNameManager.clearUserNickname(status.user_key)
}
R.id.set_nickname -> {
val nick = colorNameManager.getUserNickname(status.user_key)
val df = SetUserNicknameDialogFragment.show(fm,
status.user_key, nick)
if (fragment != null) {
df.setTargetFragment(fragment, REQUEST_SET_NICKNAME)
}
}
R.id.open_with_account -> {
val intent = Intent(INTENT_ACTION_SELECT_ACCOUNT)
intent.setClass(context, AccountSelectorActivity::class.java)
intent.putExtra(EXTRA_SINGLE_SELECTION, true)
intent.putExtra(EXTRA_ACCOUNT_HOST, status.user_key.host)
if (fragment != null) {
fragment.startActivityForResult(intent, REQUEST_SELECT_ACCOUNT)
} else if (context is Activity) {
context.startActivityForResult(intent, REQUEST_SELECT_ACCOUNT)
}
}
R.id.open_in_browser -> {
val uri = LinkCreator.getStatusWebLink(status)
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.`package` = IntentUtils.getDefaultBrowserPackage(context, uri, true)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
intent.`package` = null
context.startActivity(Intent.createChooser(intent,
context.getString(R.string.open_in_browser)))
}
}
else -> {
if (item.intent != null) {
try {
context.startActivity(item.intent)
} catch (e: ActivityNotFoundException) {
Log.w(LOGTAG, e)
return false
}
}
}
}
return true
}
}
fun setupForStatus(context: Context,
preferences: SharedPreferencesWrapper,
menu: Menu,
status: ParcelableStatus,
twitter: AsyncTwitterWrapper) {
val account = ParcelableCredentialsUtils.getCredentials(context,
status.account_key) ?: return
setupForStatus(context, preferences, menu, status, account, twitter)
}
@UiThread
fun setupForStatus(context: Context,
preferences: SharedPreferencesWrapper,
menu: Menu,
status: ParcelableStatus,
account: ParcelableCredentials,
twitter: AsyncTwitterWrapper) {
if (menu is ContextMenu) {
menu.setHeaderTitle(context.getString(R.string.status_menu_title_format,
UserColorNameManager.decideDisplayName(status.user_nickname, status.user_name,
status.user_screen_name, preferences.getBoolean(SharedPreferenceConstants.KEY_NAME_FIRST)),
status.text_unescaped))
}
val retweetHighlight = ContextCompat.getColor(context, R.color.highlight_retweet)
val favoriteHighlight = ContextCompat.getColor(context, R.color.highlight_favorite)
val likeHighlight = ContextCompat.getColor(context, R.color.highlight_like)
val isMyRetweet: Boolean
if (twitter.isCreatingRetweet(status.account_key, status.id)) {
isMyRetweet = true
} else if (twitter.isDestroyingStatus(status.account_key, status.id)) {
isMyRetweet = false
} else {
isMyRetweet = status.retweeted || Utils.isMyRetweet(status)
}
val delete = menu.findItem(R.id.delete)
if (delete != null) {
delete.isVisible = Utils.isMyStatus(status)
}
val retweet = menu.findItem(R.id.retweet)
if (retweet != null) {
ActionIconDrawable.setMenuHighlight(retweet, TwidereMenuInfo(isMyRetweet, retweetHighlight))
retweet.setTitle(if (isMyRetweet) R.string.cancel_retweet else R.string.retweet)
}
val favorite = menu.findItem(R.id.favorite)
if (favorite != null) {
val isFavorite: Boolean
if (twitter.isCreatingFavorite(status.account_key, status.id)) {
isFavorite = true
} else if (twitter.isDestroyingFavorite(status.account_key, status.id)) {
isFavorite = false
} else {
isFavorite = status.is_favorite
}
val provider = MenuItemCompat.getActionProvider(favorite)
val useStar = preferences.getBoolean(SharedPreferenceConstants.KEY_I_WANT_MY_STARS_BACK)
if (provider is FavoriteItemProvider) {
provider.setIsFavorite(favorite, isFavorite)
} else {
if (useStar) {
val oldIcon = favorite.icon
if (oldIcon is ActionIconDrawable) {
val starIcon = ContextCompat.getDrawable(context, R.drawable.ic_action_star)
favorite.icon = ActionIconDrawable(starIcon, oldIcon.defaultColor)
} else {
favorite.setIcon(R.drawable.ic_action_star)
}
ActionIconDrawable.setMenuHighlight(favorite, TwidereMenuInfo(isFavorite, favoriteHighlight))
} else {
ActionIconDrawable.setMenuHighlight(favorite, TwidereMenuInfo(isFavorite, likeHighlight))
}
}
if (useStar) {
favorite.setTitle(if (isFavorite) R.string.unfavorite else R.string.favorite)
} else {
favorite.setTitle(if (isFavorite) R.string.undo_like else R.string.like)
}
}
val translate = menu.findItem(R.id.translate)
if (translate != null) {
val isOfficialKey = Utils.isOfficialCredentials(context, account)
val prefs = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
setItemAvailability(menu, R.id.translate, isOfficialKey)
}
menu.removeGroup(Constants.MENU_GROUP_STATUS_EXTENSION)
Utils.addIntentToMenuForExtension(context, menu, Constants.MENU_GROUP_STATUS_EXTENSION, INTENT_ACTION_EXTENSION_OPEN_STATUS,
EXTRA_STATUS, EXTRA_STATUS_JSON, status)
val shareItem = menu.findItem(R.id.share)
val shareProvider = MenuItemCompat.getActionProvider(shareItem)
if (shareProvider is SupportStatusShareProvider) {
shareProvider.status = status
} else if (shareProvider is ShareActionProvider) {
val shareIntent = Utils.createStatusShareIntent(context, status)
shareProvider.setShareIntent(shareIntent)
} else if (shareItem.hasSubMenu()) {
val shareSubMenu = shareItem.subMenu
val shareIntent = Utils.createStatusShareIntent(context, status)
shareSubMenu.removeGroup(Constants.MENU_GROUP_STATUS_SHARE)
addIntentToMenu(context, shareSubMenu, shareIntent, Constants.MENU_GROUP_STATUS_SHARE)
} else {
val shareIntent = Utils.createStatusShareIntent(context, status)
val chooserIntent = Intent.createChooser(shareIntent, context.getString(R.string.share_status))
Utils.addCopyLinkIntent(context, chooserIntent, LinkCreator.getStatusWebLink(status))
shareItem.intent = chooserIntent
}
}
fun handleStatusClick(context: Context,
fragment: Fragment?,
fm: FragmentManager,
colorNameManager: UserColorNameManager,
twitter: AsyncTwitterWrapper,
status: ParcelableStatus,
item: MenuItem): Boolean {
when (item.itemId) {
R.id.copy -> {
if (ClipboardUtils.setText(context, status.text_plain)) {
Utils.showOkMessage(context, R.string.text_copied, false)
}
}
R.id.retweet -> {
if (Utils.isMyRetweet(status)) {
twitter.cancelRetweetAsync(status.account_key,
status.id, status.my_retweet_id)
} else {
twitter.retweetStatusAsync(status.account_key,
status.id)
}
}
R.id.quote -> {
val intent = Intent(INTENT_ACTION_QUOTE)
intent.putExtra(EXTRA_STATUS, status)
context.startActivity(intent)
}
R.id.reply -> {
val intent = Intent(INTENT_ACTION_REPLY)
intent.putExtra(EXTRA_STATUS, status)
context.startActivity(intent)
}
R.id.favorite -> {
if (status.is_favorite) {
twitter.destroyFavoriteAsync(status.account_key, status.id)
} else {
val provider = MenuItemCompat.getActionProvider(item)
if (provider is FavoriteItemProvider) {
provider.invokeItem(item,
AbsStatusesFragment.DefaultOnLikedListener(twitter, status))
} else {
twitter.createFavoriteAsync(status.account_key, status.id)
}
}
}
R.id.delete -> {
DestroyStatusDialogFragment.show(fm, status)
}
R.id.add_to_filter -> {
AddStatusFilterDialogFragment.show(fm, status)
}
R.id.set_color -> {
val intent = Intent(context, ColorPickerDialogActivity::class.java)
val color = colorNameManager.getUserColor(status.user_key)
if (color != 0) {
intent.putExtra(EXTRA_COLOR, color)
}
intent.putExtra(EXTRA_CLEAR_BUTTON, color != 0)
intent.putExtra(EXTRA_ALPHA_SLIDER, false)
if (fragment != null) {
fragment.startActivityForResult(intent, REQUEST_SET_COLOR)
} else if (context is Activity) {
context.startActivityForResult(intent, REQUEST_SET_COLOR)
}
}
R.id.clear_nickname -> {
colorNameManager.clearUserNickname(status.user_key)
}
R.id.set_nickname -> {
val nick = colorNameManager.getUserNickname(status.user_key)
val df = SetUserNicknameDialogFragment.show(fm,
status.user_key, nick)
if (fragment != null) {
df.setTargetFragment(fragment, REQUEST_SET_NICKNAME)
}
}
R.id.open_with_account -> {
val intent = Intent(INTENT_ACTION_SELECT_ACCOUNT)
intent.setClass(context, AccountSelectorActivity::class.java)
intent.putExtra(EXTRA_SINGLE_SELECTION, true)
intent.putExtra(EXTRA_ACCOUNT_HOST, status.user_key.host)
if (fragment != null) {
fragment.startActivityForResult(intent, REQUEST_SELECT_ACCOUNT)
} else if (context is Activity) {
context.startActivityForResult(intent, REQUEST_SELECT_ACCOUNT)
}
}
R.id.open_in_browser -> {
val uri = LinkCreator.getStatusWebLink(status)
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.`package` = IntentUtils.getDefaultBrowserPackage(context, uri, true)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
intent.`package` = null
context.startActivity(Intent.createChooser(intent,
context.getString(R.string.open_in_browser)))
}
}
else -> {
if (item.intent != null) {
try {
context.startActivity(item.intent)
} catch (e: ActivityNotFoundException) {
Log.w(LOGTAG, e)
return false
}
}
}
}
return true
}
}

View File

@ -1,207 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.BadParcelableException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.mariotaku.twidere.activity.WebLinkHandlerActivity;
import org.mariotaku.twidere.annotation.Referral;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.util.ParcelableMediaUtils;
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener;
import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.LinkEvent;
import static org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY;
import static org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API;
public class OnLinkClickHandler implements OnLinkClickListener {
@NonNull
protected final Context context;
@Nullable
protected final MultiSelectManager manager;
@NonNull
protected final SharedPreferencesWrapper preferences;
public OnLinkClickHandler(@NonNull final Context context, @Nullable final MultiSelectManager manager,
@NonNull SharedPreferencesWrapper preferences) {
this.context = context;
this.manager = manager;
this.preferences = preferences;
}
@Override
public boolean onLinkClick(final String link, final String orig, final UserKey accountKey,
final long extraId, final int type, final boolean sensitive,
final int start, final int end) {
if (manager != null && manager.isActive()) return false;
if (!isPrivateData()) {
// BEGIN HotMobi
final LinkEvent event = LinkEvent.create(context, link, type);
HotMobiLogger.getInstance(context).log(accountKey, event);
// END HotMobi
}
switch (type) {
case TwidereLinkify.LINK_TYPE_MENTION: {
IntentUtils.openUserProfile(context, accountKey, null, link, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.USER_MENTION);
return true;
}
case TwidereLinkify.LINK_TYPE_HASHTAG: {
IntentUtils.openTweetSearch(context, accountKey, "#" + link);
return true;
}
case TwidereLinkify.LINK_TYPE_LINK_IN_TEXT: {
if (isMedia(link, extraId)) {
openMedia(accountKey, extraId, sensitive, link, start, end);
} else {
openLink(link);
}
return true;
}
case TwidereLinkify.LINK_TYPE_ENTITY_URL: {
if (isMedia(link, extraId)) {
openMedia(accountKey, extraId, sensitive, link, start, end);
} else {
final String authority = UriUtils.getAuthority(link);
if (authority == null) {
openLink(link);
return true;
}
switch (authority) {
case "fanfou.com": {
if (orig != null) {
// Process special case for fanfou
final char ch = orig.charAt(0);
// Extend selection
final int length = orig.length();
if (TwidereLinkify.isAtSymbol(ch)) {
String id = UriUtils.getPath(link);
if (id != null) {
int idxOfSlash = id.indexOf('/');
if (idxOfSlash == 0) {
id = id.substring(1);
}
final String screenName = orig.substring(1, length);
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(id),
screenName, null, preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.USER_MENTION);
return true;
}
} else if (TwidereLinkify.isHashSymbol(ch) &&
TwidereLinkify.isHashSymbol(orig.charAt(length - 1))) {
IntentUtils.openSearch(context, accountKey, orig.substring(1, length - 1));
return true;
}
}
break;
}
default: {
if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) {
openTwitterLink(link, accountKey);
return true;
}
break;
}
}
openLink(link);
}
return true;
}
case TwidereLinkify.LINK_TYPE_LIST: {
final String[] mentionList = StringUtils.split(link, "/");
if (mentionList.length != 2) {
return false;
}
IntentUtils.openUserListDetails(context, accountKey, null, null, mentionList[0],
mentionList[1]);
return true;
}
case TwidereLinkify.LINK_TYPE_CASHTAG: {
IntentUtils.openTweetSearch(context, accountKey, link);
return true;
}
case TwidereLinkify.LINK_TYPE_USER_ID: {
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(link), null, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.USER_MENTION);
return true;
}
}
return false;
}
protected boolean isPrivateData() {
return false;
}
protected boolean isMedia(String link, long extraId) {
return PreviewMediaExtractor.isSupported(link);
}
protected void openMedia(UserKey accountKey, long extraId, boolean sensitive, String link, int start, int end) {
final ParcelableMedia[] media = {ParcelableMediaUtils.image(link)};
IntentUtils.openMedia(context, accountKey, sensitive, null, media, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API));
}
protected void openLink(final String link) {
if (manager != null && manager.isActive()) return;
final Uri uri = Uri.parse(link);
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setPackage(IntentUtils.getDefaultBrowserPackage(context, uri, true));
try {
context.startActivity(intent);
} catch (final ActivityNotFoundException e) {
// TODO
}
}
protected void openTwitterLink(final String link, final UserKey accountKey) {
if (manager != null && manager.isActive()) return;
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(context, WebLinkHandlerActivity.class);
intent.putExtra(EXTRA_ACCOUNT_KEY, accountKey);
intent.setExtrasClassLoader(TwidereApplication.class.getClassLoader());
if (intent.resolveActivity(context.getPackageManager()) != null) {
try {
context.startActivity(intent);
} catch (final BadParcelableException e) {
// Ignore
}
}
}
}

View File

@ -0,0 +1,188 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.BadParcelableException
import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.LinkEvent
import org.apache.commons.lang3.StringUtils
import org.mariotaku.twidere.activity.WebLinkHandlerActivity
import org.mariotaku.twidere.annotation.Referral
import org.mariotaku.twidere.app.TwidereApplication
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener
import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor
open class OnLinkClickHandler(
protected val context: Context,
protected val manager: MultiSelectManager?,
protected val preferences: SharedPreferencesWrapper
) : OnLinkClickListener {
override fun onLinkClick(link: String, orig: String?, accountKey: UserKey,
extraId: Long, type: Int, sensitive: Boolean,
start: Int, end: Int): Boolean {
if (manager != null && manager.isActive) return false
if (!isPrivateData) {
// BEGIN HotMobi
val event = LinkEvent.create(context, link, type)
HotMobiLogger.getInstance(context).log(accountKey, event)
// END HotMobi
}
when (type) {
TwidereLinkify.LINK_TYPE_MENTION -> {
IntentUtils.openUserProfile(context, accountKey, null, link, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.USER_MENTION)
return true
}
TwidereLinkify.LINK_TYPE_HASHTAG -> {
IntentUtils.openTweetSearch(context, accountKey, "#" + link)
return true
}
TwidereLinkify.LINK_TYPE_LINK_IN_TEXT -> {
if (isMedia(link, extraId)) {
openMedia(accountKey, extraId, sensitive, link, start, end)
} else {
openLink(link)
}
return true
}
TwidereLinkify.LINK_TYPE_ENTITY_URL -> {
if (isMedia(link, extraId)) {
openMedia(accountKey, extraId, sensitive, link, start, end)
} else {
val authority = UriUtils.getAuthority(link)
if (authority == null) {
openLink(link)
return true
}
when (authority) {
"fanfou.com" -> {
if (orig != null) {
// Process special case for fanfou
val ch = orig[0]
// Extend selection
val length = orig.length
if (TwidereLinkify.isAtSymbol(ch)) {
var id = UriUtils.getPath(link)
if (id != null) {
val idxOfSlash = id.indexOf('/')
if (idxOfSlash == 0) {
id = id.substring(1)
}
val screenName = orig.substring(1, length)
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(id),
screenName, null, preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.USER_MENTION)
return true
}
} else if (TwidereLinkify.isHashSymbol(ch) && TwidereLinkify.isHashSymbol(orig[length - 1])) {
IntentUtils.openSearch(context, accountKey, orig.substring(1, length - 1))
return true
}
}
}
else -> {
if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) {
openTwitterLink(link, accountKey)
return true
}
}
}
openLink(link)
}
return true
}
TwidereLinkify.LINK_TYPE_LIST -> {
val mentionList = StringUtils.split(link, "/")
if (mentionList.size != 2) {
return false
}
IntentUtils.openUserListDetails(context, accountKey, null, null, mentionList[0],
mentionList[1])
return true
}
TwidereLinkify.LINK_TYPE_CASHTAG -> {
IntentUtils.openTweetSearch(context, accountKey, link)
return true
}
TwidereLinkify.LINK_TYPE_USER_ID -> {
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(link), null, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.USER_MENTION)
return true
}
}
return false
}
protected open val isPrivateData: Boolean
get() = false
protected open fun isMedia(link: String, extraId: Long): Boolean {
return PreviewMediaExtractor.isSupported(link)
}
protected open fun openMedia(accountKey: UserKey, extraId: Long, sensitive: Boolean, link: String, start: Int, end: Int) {
val media = arrayOf(ParcelableMediaUtils.image(link))
IntentUtils.openMedia(context, accountKey, sensitive, null, media, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API))
}
protected open fun openLink(link: String) {
if (manager != null && manager.isActive) return
val uri = Uri.parse(link)
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.`package` = IntentUtils.getDefaultBrowserPackage(context, uri, true)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
// TODO
}
}
protected fun openTwitterLink(link: String, accountKey: UserKey) {
if (manager != null && manager.isActive) return
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.setClass(context, WebLinkHandlerActivity::class.java)
intent.putExtra(EXTRA_ACCOUNT_KEY, accountKey)
intent.setExtrasClassLoader(TwidereApplication::class.java.classLoader)
if (intent.resolveActivity(context.packageManager) != null) {
try {
context.startActivity(intent)
} catch (e: BadParcelableException) {
// Ignore
}
}
}
}

View File

@ -48,8 +48,8 @@ class StatusActionModeCallback(private val textView: TextView, private val conte
val string = SpannableString.valueOf(textView.text)
val spans = string.getSpans(start, end, URLSpan::class.java)
val selectingLink = spans.size == 1 && URLUtil.isValidUrl(spans[0].url)
MenuUtils.setMenuItemAvailability(menu, R.id.copy_url, selectingLink)
MenuUtils.setMenuItemAvailability(menu, R.id.share_url, selectingLink)
MenuUtils.setItemAvailability(menu, R.id.copy_url, selectingLink)
MenuUtils.setItemAvailability(menu, R.id.share_url, selectingLink)
return true
}

View File

@ -1,72 +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;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.util.ParcelableMediaUtils;
/**
* Created by mariotaku on 15/4/6.
*/
public class StatusAdapterLinkClickHandler<D> extends OnLinkClickHandler implements Constants {
private IStatusesAdapter<D> adapter;
public StatusAdapterLinkClickHandler(Context context, SharedPreferencesWrapper preferences) {
super(context, null, preferences);
}
@Override
protected void openMedia(final UserKey accountKey, final long extraId, final boolean sensitive,
final String link, final int start, final int end) {
if (extraId == RecyclerView.NO_POSITION) return;
final ParcelableStatus status = adapter.getStatus((int) extraId);
final ParcelableMedia[] media = ParcelableMediaUtils.getAllMedia(status);
final ParcelableMedia current = StatusLinkClickHandler.findByLink(media, link);
if (current != null && current.open_browser) {
openLink(link);
} else {
final boolean newDocument = preferences.getBoolean(KEY_NEW_DOCUMENT_API);
IntentUtils.openMedia(context, status, current, null, newDocument);
}
}
@Override
protected boolean isMedia(String link, long extraId) {
if (extraId != RecyclerView.NO_POSITION) {
final ParcelableStatus status = adapter.getStatus((int) extraId);
final ParcelableMedia[] media = ParcelableMediaUtils.getAllMedia(status);
final ParcelableMedia current = StatusLinkClickHandler.findByLink(media, link);
return current != null && !current.open_browser;
}
return super.isMedia(link, extraId);
}
public void setAdapter(IStatusesAdapter<D> adapter) {
this.adapter = adapter;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.content.Context
import android.support.v7.widget.RecyclerView
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
/**
* Created by mariotaku on 15/4/6.
*/
class StatusAdapterLinkClickHandler<D>(context: Context, preferences: SharedPreferencesWrapper) : OnLinkClickHandler(context, null, preferences), Constants {
private var adapter: IStatusesAdapter<D>? = null
override fun openMedia(accountKey: UserKey, extraId: Long, sensitive: Boolean,
link: String, start: Int, end: Int) {
if (extraId == RecyclerView.NO_POSITION.toLong()) return
val status = adapter!!.getStatus(extraId.toInt())
val media = ParcelableMediaUtils.getAllMedia(status)
val current = StatusLinkClickHandler.findByLink(media, link)
if (current != null && current.open_browser) {
openLink(link)
} else {
val newDocument = preferences.getBoolean(KEY_NEW_DOCUMENT_API)
IntentUtils.openMedia(context, status, current, null, newDocument)
}
}
override fun isMedia(link: String, extraId: Long): Boolean {
if (extraId != RecyclerView.NO_POSITION.toLong()) {
val status = adapter!!.getStatus(extraId.toInt())
val media = ParcelableMediaUtils.getAllMedia(status)
val current = StatusLinkClickHandler.findByLink(media, link)
return current != null && !current.open_browser
}
return super.isMedia(link, extraId)
}
fun setAdapter(adapter: IStatusesAdapter<D>) {
this.adapter = adapter
}
}

View File

@ -1,66 +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;
import android.content.Context;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.UserKey;
/**
* Created by mariotaku on 15/1/23.
*/
public class StatusLinkClickHandler extends OnLinkClickHandler implements Constants {
private ParcelableStatus mStatus;
@Override
protected void openMedia(final UserKey accountId, final long extraId, final boolean sensitive,
final String link, final int start, final int end) {
final ParcelableStatus status = mStatus;
final ParcelableMedia current = findByLink(status.media, link);
if (current == null || current.open_browser) {
openLink(link);
} else {
IntentUtils.openMedia(context, status, current, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API));
}
}
public static ParcelableMedia findByLink(ParcelableMedia[] media, String link) {
if (link == null || media == null) return null;
for (ParcelableMedia mediaItem : media) {
if (link.equals(mediaItem.media_url) || link.equals(mediaItem.url) ||
link.equals(mediaItem.page_url) || link.equals(mediaItem.preview_url))
return mediaItem;
}
return null;
}
public void setStatus(ParcelableStatus status) {
mStatus = status;
}
public StatusLinkClickHandler(Context context, MultiSelectManager manager, SharedPreferencesWrapper preferences) {
super(context, manager, preferences);
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.content.Context
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API
import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
/**
* Created by mariotaku on 15/1/23.
*/
open class StatusLinkClickHandler(context: Context, manager: MultiSelectManager, preferences: SharedPreferencesWrapper) : OnLinkClickHandler(context, manager, preferences), Constants {
var status: ParcelableStatus? = null
override fun openMedia(accountKey: UserKey, extraId: Long, sensitive: Boolean,
link: String, start: Int, end: Int) {
val status = status
val current = findByLink(status!!.media, link)
if (current == null || current.open_browser) {
openLink(link)
} else {
IntentUtils.openMedia(context, status, current, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API))
}
}
companion object {
fun findByLink(media: Array<ParcelableMedia>?, link: String?): ParcelableMedia? {
if (link == null || media == null) return null
for (mediaItem in media) {
if (link == mediaItem.media_url || link == mediaItem.url ||
link == mediaItem.page_url || link == mediaItem.preview_url)
return mediaItem
}
return null
}
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.ktextension
import android.support.v4.view.MenuItemCompat
import android.view.Menu
fun Menu.setItemAvailability(id: Int, available: Boolean) {
val item = findItem(id) ?: return
item.isVisible = available
item.isEnabled = available
}
fun Menu.setMenuGroupAvailability(groupId: Int, available: Boolean) {
setGroupEnabled(groupId, available)
setGroupVisible(groupId, available)
}
fun Menu.setItemChecked(id: Int, checked: Boolean) {
findItem(id)?.isChecked = checked
}
fun Menu.setMenuItemIcon(id: Int, icon: Int) {
findItem(id)?.setIcon(icon)
}
fun Menu.setMenuItemTitle(id: Int, title: Int) {
findItem(id)?.setTitle(title)
}
fun Menu.setMenuItemShowAsActionFlags(id: Int, flags: Int) {
val item = findItem(id) ?: return
item.setShowAsActionFlags(flags)
MenuItemCompat.setShowAsAction(item, flags)
}

View File

@ -65,6 +65,7 @@ import org.apache.commons.lang3.ObjectUtils
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.commons.io.StreamUtils
import org.mariotaku.ktextension.setItemChecked
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
@ -112,15 +113,15 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
// Adapters
private var mMediaPreviewAdapter: MediaPreviewAdapter? = null
private var mAccountsAdapter: AccountIconsAdapter? = null
private var accountsAdapter: AccountIconsAdapter? = null
// Data fields
private var mRecentLocation: ParcelableLocation? = null
private var mInReplyToStatus: ParcelableStatus? = null
private var mMentionUser: ParcelableUser? = null
private var mOriginalText: String? = null
private var mIsPossiblySensitive: Boolean = false
private var mShouldSaveAccounts: Boolean = false
private var possiblySensitive: Boolean = false
private var shouldSaveAccounts: Boolean = false
private var mImageUploaderUsed: Boolean = false
private var mStatusShortenerUsed: Boolean = false
private var mNavigateBackPressed: Boolean = false
@ -194,13 +195,13 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
public override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelableArray(EXTRA_ACCOUNT_KEYS, mAccountsAdapter!!.selectedAccountKeys)
outState.putParcelableArray(EXTRA_ACCOUNT_KEYS, accountsAdapter!!.selectedAccountKeys)
outState.putParcelableArrayList(EXTRA_MEDIA, ArrayList<Parcelable>(mediaList))
outState.putBoolean(EXTRA_IS_POSSIBLY_SENSITIVE, mIsPossiblySensitive)
outState.putBoolean(EXTRA_IS_POSSIBLY_SENSITIVE, possiblySensitive)
outState.putParcelable(EXTRA_STATUS, mInReplyToStatus)
outState.putParcelable(EXTRA_USER, mMentionUser)
outState.putParcelable(EXTRA_DRAFT, mDraft)
outState.putBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS, mShouldSaveAccounts)
outState.putBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS, shouldSaveAccounts)
outState.putString(EXTRA_ORIGINAL_TEXT, mOriginalText)
super.onSaveInstanceState(outState)
}
@ -310,7 +311,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
R.id.toggle_sensitive -> {
if (!hasMedia()) return false
mIsPossiblySensitive = !mIsPossiblySensitive
possiblySensitive = !possiblySensitive
setMenu()
updateTextCount()
}
@ -320,7 +321,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
try {
val action = intent.action
if (INTENT_ACTION_EXTENSION_COMPOSE == action) {
val accountKeys = mAccountsAdapter!!.selectedAccountKeys
val accountKeys = accountsAdapter!!.selectedAccountKeys
intent.putExtra(EXTRA_TEXT, ParseUtils.parseString(editText.text))
intent.putExtra(EXTRA_ACCOUNT_KEYS, accountKeys)
if (accountKeys.size > 0) {
@ -420,11 +421,11 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val draft = Draft()
draft.action_type = getDraftAction(intent.action)
draft.account_keys = mAccountsAdapter!!.selectedAccountKeys
draft.account_keys = accountsAdapter!!.selectedAccountKeys
draft.text = text
val extra = UpdateStatusActionExtra()
extra.inReplyToStatus = mInReplyToStatus
extra.setIsPossiblySensitive(mIsPossiblySensitive)
extra.setIsPossiblySensitive(possiblySensitive)
draft.action_extras = extra
draft.media = media
draft.location = mRecentLocation
@ -529,9 +530,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
linearLayoutManager.stackFromEnd = true
accountSelector!!.layoutManager = linearLayoutManager
accountSelector!!.itemAnimator = DefaultItemAnimator()
mAccountsAdapter = AccountIconsAdapter(this)
accountSelector!!.adapter = mAccountsAdapter
mAccountsAdapter!!.setAccounts(accounts)
accountsAdapter = AccountIconsAdapter(this)
accountSelector!!.adapter = accountsAdapter
accountsAdapter!!.setAccounts(accounts)
val adapter = MediaPreviewAdapter(this, PreviewGridOnStartDragListener(this))
@ -551,8 +552,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
if (savedInstanceState != null) {
// Restore from previous saved state
val selected = Utils.newParcelableArray(savedInstanceState.getParcelableArray(EXTRA_ACCOUNT_KEYS), UserKey.CREATOR)
mAccountsAdapter!!.setSelectedAccountIds(*selected)
mIsPossiblySensitive = savedInstanceState.getBoolean(EXTRA_IS_POSSIBLY_SENSITIVE)
accountsAdapter!!.setSelectedAccountIds(*selected)
possiblySensitive = savedInstanceState.getBoolean(EXTRA_IS_POSSIBLY_SENSITIVE)
val mediaList = savedInstanceState.getParcelableArrayList<ParcelableMediaUpdate>(EXTRA_MEDIA)
if (mediaList != null) {
addMedia(mediaList)
@ -560,7 +561,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
mInReplyToStatus = savedInstanceState.getParcelable<ParcelableStatus>(EXTRA_STATUS)
mMentionUser = savedInstanceState.getParcelable<ParcelableUser>(EXTRA_USER)
mDraft = savedInstanceState.getParcelable<Draft>(EXTRA_DRAFT)
mShouldSaveAccounts = savedInstanceState.getBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS)
shouldSaveAccounts = savedInstanceState.getBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS)
mOriginalText = savedInstanceState.getString(EXTRA_ORIGINAL_TEXT)
setLabel(intent)
} else {
@ -574,15 +575,15 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
handleDefaultIntent(intent)
}
setLabel(intent)
val selectedAccountIds = mAccountsAdapter!!.selectedAccountKeys
val selectedAccountIds = accountsAdapter!!.selectedAccountKeys
if (ArrayUtils.isEmpty(selectedAccountIds)) {
val idsInPrefs: Array<UserKey> = UserKey.arrayOf(preferences.getString(KEY_COMPOSE_ACCOUNTS, null)) ?: emptyArray()
val intersection: Array<UserKey> = defaultAccountIds.intersect(listOf(*idsInPrefs)).toTypedArray()
if (intersection.isEmpty()) {
mAccountsAdapter!!.setSelectedAccountIds(*defaultAccountIds)
accountsAdapter!!.setSelectedAccountIds(*defaultAccountIds)
} else {
mAccountsAdapter!!.setSelectedAccountIds(*intersection)
accountsAdapter!!.setSelectedAccountIds(*intersection)
}
}
mOriginalText = ParseUtils.parseString(editText.text)
@ -776,17 +777,17 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
if (intent.hasExtra(EXTRA_ACCOUNT_KEYS)) {
val accountKeys = Utils.newParcelableArray(
intent.getParcelableArrayExtra(EXTRA_ACCOUNT_KEYS), UserKey.CREATOR)
mAccountsAdapter!!.setSelectedAccountIds(*accountKeys)
accountsAdapter!!.setSelectedAccountIds(*accountKeys)
hasAccountIds = true
} else if (intent.hasExtra(EXTRA_ACCOUNT_KEY)) {
val accountKey = intent.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
mAccountsAdapter!!.setSelectedAccountIds(accountKey)
accountsAdapter!!.setSelectedAccountIds(accountKey)
hasAccountIds = true
} else {
hasAccountIds = false
}
if (Intent.ACTION_SEND == action) {
mShouldSaveAccounts = false
shouldSaveAccounts = false
val stream = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
if (stream != null) {
val src = arrayOf(stream)
@ -795,7 +796,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
ParcelableMedia.Type.IMAGE), false))
}
} else if (Intent.ACTION_SEND_MULTIPLE == action) {
mShouldSaveAccounts = false
shouldSaveAccounts = false
val extraStream = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
if (extraStream != null) {
val src = extraStream.toTypedArray()
@ -804,7 +805,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
ParcelableMedia.Type.IMAGE), false))
}
} else {
mShouldSaveAccounts = !hasAccountIds
shouldSaveAccounts = !hasAccountIds
val data = intent.data
if (data != null) {
val src = arrayOf(data)
@ -831,14 +832,14 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
editText.setText(draft.text)
val selectionEnd = editText.length()
editText.setSelection(selectionEnd)
mAccountsAdapter!!.setSelectedAccountIds(*draft.account_keys ?: emptyArray())
accountsAdapter!!.setSelectedAccountIds(*draft.account_keys ?: emptyArray())
if (draft.media != null) {
addMedia(Arrays.asList(*draft.media))
}
mRecentLocation = draft.location
if (draft.action_extras is UpdateStatusActionExtra) {
val extra = draft.action_extras as UpdateStatusActionExtra?
mIsPossiblySensitive = extra!!.isPossiblySensitive
possiblySensitive = extra!!.isPossiblySensitive
mInReplyToStatus = extra.inReplyToStatus
}
return true
@ -899,7 +900,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private fun handleIntent(intent: Intent): Boolean {
val action = intent.action ?: return false
mShouldSaveAccounts = false
shouldSaveAccounts = false
mMentionUser = intent.getParcelableExtra<ParcelableUser>(EXTRA_USER)
mInReplyToStatus = intent.getParcelableExtra<ParcelableStatus>(EXTRA_STATUS)
when (action) {
@ -940,7 +941,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
editText.setText(String.format("@%s ", user.screen_name))
val selection_end = editText.length()
editText.setSelection(selection_end)
mAccountsAdapter!!.setSelectedAccountIds(user.account_key)
accountsAdapter!!.setSelectedAccountIds(user.account_key)
return true
}
@ -948,7 +949,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
if (status == null) return false
editText.setText(Utils.getQuoteStatus(this, status))
editText.setSelection(0)
mAccountsAdapter!!.setSelectedAccountIds(status.account_key)
accountsAdapter!!.setSelectedAccountIds(status.account_key)
showQuoteLabel(status)
return true
}
@ -1003,16 +1004,16 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
mentions.add(mention.screen_name)
}
mentions.addAll(extractor!!.extractMentionedScreennames(status.quoted_text_plain))
mentions.addAll(extractor.extractMentionedScreennames(status.quoted_text_plain))
} else if (USER_TYPE_FANFOU_COM == status.account_key.host) {
addFanfouHtmlToMentions(status.text_unescaped, status.spans, mentions)
if (status.is_quote) {
addFanfouHtmlToMentions(status.quoted_text_unescaped, status.quoted_spans, mentions)
}
} else {
mentions.addAll(extractor!!.extractMentionedScreennames(status.text_plain))
mentions.addAll(extractor.extractMentionedScreennames(status.text_plain))
if (status.is_quote) {
mentions.addAll(extractor!!.extractMentionedScreennames(status.quoted_text_plain))
mentions.addAll(extractor.extractMentionedScreennames(status.quoted_text_plain))
}
}
@ -1024,7 +1025,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
val selectionEnd = editText.length()
editText.setSelection(selectionStart, selectionEnd)
mAccountsAdapter!!.setSelectedAccountIds(status.account_key)
accountsAdapter!!.setSelectedAccountIds(status.account_key)
showReplyLabel(status)
return true
}
@ -1054,7 +1055,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
editText.append("@$screenName ")
}
editText.setSelection(editText.length())
mAccountsAdapter!!.setSelectedAccountIds(accountId)
accountsAdapter!!.setSelectedAccountIds(accountId)
mInReplyToStatus = inReplyToStatus
return true
}
@ -1081,7 +1082,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
private fun notifyAccountSelectionChanged() {
val accounts = mAccountsAdapter!!.selectedAccounts
val accounts = accountsAdapter!!.selectedAccounts
setSelectedAccounts(*accounts)
if (ArrayUtils.isEmpty(accounts)) {
editText.setAccountKey(Utils.getDefaultAccountKey(this))
@ -1099,9 +1100,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
private fun saveAccountSelection() {
if (!mShouldSaveAccounts) return
if (!shouldSaveAccounts) return
val editor = preferences.edit()
editor.putString(KEY_COMPOSE_ACCOUNTS, TwidereArrayUtils.toString(mAccountsAdapter!!.selectedAccountKeys, ',', false))
editor.putString(KEY_COMPOSE_ACCOUNTS, TwidereArrayUtils.toString(accountsAdapter!!.selectedAccountKeys, ',', false))
editor.apply()
}
@ -1115,21 +1116,21 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
* Has media & Not reply: [Take photo][Media menu][Attach location][Drafts]
* Is reply: [Media menu][View status][Attach location][Drafts]
*/
MenuUtils.setMenuItemAvailability(menu, R.id.add_image, !hasMedia)
MenuUtils.setMenuItemAvailability(menu, R.id.media_menu, hasMedia)
MenuUtils.setMenuItemAvailability(menu, R.id.toggle_sensitive, hasMedia)
MenuUtils.setMenuItemAvailability(menu, R.id.schedule, isScheduleSupported)
MenuUtils.setItemAvailability(menu, R.id.add_image, !hasMedia)
MenuUtils.setItemAvailability(menu, R.id.media_menu, hasMedia)
MenuUtils.setItemAvailability(menu, R.id.toggle_sensitive, hasMedia)
MenuUtils.setItemAvailability(menu, R.id.schedule, scheduleSupported)
menu.setGroupEnabled(MENU_GROUP_IMAGE_EXTENSION, hasMedia)
menu.setGroupVisible(MENU_GROUP_IMAGE_EXTENSION, hasMedia)
MenuUtils.setMenuItemChecked(menu, R.id.toggle_sensitive, hasMedia && mIsPossiblySensitive)
menu.setItemChecked(R.id.toggle_sensitive, hasMedia && possiblySensitive)
ThemeUtils.resetCheatSheet(menuBar)
// mMenuBar.show();
}
private val isScheduleSupported: Boolean
private val scheduleSupported: Boolean
get() {
val accounts = mAccountsAdapter!!.selectedAccounts
val accounts = accountsAdapter!!.selectedAccounts
if (ArrayUtils.isEmpty(accounts)) return false
for (account in accounts) {
if (TwitterContentUtils.getOfficialKeyType(this, account.consumer_key, account.consumer_secret) != ConsumerKeyType.TWEETDECK) {
@ -1173,8 +1174,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
} else {
if (locationText!!.tag == null || location != mRecentLocation) {
val task = DisplayPlaceNameTask(this)
task.setParams(location)
task.setResultHandler(locationText)
task.params = location
task.setCallback(locationText)
TaskStarter.execute(task)
}
}
@ -1256,7 +1257,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val text = if (editText != null) ParseUtils.parseString(editText.text) else null
val tweetLength = validator!!.getTweetLength(text)
val maxLength = statusTextCount.maxLength
if (mAccountsAdapter!!.isSelectionEmpty) {
if (accountsAdapter!!.isSelectionEmpty) {
editText.error = getString(R.string.no_account_selected)
return
} else if (!hasMedia && (TextUtils.isEmpty(text) || noReplyContent(text))) {
@ -1270,8 +1271,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
}
val attachLocation = preferences.getBoolean(KEY_ATTACH_LOCATION)
val attachPreciseLocation = preferences.getBoolean(KEY_ATTACH_PRECISE_LOCATION)
val accountKeys = mAccountsAdapter!!.selectedAccountKeys
val isPossiblySensitive = hasMedia && mIsPossiblySensitive
val accountKeys = accountsAdapter!!.selectedAccountKeys
val isPossiblySensitive = hasMedia && possiblySensitive
val update = ParcelableStatusUpdate()
@Draft.Action val action: String
if (mDraft != null) {
@ -1290,8 +1291,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
update.is_possibly_sensitive = isPossiblySensitive
BackgroundOperationService.updateStatusesAsync(this, action, update)
if (preferences.getBoolean(KEY_NO_CLOSE_AFTER_TWEET_SENT, false) && mInReplyToStatus == null) {
mIsPossiblySensitive = false
mShouldSaveAccounts = true
possiblySensitive = false
shouldSaveAccounts = true
mInReplyToStatus = null
mMentionUser = null
mDraft = null
@ -1312,7 +1313,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private fun updateTextCount() {
val text = ParseUtils.parseString(editText.text)
val validatedCount = if (text != null) validator!!.getTweetLength(text) else 0
val validatedCount = if (text != null) validator.getTweetLength(text) else 0
statusTextCount.textCount = validatedCount
}
@ -1326,14 +1327,14 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
internal class ComposeLocationListener(activity: ComposeActivity) : LocationListener {
val mActivityRef: WeakReference<ComposeActivity>
val activityRef: WeakReference<ComposeActivity>
init {
mActivityRef = WeakReference(activity)
activityRef = WeakReference(activity)
}
override fun onLocationChanged(location: Location) {
val activity = mActivityRef.get() ?: return
val activity = activityRef.get() ?: return
activity.setRecentLocation(ParcelableLocationUtils.fromLocation(location))
}