fixed action mode position

added notice for loaded replies
This commit is contained in:
Mariotaku Lee 2015-10-12 14:59:17 +08:00
parent 3e9e91e871
commit c245c05c56
53 changed files with 733 additions and 342 deletions

View File

@ -136,6 +136,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
String QUERY_PARAM_EXTRA_ID = "extra_id";
String QUERY_PARAM_TIMESTAMP = "timestamp";
String QUERY_PARAM_FROM_NOTIFICATION = "from_notification";
String QUERY_PARAM_NOTIFICATION_TYPE = "notification_type";
String DEFAULT_PROTOCOL = PROTOCOL_HTTPS;

View File

@ -220,5 +220,6 @@ public interface IntentConstants {
String EXTRA_ACTIVITY_OPTIONS = "activity_options";
String EXTRA_MAKE_GAP = "make_gap";
String EXTRA_QUOTE_ORIGINAL_STATUS = "quote_original_status";
String EXTRA_KEY = "key";
}

View File

@ -64,6 +64,8 @@
android:name=".app.TwidereApplication"
android:allowBackup="true"
android:backupAgent=".backup.TwidereBackupAgentHelper"
android:description="@string/app_description"
android:fullBackupContent="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@ -30,6 +30,8 @@ import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import org.mariotaku.twidere.activity.iface.IAppCompatActivity;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
@ -37,9 +39,11 @@ import android.view.ViewGroup;
* This technique can be used with an {@link android.app.Activity} class, not just
* {@link android.preference.PreferenceActivity}.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity implements AppCompatCallback {
public abstract class AppCompatPreferenceActivity extends PreferenceActivity
implements AppCompatCallback, IAppCompatActivity {
private AppCompatDelegate mDelegate;
@Override
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}

View File

@ -238,7 +238,7 @@ public class SettingsActivity extends BasePreferenceActivity {
public boolean handleKeyboardShortcutSingle(@NonNull KeyboardShortcutsHandler handler, int keyCode, @NonNull KeyEvent event, int metaState) {
final String action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState);
if (ACTION_NAVIGATION_BACK.equals(action)) {
navigateUp();
onBackPressed();
return true;
}
return super.handleKeyboardShortcutSingle(handler, keyCode, event, metaState);
@ -286,7 +286,7 @@ public class SettingsActivity extends BasePreferenceActivity {
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navigateUp();
onBackPressed();
}
});
@ -332,15 +332,11 @@ public class SettingsActivity extends BasePreferenceActivity {
return mShouldNotifyChange;
}
private void navigateUp() {
@Override
public void onBackPressed() {
if (mTwidereActionModeForChildListener.finishExisting()) {
return;
}
onBackPressed();
}
@Override
public void onBackPressed() {
if (isTopSettings() && shouldNotifyChange()) {
final RestartConfirmDialogFragment df = new RestartConfirmDialogFragment();
df.show(getFragmentManager().beginTransaction(), "restart_confirm");

View File

@ -0,0 +1,29 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.activity.iface;
import android.support.v7.app.ActionBar;
/**
* Created by mariotaku on 15/10/12.
*/
public interface IAppCompatActivity {
ActionBar getSupportActionBar();
}

View File

@ -288,7 +288,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
super.onStart();
mImageUploaderUsed = !ServicePickerPreference.isNoneValue(mPreferences.getString(KEY_MEDIA_UPLOADER, null));
mStatusShortenerUsed = !ServicePickerPreference.isNoneValue(mPreferences.getString(KEY_STATUS_SHORTENER, null));
startLocationUpdateIfEnabled();
requestOrUpdateLocation();
setMenu();
updateTextCount();
final int textSize = mPreferences.getInt(KEY_TEXT_SIZE, Utils.getDefaultTextSize(this));
@ -351,6 +351,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
contentView.getPaddingRight(), contentView.getPaddingBottom());
return true;
}
// Offset content view
final int statusBarHeight = rect.top;
contentView.getWindowVisibleDisplayFrame(rect);
final int paddingTop = statusBarHeight + actionBarHeight - rect.top;
@ -580,6 +581,13 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
}
@Override
public void onActionModeStarted(ActionMode mode) {
super.onActionModeStarted(mode);
ThemeUtils.applyColorFilterToMenuIcon(mode.getMenu(), ThemeUtils.getContrastActionBarItemColor(this),
0, 0, Mode.MULTIPLY);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@ -28,7 +28,9 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.DialogFragment;
import android.widget.Toast;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.fragment.support.FileSelectorDialogFragment;
import org.mariotaku.twidere.util.PermissionUtils;
import org.mariotaku.twidere.util.ThemeUtils;
@ -125,6 +127,7 @@ public class FileSelectorActivity extends BaseSupportDialogActivity implements F
private void finishWithDeniedMessage() {
if (isFinishing()) return;
Toast.makeText(this, R.string.select_file_no_storage_permission_message, Toast.LENGTH_SHORT).show();
finish();
}

View File

@ -30,13 +30,15 @@ import android.view.View;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.activity.iface.IAppCompatActivity;
import org.mariotaku.twidere.activity.iface.IThemedActivity;
import org.mariotaku.twidere.util.StrictModeUtils;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.view.ShapedImageView.ShapeStyle;
public abstract class ThemedAppCompatActivity extends AppCompatActivity implements Constants, IThemedActivity {
public abstract class ThemedAppCompatActivity extends AppCompatActivity implements Constants,
IThemedActivity, IAppCompatActivity {
private int mCurrentThemeResource, mCurrentThemeColor, mCurrentThemeBackgroundAlpha;
@ShapeStyle

View File

@ -36,6 +36,7 @@ import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.MultiSelectManager;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.dagger.ApplicationModule;
import org.mariotaku.twidere.util.dagger.DaggerGeneralComponent;
@ -44,11 +45,19 @@ 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;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(activity)).build().inject(this);
}
public final TwidereApplication getApplication() {
return TwidereApplication.getInstance(getActivity());
@ -103,15 +112,6 @@ public class BaseListFragment extends ListFragment implements Constants, OnScrol
lv.setOnScrollListener(this);
}
@Inject
protected AsyncTwitterWrapper mTwitterWrapper;
@Override
public void onAttach(Context context) {
super.onAttach(context);
DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(context)).build().inject(this);
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@ -50,6 +50,7 @@ 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;
@ -62,15 +63,18 @@ 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 SharedPreferences mPreferences;
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
mPreferences = getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mPreferences.registerOnSharedPreferenceChangeListener(this);
mAdapter = new HostMappingAdapter(getActivity());
setListAdapter(mAdapter);
@ -126,13 +130,28 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
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:
case R.id.add: {
final DialogFragment df = new AddMappingDialogFragment();
df.show(getFragmentManager(), "add_mapping");
break;
}
}
return super.onOptionsItemSelected(item);
}
@ -162,9 +181,6 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
public static class AddMappingDialogFragment extends BaseDialogFragment implements OnClickListener,
OnShowListener, TextWatcher, OnCheckedChangeListener {
private static final String EXTRA_HOST = "host";
private static final String EXTRA_ADDRESS = "address";
private static final String EXTRA_EXCLUDED = "excluded";
private EditText mEditHost, mEditAddress;
private CheckBox mCheckExclude;
@ -222,7 +238,8 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
mEditAddress.addTextChangedListener(this);
mCheckExclude.setOnCheckedChangeListener(this);
final Bundle args = getArguments();
if (savedInstanceState == null && 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));
@ -264,11 +281,11 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
static class HostMappingAdapter extends ArrayAdapter<String> {
private final SharedPreferences mPreferences;
private final SharedPreferences mHostMapping;
public HostMappingAdapter(final Context context) {
super(context, android.R.layout.simple_list_item_activated_2);
mPreferences = context.getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mHostMapping = context.getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@Override
@ -278,8 +295,8 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
final String key = getItem(position);
text1.setText(key);
final String value = mPreferences.getString(key, null);
if (key.equals(value)) {
final String value = getAddress(key);
if (StringUtils.equals(key, value)) {
text2.setText(R.string.excluded);
} else {
text2.setText(value);
@ -289,10 +306,13 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
public void reload() {
clear();
final Map<String, ?> all = mPreferences.getAll();
final Map<String, ?> all = mHostMapping.getAll();
addAll(all.keySet());
}
public String getAddress(String key) {
return mHostMapping.getString(key, null);
}
}
}

View File

@ -115,7 +115,7 @@ public abstract class AbsContentListViewFragment<A extends ListAdapter> extends
}
@Override
public void onLoadMoreContents() {
public void onLoadMoreContents(boolean fromStart) {
setRefreshEnabled(false);
}

View File

@ -175,7 +175,7 @@ public abstract class AbsContentRecyclerViewFragment<A extends LoadMoreSupportAd
}
@Override
public void onLoadMoreContents() {
public void onLoadMoreContents(boolean fromStart) {
setLoadMoreIndicatorVisible(true);
setRefreshEnabled(false);
}

View File

@ -217,8 +217,10 @@ public abstract class CursorStatusesFragment extends AbsStatusesFragment<List<Pa
}
@Override
public void onLoadMoreContents() {
super.onLoadMoreContents();
public void onLoadMoreContents(boolean fromStart) {
if (fromStart) return;
//noinspection ConstantConditions
super.onLoadMoreContents(fromStart);
AsyncManager.runBackgroundTask(new TaskRunnable<Object, long[][], CursorStatusesFragment>() {
@Override
public long[][] doLongOperation(Object o) throws InterruptedException {

View File

@ -67,8 +67,10 @@ public abstract class CursorSupportUsersListFragment extends ParcelableUsersFrag
}
@Override
public void onLoadMoreContents() {
super.onLoadMoreContents();
public void onLoadMoreContents(boolean fromStart) {
if (fromStart) return;
//noinspection ConstantConditions
super.onLoadMoreContents(fromStart);
final Bundle loaderArgs = new Bundle(getArguments());
loaderArgs.putBoolean(EXTRA_FROM_USER, true);
loaderArgs.putLong(EXTRA_NEXT_CURSOR, mNextCursor);

View File

@ -59,7 +59,6 @@ import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
import org.mariotaku.twidere.util.MultiSelectManager;
import org.mariotaku.twidere.util.RecyclerViewNavigationHelper;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.content.SupportFragmentReloadCursorObserver;
@ -69,8 +68,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
import static org.mariotaku.twidere.util.Utils.openMessageConversation;
public class DirectMessagesFragment extends AbsContentRecyclerViewFragment<MessageEntriesAdapter>
@ -95,7 +92,7 @@ public class DirectMessagesFragment extends AbsContentRecyclerViewFragment<Messa
}
@Override
public void onLoadMoreContents() {
public void onLoadMoreContents(boolean fromStart) {
loadMoreMessages();
}

View File

@ -57,7 +57,7 @@ public abstract class ParcelableActivitiesFragment extends AbsActivitiesFragment
}
@Override
public void onLoadMoreContents() {
public void onLoadMoreContents(boolean fromStart) {
final IActivitiesAdapter<List<ParcelableActivity>> adapter = getAdapter();
final long[] maxIds = new long[]{adapter.getActivity(adapter.getActivityCount() - 1).min_position};
getActivities(getAccountIds(), maxIds, null);

View File

@ -47,19 +47,19 @@ public abstract class ParcelableStatusesFragment extends AbsStatusesFragment<Lis
private long mLastId;
public final void deleteStatus(final long statusId) {
final List<ParcelableStatus> data = getAdapterData();
if (statusId <= 0 || data == null) return;
final List<ParcelableStatus> list = getAdapterData();
if (statusId <= 0 || list == null) return;
final Set<ParcelableStatus> dataToRemove = new HashSet<>();
for (int i = 0, j = data.size(); i < j; i++) {
final ParcelableStatus status = data.get(i);
for (int i = 0, j = list.size(); i < j; i++) {
final ParcelableStatus status = list.get(i);
if (status.id == statusId || status.retweet_id > 0 && status.retweet_id == statusId) {
dataToRemove.add(status);
} else if (status.my_retweet_id == statusId) {
data.set(i, new ParcelableStatus(status, -1, status.retweet_count - 1));
list.set(i, new ParcelableStatus(status, -1, status.retweet_count - 1));
}
}
data.removeAll(dataToRemove);
setAdapterData(data);
list.removeAll(dataToRemove);
setAdapterData(list);
}
@Override
@ -90,9 +90,9 @@ public abstract class ParcelableStatusesFragment extends AbsStatusesFragment<Lis
}
@Override
protected boolean hasMoreData(List<ParcelableStatus> data) {
if (data == null || data.isEmpty()) return false;
return (mLastId != (mLastId = data.get(data.size() - 1).id));
protected boolean hasMoreData(List<ParcelableStatus> list) {
if (list == null || list.isEmpty()) return false;
return (mLastId != (mLastId = list.get(list.size() - 1).id));
}
@Override
@ -121,8 +121,10 @@ public abstract class ParcelableStatusesFragment extends AbsStatusesFragment<Lis
@Override
public void onLoadMoreContents() {
super.onLoadMoreContents();
public void onLoadMoreContents(boolean fromStart) {
if (fromStart) return;
//noinspection ConstantConditions
super.onLoadMoreContents(fromStart);
final IStatusesAdapter<List<ParcelableStatus>> adapter = getAdapter();
final long[] maxIds = new long[]{adapter.getStatusId(adapter.getStatusesCount() - 1)};
getStatuses(null, maxIds, null);
@ -130,7 +132,7 @@ public abstract class ParcelableStatusesFragment extends AbsStatusesFragment<Lis
public final void replaceStatus(final ParcelableStatus status) {
final List<ParcelableStatus> data = getAdapterData();
if (status == null || data == null) return;
if (status == null || data == null || data.isEmpty()) return;
for (int i = 0, j = data.size(); i < j; i++) {
if (status.equals(data.get(i))) {
data.set(i, status);

View File

@ -63,8 +63,10 @@ public abstract class ParcelableUserListsFragment extends AbsUserListsFragment<L
}
@Override
public void onLoadMoreContents() {
super.onLoadMoreContents();
public void onLoadMoreContents(boolean fromStart) {
if (fromStart) return;
//noinspection ConstantConditions
super.onLoadMoreContents(fromStart);
final Bundle loaderArgs = new Bundle(getArguments());
loaderArgs.putBoolean(EXTRA_FROM_USER, true);
loaderArgs.putLong(EXTRA_NEXT_CURSOR, getNextCursor());

View File

@ -58,8 +58,10 @@ public class SearchUsersFragment extends ParcelableUsersFragment {
}
@Override
public void onLoadMoreContents() {
super.onLoadMoreContents();
public void onLoadMoreContents(boolean fromStart) {
if (fromStart) return;
//noinspection ConstantConditions
super.onLoadMoreContents(fromStart);
final Bundle loaderArgs = new Bundle(getArguments());
loaderArgs.putBoolean(EXTRA_FROM_USER, true);
loaderArgs.putInt(EXTRA_PAGE, mPage + 1);

View File

@ -54,9 +54,13 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutParams;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -180,11 +184,37 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
@Override
public void onLoadFinished(Loader<List<ParcelableStatus>> loader, List<ParcelableStatus> data) {
mStatusAdapter.setRepliesLoading(false);
mStatusAdapter.updateItemDecoration();
final Pair<Long, Integer> readPosition = saveReadPosition();
setReplies(data);
restoreReadPosition(readPosition);
final ParcelableCredentials account = mStatusAdapter.getStatusAccount();
if (Utils.hasOfficialAPIAccess(loader.getContext(), mPreferences, account)) {
mStatusAdapter.setReplyError(null);
} else {
final SpannableStringBuilder error = SpannableStringBuilder.valueOf(Html.fromHtml(getString(R.string.cant_load_all_replies_message)));
ClickableSpan dialogSpan = null;
for (URLSpan span : error.getSpans(0, error.length(), URLSpan.class)) {
if ("#dialog".equals(span.getURL())) {
dialogSpan = span;
break;
}
}
if (dialogSpan != null) {
final int spanStart = error.getSpanStart(dialogSpan), spanEnd = error.getSpanEnd(dialogSpan);
error.removeSpan(dialogSpan);
error.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
final FragmentActivity activity = getActivity();
if (activity == null) return;
SupportMessageDialogFragment.show(activity,
getString(R.string.cant_load_all_replies_explanation),
"cant_load_all_replies_explanation");
}
}, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
mStatusAdapter.setReplyError(error);
}
mStatusAdapter.setRepliesLoading(false);
}
@Override
@ -492,21 +522,25 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private void restoreReadPosition(@Nullable Pair<Long, Integer> position) {
if (position == null) return;
final int adapterPosition = mStatusAdapter.findPositionById(position.first);
if (adapterPosition == RecyclerView.NO_POSITION) return;
// FIXME We don't know why we need to -1 here, but only -1 works.
final int adapterPosition = mStatusAdapter.findPositionById(position.first) - 1;
if (adapterPosition < 0) return;
//TODO maintain read position
mLayoutManager.scrollToPositionWithOffset(adapterPosition, position.second);
}
@Nullable
private Pair<Long, Integer> saveReadPosition() {
final int position = mLayoutManager.findFirstVisibleItemPosition();
if (position == RecyclerView.NO_POSITION) return null;
if (position < 0) return null;
final int itemType = mStatusAdapter.getItemType(position);
long itemId = mStatusAdapter.getItemId(position);
final View positionView;
if (itemId == StatusAdapter.VIEW_TYPE_CONVERSATION_LOAD_INDICATOR) {
if (itemType == StatusAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE) {
// Should be next item
positionView = mLayoutManager.findViewByPosition(position + 1);
itemId = mStatusAdapter.getItemId(position + 1);
final int statusPosition = mStatusAdapter.getFirstPositionOfItem(StatusAdapter.ITEM_IDX_STATUS);
positionView = mLayoutManager.findViewByPosition(statusPosition);
itemId = mStatusAdapter.getItemId(statusPosition);
} else {
positionView = mLayoutManager.findViewByPosition(position);
}
@ -517,25 +551,26 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
public void onLoadFinished(final Loader<SingleResponse<ParcelableStatus>> loader,
final SingleResponse<ParcelableStatus> data) {
if (data.hasData()) {
final int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
final Pair<Long, Integer> readPosition = saveReadPosition();
final ParcelableStatus status = data.getData();
final Bundle dataExtra = data.getExtras();
final ParcelableCredentials credentials = dataExtra.getParcelable(EXTRA_ACCOUNT);
if (mStatusAdapter.setStatus(status, credentials)) {
mLayoutManager.scrollToPositionWithOffset(1, 0);
mStatusAdapter.setConversation(null);
mStatusAdapter.setReplies(null);
loadReplies(status);
loadConversation(status);
loadReplies(status);
final int position = mStatusAdapter.getFirstPositionOfItem(StatusAdapter.ITEM_IDX_STATUS);
if (position != RecyclerView.NO_POSITION) {
mLayoutManager.scrollToPositionWithOffset(position, 0);
}
final TweetEvent event = TweetEvent.create(getActivity(), status, TimelineType.OTHER);
event.setAction(TweetEvent.Action.OPEN);
mStatusEvent = event;
} else if (firstVisibleItemPosition >= 0) {
final long itemId = mStatusAdapter.getItemId(firstVisibleItemPosition);
final View firstChild = mLayoutManager.getChildAt(0);
final int top = firstChild != null ? firstChild.getTop() : 0;
final int position = mStatusAdapter.findPositionById(itemId);
mLayoutManager.scrollToPositionWithOffset(position, top);
} else if (readPosition != null) {
restoreReadPosition(readPosition);
}
setState(STATE_LOADED);
} else {
@ -551,15 +586,10 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
private void setReplies(List<ParcelableStatus> data) {
if (mLayoutManager.getChildCount() != 0) {
final long itemId = mStatusAdapter.getItemId(mLayoutManager.findFirstVisibleItemPosition());
final int top = mLayoutManager.getChildAt(0).getTop();
mStatusAdapter.setReplies(data);
final int position = mStatusAdapter.findPositionById(itemId);
mLayoutManager.scrollToPositionWithOffset(position, top);
} else {
mStatusAdapter.setReplies(data);
}
final Pair<Long, Integer> readPosition = saveReadPosition();
mStatusAdapter.setReplies(data);
//TODO maintain read position
restoreReadPosition(readPosition);
}
private void setState(int state) {
@ -576,6 +606,10 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
HotMobiLogger.getInstance(getActivity()).log(event.getAccountId(), event);
}
private void showConversationError(Exception exception) {
}
private static class DetailStatusViewHolder extends ViewHolder implements OnClickListener,
ActionMenuView.OnMenuItemClickListener {
@ -977,12 +1011,15 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
@Override
protected void onPostExecute(final ListResponse<ParcelableStatus> data) {
fragment.getAdapter().setConversationsLoading(false);
final StatusAdapter adapter = fragment.getAdapter();
if (data.hasData()) {
fragment.setConversation(data.getData());
} else if (data.hasException()) {
fragment.showConversationError(data.getException());
} else {
Utils.showErrorMessage(context, context.getString(R.string.action_getting_status), data.getException(), true);
}
adapter.setConversationsLoading(false);
}
@Override
@ -1042,14 +1079,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private static final int VIEW_TYPE_DETAIL_STATUS = 1;
private static final int VIEW_TYPE_CONVERSATION_LOAD_INDICATOR = 2;
private static final int VIEW_TYPE_REPLIES_LOAD_INDICATOR = 3;
private static final int VIEW_TYPE_SPACE = 4;
private final Context mContext;
private final StatusFragment mFragment;
private final LayoutInflater mInflater;
private final MediaLoadingHandler mMediaLoadingHandler;
private final TwidereLinkify mTwidereLinkify;
private static final int VIEW_TYPE_REPLY_ERROR = 4;
private static final int VIEW_TYPE_SPACE = 5;
private static final int ITEM_IDX_CONVERSATION_ERROR = 0;
private static final int ITEM_IDX_CONVERSATION_LOAD_MORE = 1;
private static final int ITEM_IDX_CONVERSATION = 2;
@ -1059,7 +1090,11 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private static final int ITEM_IDX_REPLY_ERROR = 6;
private static final int ITEM_IDX_SPACE = 7;
private static final int ITEM_TYPES_SUM = 8;
private final Context mContext;
private final StatusFragment mFragment;
private final LayoutInflater mInflater;
private final MediaLoadingHandler mMediaLoadingHandler;
private final TwidereLinkify mTwidereLinkify;
private final int[] mItemCounts;
private final boolean mNameFirst;
@ -1083,6 +1118,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private StatusAdapterListener mStatusAdapterListener;
private RecyclerView mRecyclerView;
private DetailStatusViewHolder mStatusViewHolder;
private CharSequence mReplyError;
public StatusAdapter(StatusFragment fragment, boolean compact) {
super(fragment.getContext());
@ -1334,6 +1370,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
mLoadMoreIndicatorVisible = false;
}
notifyDataSetChanged();
updateItemDecoration();
}
@Override
@ -1375,6 +1412,11 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
case VIEW_TYPE_SPACE: {
return new SpaceViewHolder(new Space(mContext));
}
case VIEW_TYPE_REPLY_ERROR: {
final View view = mInflater.inflate(R.layout.adapter_item_status_error, parent,
false);
return new StatusErrorItemViewHolder(view);
}
}
return null;
}
@ -1400,6 +1442,11 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
&& (position - getItemTypeStart(position)) == 0);
break;
}
case VIEW_TYPE_REPLY_ERROR: {
final StatusErrorItemViewHolder errorHolder = (StatusErrorItemViewHolder) holder;
errorHolder.showError(mReplyError);
break;
}
}
}
@ -1421,6 +1468,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
return VIEW_TYPE_DETAIL_STATUS;
case ITEM_IDX_SPACE:
return VIEW_TYPE_SPACE;
case ITEM_IDX_REPLY_ERROR:
return VIEW_TYPE_REPLY_ERROR;
}
throw new IllegalStateException();
}
@ -1433,7 +1482,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
if (position >= typeStart && position < typeEnd) return type;
typeStart = typeEnd;
}
throw new IllegalStateException();
throw new IllegalStateException("Unknown position " + position);
}
private int getItemTypeStart(int position) {
@ -1531,6 +1580,13 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
mStatusAdapterListener = listener;
}
public void setReplyError(CharSequence error) {
mReplyError = error;
mItemCounts[ITEM_IDX_REPLY_ERROR] = error != null ? 1 : 0;
notifyDataSetChanged();
updateItemDecoration();
}
public void setReplies(List<ParcelableStatus> replies) {
mReplies = replies;
mItemCounts[ITEM_IDX_REPLY] = replies != null ? replies.size() : 0;
@ -1548,18 +1604,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
return !CompareUtils.objectEquals(old, status);
}
private int getConversationCount() {
return mConversation != null ? mConversation.size() : 1;
}
private int getRepliesCount() {
return mReplies != null ? mReplies.size() : 1;
}
private int getStatusPosition() {
return getConversationCount();
}
private void updateItemDecoration() {
if (mRecyclerView == null) return;
final DividerItemDecoration decoration = mFragment.getItemDecoration();
@ -1575,11 +1619,37 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
public void setRepliesLoading(boolean loading) {
mItemCounts[ITEM_IDX_REPLY_LOAD_MORE] = loading ? 1 : 0;
notifyDataSetChanged();
updateItemDecoration();
}
public void setConversationsLoading(boolean loading) {
mItemCounts[ITEM_IDX_CONVERSATION_LOAD_MORE] = loading ? 1 : 0;
notifyDataSetChanged();
updateItemDecoration();
}
public int getFirstPositionOfItem(int itemIdx) {
int position = 0;
for (int i = 0; i < ITEM_TYPES_SUM; i++) {
if (itemIdx == i) return position;
position += mItemCounts[i];
}
return RecyclerView.NO_POSITION;
}
public static class StatusErrorItemViewHolder extends ViewHolder {
private final TextView textView;
public StatusErrorItemViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(android.R.id.text1);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setLinksClickable(true);
}
public void showError(CharSequence text) {
textView.setText(text);
}
}
}
@ -1602,13 +1672,13 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final View childToMeasure = getChildAt(i);
final LayoutParams paramsToMeasure = (LayoutParams) childToMeasure.getLayoutParams();
final int typeToMeasure = getItemViewType(childToMeasure);
if (typeToMeasure == StatusAdapter.VIEW_TYPE_SPACE) {
break;
}
if (typeToMeasure == StatusAdapter.VIEW_TYPE_DETAIL_STATUS || heightBeforeSpace != 0) {
heightBeforeSpace += super.getDecoratedMeasuredHeight(childToMeasure)
+ paramsToMeasure.topMargin + paramsToMeasure.bottomMargin;
}
if (typeToMeasure == StatusAdapter.VIEW_TYPE_REPLIES_LOAD_INDICATOR) {
break;
}
}
if (heightBeforeSpace != 0) {
final int spaceHeight = recyclerView.getMeasuredHeight() - heightBeforeSpace;
@ -1627,5 +1697,4 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
}
}

View File

@ -32,8 +32,8 @@ public class StatusRepliesListFragment extends StatusesSearchFragment {
@Override
protected Loader<List<ParcelableStatus>> onCreateStatusesLoader(final Context context,
final Bundle args,
final boolean fromUser) {
final Bundle args,
final boolean fromUser) {
final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1);
final String screenName = args.getString(EXTRA_SCREEN_NAME);
final long statusId = args.getLong(EXTRA_STATUS_ID, -1);

View File

@ -32,8 +32,8 @@ public class StatusesListFragment extends ParcelableStatusesFragment {
@Override
protected Loader<List<ParcelableStatus>> onCreateStatusesLoader(final Context context,
final Bundle args,
final boolean fromUser) {
final Bundle args,
final boolean fromUser) {
return new IntentExtrasStatusesLoader(context, getArguments(), getAdapterData(), fromUser);
}

View File

@ -0,0 +1,62 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment.support;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
/**
* Created by mariotaku on 14-6-24.
*/
public class SupportMessageDialogFragment extends DialogFragment {
private static final String EXTRA_MESSAGE = "message";
public static SupportMessageDialogFragment show(FragmentActivity activity, String message, String tag) {
SupportMessageDialogFragment df = new SupportMessageDialogFragment();
Bundle args = new Bundle();
args.putString(EXTRA_MESSAGE, message);
df.setArguments(args);
df.show(activity.getSupportFragmentManager(), tag);
return df;
}
public static SupportMessageDialogFragment create(String message) {
SupportMessageDialogFragment df = new SupportMessageDialogFragment();
Bundle args = new Bundle();
args.putString(EXTRA_MESSAGE, message);
df.setArguments(args);
return df;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
FragmentActivity activity = getActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final Bundle args = getArguments();
builder.setMessage(args.getString(EXTRA_MESSAGE));
builder.setPositiveButton(android.R.string.ok, null);
return builder.create();
}
}

View File

@ -24,6 +24,7 @@ import android.os.Bundle;
import android.support.v4.content.Loader;
import org.mariotaku.twidere.loader.support.UserFavoritesLoader;
import org.mariotaku.twidere.model.ListResponse;
import org.mariotaku.twidere.model.ParcelableStatus;
import java.util.List;
@ -35,8 +36,8 @@ public class UserFavoritesFragment extends ParcelableStatusesFragment {
@Override
protected Loader<List<ParcelableStatus>> onCreateStatusesLoader(final Context context,
final Bundle args,
final boolean fromUser) {
final Bundle args,
final boolean fromUser) {
setRefreshing(true);
final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1);
final long maxId = args.getLong(EXTRA_MAX_ID, -1);

View File

@ -35,8 +35,8 @@ public class UserListTimelineFragment extends ParcelableStatusesFragment {
@Override
protected Loader<List<ParcelableStatus>> onCreateStatusesLoader(final Context context,
final Bundle args,
final boolean fromUser) {
final Bundle args,
final boolean fromUser) {
setRefreshing(true);
if (args == null) return null;
final long listId = args.getLong(EXTRA_LIST_ID, -1);

View File

@ -24,6 +24,7 @@ import android.os.Bundle;
import android.support.v4.content.Loader;
import org.mariotaku.twidere.loader.support.UserTimelineLoader;
import org.mariotaku.twidere.model.ListResponse;
import org.mariotaku.twidere.model.ParcelableStatus;
import java.util.List;
@ -35,8 +36,8 @@ public class UserTimelineFragment extends ParcelableStatusesFragment {
@Override
protected Loader<List<ParcelableStatus>> onCreateStatusesLoader(final Context context,
final Bundle args,
final boolean fromUser) {
final Bundle args,
final boolean fromUser) {
setRefreshing(true);
final List<ParcelableStatus> data = getAdapterData();
final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1);

View File

@ -21,9 +21,9 @@ package org.mariotaku.twidere.loader.support;
import android.content.Context;
import org.mariotaku.twidere.model.ListResponse;
import org.mariotaku.twidere.model.ParcelableStatus;
import java.util.Collections;
import java.util.List;
public final class DummyParcelableStatusesLoader extends ParcelableStatusesLoader {
@ -37,10 +37,10 @@ public final class DummyParcelableStatusesLoader extends ParcelableStatusesLoade
}
@Override
public List<ParcelableStatus> loadInBackground() {
public ListResponse<ParcelableStatus> loadInBackground() {
final List<ParcelableStatus> data = getData();
if (data != null) return data;
return Collections.emptyList();
if (data != null) return ListResponse.getListInstance(data);
return ListResponse.emptyListInstance();
}
}

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.loader.support;
import android.content.Context;
import android.os.Bundle;
import org.mariotaku.twidere.model.ListResponse;
import org.mariotaku.twidere.model.ParcelableStatus;
import java.util.Collections;
@ -38,14 +39,16 @@ public class IntentExtrasStatusesLoader extends ParcelableStatusesLoader {
}
@Override
public List<ParcelableStatus> loadInBackground() {
public ListResponse<ParcelableStatus> loadInBackground() {
final List<ParcelableStatus> data = getData();
if (mExtras != null && mExtras.containsKey(EXTRA_STATUSES)) {
final List<ParcelableStatus> users = mExtras.getParcelableArrayList(EXTRA_STATUSES);
data.addAll(users);
Collections.sort(data);
if (data != null && users != null) {
data.addAll(users);
Collections.sort(data);
}
}
return data;
return ListResponse.getListInstance(data);
}
}

View File

@ -26,6 +26,7 @@ import android.support.annotation.NonNull;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.LoaderTrojan;
import org.mariotaku.twidere.model.ListResponse;
import org.mariotaku.twidere.model.ObjectCursor;
import java.io.FileDescriptor;

View File

@ -23,45 +23,29 @@ import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.model.Paging;
import org.mariotaku.twidere.api.twitter.model.ResponseList;
import org.mariotaku.twidere.api.twitter.model.Status;
import org.mariotaku.twidere.model.ParcelableStatus;
import java.util.List;
import org.mariotaku.twidere.api.twitter.model.Paging;
import org.mariotaku.twidere.api.twitter.model.ResponseList;
import org.mariotaku.twidere.api.twitter.model.Status;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.model.User;
import static org.mariotaku.twidere.util.Utils.isFiltered;
public class RetweetsOfMeLoader extends TwitterAPIStatusesLoader {
private long mTotalItemsCount;
public RetweetsOfMeLoader(final Context context, final long accountId, final long sinceId, final long maxId,
final List<ParcelableStatus> data, final String[] savedStatusesArgs,
final int tabPosition, boolean fromUser) {
super(context, accountId, sinceId, maxId, data, savedStatusesArgs, tabPosition, fromUser);
}
public long getTotalItemsCount() {
return mTotalItemsCount;
}
@NonNull
@Override
protected ResponseList<Status> getStatuses(@NonNull final Twitter twitter, final Paging paging) throws TwitterException {
if (twitter == null) return null;
final ResponseList<Status> statuses = twitter.getRetweetsOfMe(paging);
if (mTotalItemsCount == -1 && !statuses.isEmpty()) {
final User user = statuses.get(0).getUser();
if (user != null) {
mTotalItemsCount = user.getStatusesCount();
}
}
return statuses;
return twitter.getRetweetsOfMe(paging);
}
@Override

View File

@ -34,6 +34,7 @@ import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.model.Paging;
import org.mariotaku.twidere.api.twitter.model.Status;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.ListResponse;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.util.LoganSquareWrapper;
import org.mariotaku.twidere.util.TwitterAPIFactory;
@ -70,7 +71,7 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader
@SuppressWarnings("unchecked")
@Override
public final List<ParcelableStatus> loadInBackground() {
public final ListResponse<ParcelableStatus> loadInBackground() {
final File serializationFile = getSerializationFile();
List<ParcelableStatus> data = getData();
if (data == null) {
@ -85,10 +86,10 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader
} else {
Collections.sort(data);
}
return new CopyOnWriteArrayList<>(data);
return ListResponse.getListInstance(new CopyOnWriteArrayList<>(data));
}
}
if (!isFromUser()) return data;
if (!isFromUser()) return ListResponse.getListInstance(data);
final Twitter twitter = getTwitter();
if (twitter == null) return null;
final List<Status> statuses;
@ -114,7 +115,7 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader
} catch (final TwitterException e) {
// mHandler.post(new ShowErrorRunnable(e));
Log.w(LOGTAG, e);
return new CopyOnWriteArrayList<>(data);
return ListResponse.getListInstance(new CopyOnWriteArrayList<>(data), e);
}
final long[] statusIds = new long[statuses.size()];
@ -159,7 +160,7 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader
Collections.sort(data);
}
saveCachedData(serializationFile, data);
return new CopyOnWriteArrayList<>(data);
return ListResponse.getListInstance(new CopyOnWriteArrayList<>(data));
}
public final void setComparator(Comparator<ParcelableStatus> comparator) {

View File

@ -50,7 +50,6 @@ public class UserFavoritesLoader extends TwitterAPIStatusesLoader {
@NonNull
@Override
public ResponseList<Status> getStatuses(@NonNull final Twitter twitter, final Paging paging) throws TwitterException {
if (twitter == null) return null;
if (mUserId != -1)
return twitter.getFavorites(mUserId, paging);
else if (mUserScreenName != null) return twitter.getFavorites(mUserScreenName, paging);

View File

@ -20,21 +20,28 @@
package org.mariotaku.twidere.model;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
public class ListResponse<Data> extends SingleResponse<List<Data>> {
public class ListResponse<Data> extends AbstractList<Data> implements Response<List<Data>>, List<Data> {
public final List<Data> list;
@Nullable
private final List<Data> list;
private final Exception exception;
private final Bundle extras;
public ListResponse(final List<Data> list, final Exception exception) {
super(list, exception);
this.list = list;
this(list, exception, new Bundle());
}
public ListResponse(final List<Data> list, final Exception exception, final Bundle extras) {
super(list, exception, extras);
public ListResponse(@Nullable final List<Data> list, final Exception exception, @NonNull final Bundle extras) {
this.list = list;
this.exception = exception;
this.extras = extras;
}
public static <Data> ListResponse<Data> getListInstance(Exception exception) {
@ -45,4 +52,72 @@ public class ListResponse<Data> extends SingleResponse<List<Data>> {
return new ListResponse<>(data, null);
}
public static <Data> ListResponse<Data> emptyListInstance() {
return new ListResponse<>(Collections.<Data>emptyList(), null);
}
public static <Data> ListResponse<Data> getListInstance(List<Data> list, Exception e) {
return new ListResponse<>(list, e);
}
@Override
public int size() {
if (list == null) throw new NullPointerException();
return list.size();
}
@Override
public boolean isEmpty() {
return list != null && list.isEmpty();
}
@Override
public Data remove(int location) {
if (list == null) throw new NullPointerException();
return list.remove(location);
}
@Override
public Data set(int location, Data object) {
if (list == null) throw new NullPointerException();
return list.set(location, object);
}
@Override
public void add(int location, Data object) {
if (list == null) throw new NullPointerException();
list.add(location, object);
}
@Override
public Data get(int location) {
if (list == null) throw new NullPointerException();
return list.get(location);
}
@Override
public List<Data> getData() {
return list;
}
@Override
public Exception getException() {
return exception;
}
@NonNull
@Override
public Bundle getExtras() {
return extras;
}
@Override
public boolean hasData() {
return list != null;
}
@Override
public boolean hasException() {
return exception != null;
}
}

View File

@ -0,0 +1,39 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.model;
import android.os.Bundle;
import android.support.annotation.NonNull;
/**
* Created by mariotaku on 15/10/11.
*/
public interface Response<Data> {
Data getData();
Exception getException();
@NonNull
Bundle getExtras();
boolean hasData();
boolean hasException();
}

View File

@ -22,81 +22,86 @@ package org.mariotaku.twidere.model;
import android.os.Bundle;
import android.support.annotation.NonNull;
public class SingleResponse<Data> {
private final Exception exception;
private final Data data;
private final Bundle extras;
public class SingleResponse<Data> implements Response<Data> {
protected final Exception exception;
protected final Data data;
protected final Bundle extras;
public SingleResponse(final Data data, final Exception exception) {
this(data, exception, null);
}
public SingleResponse(final Data data, final Exception exception) {
this(data, exception, null);
}
public SingleResponse(final Data data, final Exception exception, final Bundle extras) {
this.data = data;
this.exception = exception;
this.extras = extras != null ? extras : new Bundle();
}
public SingleResponse(final Data data, final Exception exception, final Bundle extras) {
this.data = data;
this.exception = exception;
this.extras = extras != null ? extras : new Bundle();
}
@Override
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof SingleResponse)) return false;
final SingleResponse<?> other = (SingleResponse<?>) obj;
if (getData() == null) {
if (other.getData() != null) return false;
} else if (!getData().equals(other.getData())) return false;
if (exception == null) {
if (other.exception != null) return false;
} else if (!exception.equals(other.exception)) return false;
@Override
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof SingleResponse)) return false;
final SingleResponse<?> other = (SingleResponse<?>) obj;
if (getData() == null) {
if (other.getData() != null) return false;
} else if (!getData().equals(other.getData())) return false;
if (exception == null) {
if (other.exception != null) return false;
} else if (!exception.equals(other.exception)) return false;
if (!getExtras().equals(other.getExtras())) return false;
return true;
}
return true;
}
public Data getData() {
return data;
}
@Override
public Data getData() {
return data;
}
public Exception getException() {
return exception;
}
@Override
public Exception getException() {
return exception;
}
@Override
@NonNull
public Bundle getExtras() {
return extras;
}
public Bundle getExtras() {
return extras;
}
public boolean hasData() {
return getData() != null;
}
@Override
public boolean hasData() {
return getData() != null;
}
public boolean hasException() {
return exception != null;
}
@Override
public boolean hasException() {
return exception != null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (getData() == null ? 0 : getData().hashCode());
result = prime * result + (exception == null ? 0 : exception.hashCode());
result = prime * result + (getExtras().hashCode());
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (getData() == null ? 0 : getData().hashCode());
result = prime * result + (exception == null ? 0 : exception.hashCode());
result = prime * result + (getExtras().hashCode());
return result;
}
public static <T> SingleResponse<T> getInstance() {
return new SingleResponse<>(null, null);
}
public static <T> SingleResponse<T> getInstance() {
return new SingleResponse<>(null, null);
}
public static <T> SingleResponse<T> getInstance(final Exception exception) {
return new SingleResponse<>(null, exception);
}
public static <T> SingleResponse<T> getInstance(final Exception exception) {
return new SingleResponse<>(null, exception);
}
public static <T> SingleResponse<T> getInstance(final T data) {
return new SingleResponse<>(data, null);
}
public static <T> SingleResponse<T> getInstance(final T data) {
return new SingleResponse<>(data, null);
}
public static <T> SingleResponse<T> getInstance(final T data, final Exception exception) {
return new SingleResponse<>(data, exception);
}
public static <T> SingleResponse<T> getInstance(final T data, final Exception exception) {
return new SingleResponse<>(data, exception);
}
}

View File

@ -186,32 +186,34 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
private static PendingIntent getMarkReadDeleteIntent(Context context, String type, long accountId, long position, long extraId) {
// Setup delete intent
final Intent recvIntent = new Intent(context, NotificationReceiver.class);
recvIntent.setAction(BROADCAST_NOTIFICATION_DELETED);
final Uri.Builder recvLinkBuilder = new Uri.Builder();
recvLinkBuilder.scheme(SCHEME_TWIDERE);
recvLinkBuilder.authority(AUTHORITY_NOTIFICATIONS);
recvLinkBuilder.appendPath(type);
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITION, String.valueOf(position));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_EXTRA_ID, String.valueOf(extraId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
recvIntent.setData(recvLinkBuilder.build());
return PendingIntent.getBroadcast(context, 0, recvIntent, 0);
final Intent intent = new Intent(context, NotificationReceiver.class);
intent.setAction(BROADCAST_NOTIFICATION_DELETED);
final Uri.Builder linkBuilder = new Uri.Builder();
linkBuilder.scheme(SCHEME_TWIDERE);
linkBuilder.authority(AUTHORITY_NOTIFICATIONS);
linkBuilder.appendPath(type);
linkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
linkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITION, String.valueOf(position));
linkBuilder.appendQueryParameter(QUERY_PARAM_EXTRA_ID, String.valueOf(extraId));
linkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
linkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, type);
intent.setData(linkBuilder.build());
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
private static PendingIntent getMarkReadDeleteIntent(Context context, String type, long accountId, StringLongPair[] positions) {
// Setup delete intent
final Intent recvIntent = new Intent(context, NotificationReceiver.class);
final Uri.Builder recvLinkBuilder = new Uri.Builder();
recvLinkBuilder.scheme(SCHEME_TWIDERE);
recvLinkBuilder.authority(AUTHORITY_NOTIFICATIONS);
recvLinkBuilder.appendPath(type);
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITIONS, StringLongPair.toString(positions));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
recvIntent.setData(recvLinkBuilder.build());
return PendingIntent.getBroadcast(context, 0, recvIntent, 0);
final Intent intent = new Intent(context, NotificationReceiver.class);
final Uri.Builder linkBuilder = new Uri.Builder();
linkBuilder.scheme(SCHEME_TWIDERE);
linkBuilder.authority(AUTHORITY_NOTIFICATIONS);
linkBuilder.appendPath(type);
linkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
linkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITIONS, StringLongPair.toString(positions));
linkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
linkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, type);
intent.setData(linkBuilder.build());
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
private static Cursor getPreferencesCursor(final SharedPreferencesWrapper preferences, final String key) {
@ -1238,7 +1240,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
mNameFirst, false)));
builder.setContentText(text);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(getStatusContentIntent(context, accountId, statusId));
builder.setContentIntent(getStatusContentIntent(context, AUTHORITY_MENTIONS, accountId,
statusId));
builder.setDeleteIntent(getMarkReadDeleteIntent(context, AUTHORITY_MENTIONS, accountId,
statusId, statusId));
builder.setWhen(statusCursor.getLong(indices.status_timestamp));
@ -1355,11 +1358,12 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_FROM_NOTIFICATION, String.valueOf(true));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, type);
homeIntent.setData(homeLinkBuilder.build());
return PendingIntent.getActivity(context, 0, homeIntent, 0);
}
private PendingIntent getStatusContentIntent(Context context, long accountId, long statusId) {
private PendingIntent getStatusContentIntent(Context context, String type, long accountId, long statusId) {
// Setup click intent
final Intent homeIntent = new Intent(Intent.ACTION_VIEW);
homeIntent.setPackage(BuildConfig.APPLICATION_ID);
@ -1371,6 +1375,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_EXTRA_ID, String.valueOf(statusId));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_FROM_NOTIFICATION, String.valueOf(true));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, type);
homeIntent.setData(homeLinkBuilder.build());
return PendingIntent.getActivity(context, 0, homeIntent, 0);
}

View File

@ -48,7 +48,7 @@ public class NotificationReceiver extends BroadcastReceiver implements Constants
case BROADCAST_NOTIFICATION_DELETED: {
final Uri uri = intent.getData();
if (uri == null) return;
final String type = uri.getLastPathSegment();
final String type = uri.getQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE);
final long accountId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_ID), -1);
final long extraId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_EXTRA_ID), -1);
final long timestamp = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_TIMESTAMP), -1);

View File

@ -59,10 +59,10 @@ public class CacheUsersStatusesTask extends AsyncTask<TwitterListResponse<Status
final Extractor extractor = new Extractor();
for (final TwitterListResponse<org.mariotaku.twidere.api.twitter.model.Status> response : args) {
if (response == null || response.list == null) {
if (response == null || !response.hasData()) {
continue;
}
final List<org.mariotaku.twidere.api.twitter.model.Status> list = response.list;
final List<org.mariotaku.twidere.api.twitter.model.Status> list = response.getData();
for (int bulkIdx = 0, totalSize = list.size(); bulkIdx < totalSize; bulkIdx += 100) {
for (int idx = bulkIdx, end = Math.min(totalSize, bulkIdx + ContentResolverUtils.MAX_BULK_COUNT); idx < end; idx++) {
final org.mariotaku.twidere.api.twitter.model.Status status = list.get(idx);

View File

@ -69,6 +69,7 @@ import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableStatusUpdate;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.model.Response;
import org.mariotaku.twidere.model.SingleResponse;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags;
@ -500,7 +501,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
private static <T extends SingleResponse<?>> Exception getException(List<T> responses) {
private static <T extends Response<?>> Exception getException(List<T> responses) {
for (T response : responses) {
if (response.hasException()) return response.getException();
}
@ -1036,7 +1037,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
@Override
protected void onPostExecute(final ListResponse<Long> result) {
if (result.list != null) {
if (result.hasData()) {
Utils.showInfoMessage(mContext, R.string.users_blocked, false);
} else {
Utils.showErrorMessage(mContext, R.string.action_blocking, result.getException(), true);
@ -2496,7 +2497,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
@Override
protected void onPostExecute(final ListResponse<Long> result) {
if (result != null) {
final String user_id_where = ListUtils.toString(result.list, ',', false);
final String user_id_where = ListUtils.toString(result.getData(), ',', false);
for (final Uri uri : TwidereDataStore.STATUSES_URIS) {
final Expression where = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, account_id),
new Expression(String.format(Locale.ROOT, "%s IN (%s)", Statuses.USER_ID, user_id_where)));
@ -2652,7 +2653,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
@Override
protected SingleResponse<Boolean> doInBackground(final Object... args) {
if (response == null) return SingleResponse.getInstance(false);
final List<Trends> messages = response.list;
final List<Trends> messages = response.getData();
final ArrayList<String> hashtags = new ArrayList<>();
final ArrayList<ContentValues> hashtagValues = new ArrayList<>();
if (messages != null && messages.size() > 0) {

View File

@ -75,9 +75,12 @@ public class ContentListScrollListener extends OnScrollListener {
final ILoadMoreSupportAdapter loadMoreAdapter = (ILoadMoreSupportAdapter) adapter;
final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (!mContentListSupport.isRefreshing() && loadMoreAdapter.isLoadMoreSupported()
&& !loadMoreAdapter.isLoadMoreIndicatorVisible()
&& layoutManager.findLastVisibleItemPosition() == layoutManager.getItemCount() - 1) {
mContentListSupport.onLoadMoreContents();
&& !loadMoreAdapter.isLoadMoreIndicatorVisible()) {
if (layoutManager.findLastVisibleItemPosition() == layoutManager.getItemCount() - 1) {
mContentListSupport.onLoadMoreContents(false);
} else if (layoutManager.findFirstVisibleItemPosition() == 0) {
mContentListSupport.onLoadMoreContents(true);
}
}
}
@ -87,7 +90,7 @@ public class ContentListScrollListener extends OnScrollListener {
boolean isRefreshing();
void onLoadMoreContents();
void onLoadMoreContents(boolean fromStart);
void setControlVisible(boolean visible);

View File

@ -124,9 +124,11 @@ public class ThemeUtils implements Constants {
icon.mutate();
if (info instanceof TwidereMenuInfo) {
final TwidereMenuInfo sInfo = (TwidereMenuInfo) info;
icon.setColorFilter(sInfo.isHighlight() ?
sInfo.getHighlightColor(highlightColor) : color, mode);
} else {
final int stateColor = sInfo.isHighlight() ? sInfo.getHighlightColor(highlightColor) : color;
if (stateColor != 0) {
icon.setColorFilter(stateColor, mode);
}
} else if (color != 0) {
icon.setColorFilter(color, mode);
}
}

View File

@ -23,11 +23,13 @@ import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatCallback;
import android.support.v7.internal.view.StandaloneActionMode;
import android.support.v7.internal.view.SupportActionModeWrapper;
import android.support.v7.internal.widget.ActionBarContextView;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.Menu;
@ -38,8 +40,9 @@ import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
import android.widget.PopupWindow;
import org.mariotaku.twidere.activity.iface.IAppCompatActivity;
import org.mariotaku.twidere.activity.iface.IThemedActivity;
import org.mariotaku.twidere.fragment.iface.IBaseFragment;
import org.mariotaku.twidere.view.AppCompatUtils;
import org.mariotaku.twidere.view.TintedStatusNativeActionModeAwareLayout;
/**
@ -110,7 +113,6 @@ public class TwidereActionModeForChildListener implements TintedStatusNativeActi
mActionModePopup = new PopupWindow(actionBarContext, null,
android.support.v7.appcompat.R.attr.actionModePopupWindowStyle);
mActionModePopup.setContentView(mActionModeView);
mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
final TypedValue outValue = new TypedValue();
actionBarContext.getTheme().resolveAttribute(
@ -122,13 +124,21 @@ public class TwidereActionModeForChildListener implements TintedStatusNativeActi
mThemed.getCurrentThemeResourceId(), mThemed.getCurrentThemeColor(),
mThemed.getCurrentThemeBackgroundOption(), false);
mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
final int actionModeOffset = getActionModeOffset();
final Rect actionModeBounds = getActionModeBounds();
if (actionModeBounds != null) {
mActionModePopup.setWidth(actionModeBounds.width());
} else {
mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
}
mShowActionModePopup = new Runnable() {
@Override
public void run() {
mActionModePopup.showAtLocation(
mWindow.getDecorView(),
Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, actionModeOffset);
if (actionModeBounds != null) {
mActionModePopup.showAtLocation(mWindow.getDecorView(), Gravity.TOP | Gravity.LEFT,
actionModeBounds.left, actionModeBounds.top);
} else {
mActionModePopup.showAtLocation(mWindow.getDecorView(), Gravity.TOP | Gravity.LEFT, 0, 0);
}
}
};
}
@ -162,14 +172,22 @@ public class TwidereActionModeForChildListener implements TintedStatusNativeActi
return mActionMode;
}
private int getActionModeOffset() {
if (mActivity instanceof IBaseFragment.SystemWindowsInsetsCallback) {
final Rect insets = new Rect();
if (((IBaseFragment.SystemWindowsInsetsCallback) mActivity).getSystemWindowsInsets(insets)) {
return Utils.getInsetsTopWithoutActionBarHeight(mActivity, insets.top);
private Rect getActionModeBounds() {
if (mActivity instanceof IAppCompatActivity) {
final Rect bounds = new Rect();
final int[] location = new int[2];
final ActionBar actionBar = ((IAppCompatActivity) mActivity).getSupportActionBar();
final Toolbar toolbar = AppCompatUtils.findToolbarForActionBar(actionBar);
if (toolbar != null) {
toolbar.getLocationInWindow(location);
bounds.left = location[0];
bounds.top = location[1];
bounds.right = bounds.left + toolbar.getWidth();
bounds.bottom = bounds.top + toolbar.getHeight();
return bounds;
}
}
return 0;
return null;
}
public boolean finishExisting() {

View File

@ -3948,10 +3948,7 @@ public final class Utils implements Constants {
public static void logOpenNotificationFromUri(Context context, Uri uri) {
if (!uri.getBooleanQueryParameter(QUERY_PARAM_FROM_NOTIFICATION, false)) return;
String type = uri.getLastPathSegment();
if (type == null) {
type = uri.getAuthority();
}
final String type = uri.getQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE);
final long accountId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_ID), -1);
final long extraId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_EXTRA_ID), -1);
final long timestamp = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_TIMESTAMP), -1);
@ -3961,6 +3958,11 @@ public final class Utils implements Constants {
logger.log(accountId, NotificationEvent.open(context, timestamp, type, accountId, extraId));
}
public static boolean hasOfficialAPIAccess(Context context, SharedPreferences preferences, ParcelableCredentials account) {
if (preferences.getBoolean(KEY_FORCE_USING_PRIVATE_APIS, false)) return true;
return isOfficialCredentials(context, account);
}
static class UtilsL {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)

View File

@ -0,0 +1,44 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.view;
import android.support.v7.app.ActionBar;
import android.support.v7.internal.app.ToolbarActionBar;
import android.support.v7.internal.widget.DecorToolbar;
import android.support.v7.widget.Toolbar;
import org.mariotaku.twidere.util.Utils;
/**
* Created by mariotaku on 15/10/12.
*/
public class AppCompatUtils {
public static Toolbar findToolbarForActionBar(ActionBar actionBar) {
if (actionBar instanceof ToolbarActionBar) {
final Object decorToolbar = Utils.findFieldOfTypes(actionBar, ToolbarActionBar.class, DecorToolbar.class);
if (decorToolbar instanceof DecorToolbar) {
return (Toolbar) ((DecorToolbar) decorToolbar).getViewGroup();
}
}
return null;
}
}

View File

@ -42,20 +42,20 @@ public class SquareShapedImageView extends ShapedImageView {
final int width = MeasureSpec.getSize(widthMeasureSpec), height = MeasureSpec.getSize(heightMeasureSpec);
final ViewGroup.LayoutParams lp = getLayoutParams();
if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT && lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
setMeasuredDimension(height, height);
super.onMeasure(makeSpec(heightMeasureSpec, MeasureSpec.EXACTLY), makeSpec(heightMeasureSpec, MeasureSpec.EXACTLY));
} else if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT && lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
setMeasuredDimension(width, width);
super.onMeasure(makeSpec(widthMeasureSpec, MeasureSpec.EXACTLY), makeSpec(widthMeasureSpec, MeasureSpec.EXACTLY));
} else {
if (width > height) {
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
setMeasuredDimension(height, height);
super.onMeasure(makeSpec(heightMeasureSpec, MeasureSpec.EXACTLY), makeSpec(heightMeasureSpec, MeasureSpec.EXACTLY));
} else {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
setMeasuredDimension(width, width);
super.onMeasure(makeSpec(widthMeasureSpec, MeasureSpec.EXACTLY), makeSpec(widthMeasureSpec, MeasureSpec.EXACTLY));
}
}
}
private static int makeSpec(int spec, int mode) {
return MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(spec), mode);
}
}

View File

@ -17,8 +17,7 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -41,28 +40,34 @@
android:paddingStart="@dimen/element_spacing_msmall"
android:paddingTop="@dimen/element_spacing_msmall">
<org.mariotaku.twidere.view.SquareShapedImageView
android:id="@+id/account_profile_image"
style="?profileImageStyle"
<org.mariotaku.twidere.view.SquareFrameLayout
android:id="@+id/account_profile_image_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
app:sivBackgroundColor="?android:colorBackground"
app:sivBorder="true"
app:sivBorderWidth="@dimen/line_width_compose_account_profile_image"/>
android:layout_height="match_parent">
<org.mariotaku.twidere.view.ShapedImageView
android:id="@+id/account_profile_image"
style="?profileImageStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:sivBackgroundColor="?android:colorBackground"
app:sivBorder="true"
app:sivBorderWidth="@dimen/line_width_compose_account_profile_image" />
</org.mariotaku.twidere.view.SquareFrameLayout>
<org.mariotaku.twidere.view.BadgeView
android:id="@+id/accounts_count"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="@id/account_profile_image"
android:layout_alignEnd="@id/account_profile_image"
android:layout_alignLeft="@id/account_profile_image"
android:layout_alignRight="@id/account_profile_image"
android:layout_alignStart="@id/account_profile_image"
android:layout_alignTop="@id/account_profile_image"
android:layout_alignBottom="@id/account_profile_image_frame"
android:layout_alignEnd="@id/account_profile_image_frame"
android:layout_alignLeft="@id/account_profile_image_frame"
android:layout_alignRight="@id/account_profile_image_frame"
android:layout_alignStart="@id/account_profile_image_frame"
android:layout_alignTop="@id/account_profile_image_frame"
android:layout_gravity="center"
android:textColor="?android:colorForeground"/>
android:textColor="?android:colorForeground" />
</RelativeLayout>
@ -80,7 +85,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@null"/>
android:background="@null" />
</FrameLayout>
<LinearLayout
@ -107,7 +112,7 @@
android:gravity="center"
android:minWidth="@dimen/element_size_small"
android:textAppearance="?android:textAppearanceSmall"
tools:text="140"/>
tools:text="140" />
<org.mariotaku.twidere.view.ActionIconView
android:layout_width="wrap_content"
@ -117,7 +122,7 @@
android:cropToPadding="false"
android:padding="@dimen/element_spacing_xsmall"
android:scaleType="centerCrop"
android:src="@drawable/ic_action_send"/>
android:src="@drawable/ic_action_send" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
@ -18,19 +17,18 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.mariotaku.twidere.view.SquareFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<org.mariotaku.twidere.view.SquareFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="@dimen/element_spacing_msmall">
<org.mariotaku.twidere.view.SquareShapedImageView
<org.mariotaku.twidere.view.ShapedImageView
android:id="@android:id/icon"
style="?profileImageStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:sivBorder="true"
app:sivBorderWidth="1.5dp"/>
app:sivBorderWidth="1.5dp" />
</org.mariotaku.twidere.view.SquareFrameLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Twidere - Twitter client for Android
<?xml version="1.0" encoding="utf-8"?><!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
~
@ -18,18 +17,16 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="?android:actionBarSize"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:activatedBackgroundIndicator"
android:orientation="vertical">
android:orientation="vertical"
android:padding="@dimen/element_spacing_normal">
<org.mariotaku.twidere.view.SquareShapedImageView
android:id="@android:id/icon"
style="?profileImageStyle"
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/element_spacing_normal"/>
android:gravity="center" />
</LinearLayout>

View File

@ -62,7 +62,7 @@
android:gravity="center_vertical"
android:orientation="horizontal">
<org.mariotaku.twidere.view.SquareShapedImageView
<org.mariotaku.twidere.view.ShapedImageView
android:id="@+id/activity_profile_image_0"
style="?profileImageStyle"
android:layout_width="@dimen/profile_image_size_activity_small"
@ -70,7 +70,7 @@
android:layout_margin="2dp"
android:contentDescription="@string/profile_image" />
<org.mariotaku.twidere.view.SquareShapedImageView
<org.mariotaku.twidere.view.ShapedImageView
android:id="@+id/activity_profile_image_1"
style="?profileImageStyle"
android:layout_width="@dimen/profile_image_size_activity_small"
@ -78,7 +78,7 @@
android:layout_margin="2dp"
android:contentDescription="@string/profile_image" />
<org.mariotaku.twidere.view.SquareShapedImageView
<org.mariotaku.twidere.view.ShapedImageView
android:id="@+id/activity_profile_image_2"
style="?profileImageStyle"
android:layout_width="@dimen/profile_image_size_activity_small"
@ -86,7 +86,7 @@
android:layout_margin="2dp"
android:contentDescription="@string/profile_image" />
<org.mariotaku.twidere.view.SquareShapedImageView
<org.mariotaku.twidere.view.ShapedImageView
android:id="@+id/activity_profile_image_3"
style="?profileImageStyle"
android:layout_width="@dimen/profile_image_size_activity_small"
@ -94,7 +94,7 @@
android:layout_margin="2dp"
android:contentDescription="@string/profile_image" />
<org.mariotaku.twidere.view.SquareShapedImageView
<org.mariotaku.twidere.view.ShapedImageView
android:id="@+id/activity_profile_image_4"
style="?profileImageStyle"
android:layout_width="@dimen/profile_image_size_activity_small"

View File

@ -32,7 +32,7 @@
app:ignorePadding="true"
tools:context=".adapter.DirectMessagesEntryAdapter">
<org.mariotaku.twidere.view.SquareShapedImageView
<org.mariotaku.twidere.view.ShapedImageView
android:id="@+id/profile_image"
style="?profileImageStyle"
android:layout_width="@dimen/icon_size_card_list_item"

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
@ -18,13 +17,13 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.mariotaku.twidere.view.SquareShapedImageView
android:id="@+id/color"
<org.mariotaku.twidere.view.ShapedImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/color"
android:layout_width="@dimen/element_size_small"
android:layout_height="@dimen/element_size_small"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
app:sivShape="circle"/>
app:sivShape="circle" />

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_description">Your own Twitter app</string>
<string name="compose">Compose</string>
<string name="add_account">Add account</string>
<string name="settings">Settings</string>
@ -386,7 +387,7 @@
<string name="preview_images">Preview images</string>
<string name="preload_wifi_only">Preload using Wi-Fi only</string>
<string name="sign_in_method_introduction_title">How does it work?</string>
<string name="sign_in_method_introduction">Most clients opens a webpage to authorize to Twitter, this could be inconvenient when using custom API, or on slow network. Twidere simulates a normal browser to help sign in to Twitter. Don\'t worry, your password will never be stored nor leaked.</string>
<string name="sign_in_method_introduction">Most Twitter apps opens a webpage to authorize to Twitter, this could be inconvenient when using custom API, or on slow network. Twidere simulates a normal browser to help sign in to Twitter. Don\'t worry, your password will never be stored nor leaked.</string>
<string name="quote_protected_status_notice">It\'s not recommended to quote protected tweets.</string>
<string name="edit_draft">Edit draft</string>
<string name="profile_image">Profile image</string>
@ -791,5 +792,8 @@
<string name="combined_notifications_summary_on">Notifications will be grouped</string>
<string name="combined_notifications_summary_off">Notifications will be displayed separately</string>
<string name="save_media_no_storage_permission_message">Storage permission is needed to save media.</string>
<string name="select_file_no_storage_permission_message">Storage permission is needed to select file.</string>
<string name="user_mentioned_you"><xliff:g id="name">%s</xliff:g> mentioned you</string>
<string name="cant_load_all_replies_message">Can\'t load all replies. &lt;a href=\"#dialog\";&gt;Why?&lt;/a&gt;</string>
<string name="cant_load_all_replies_explanation">Due to Twitter\'s limitation to third party twitter apps, Twidere has no access to replies to a tweet, there\'s no guarantee that Twidere can load all replies to a tweet.</string>
</resources>