blob: 3c4e2be20fdb1d973a92ff8d8cbcbbdedc3f4904 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mail.ui;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.Animator.AnimatorListener;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.Loader;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.mail.R;
import com.android.mail.browse.ConversationCursor;
import com.android.mail.content.ObjectCursor;
import com.android.mail.content.ObjectCursorLoader;
import com.android.mail.preferences.AccountPreferences;
import com.android.mail.providers.Account;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.Utils;
/**
* Tip that is displayed in conversation list of 'Sent' folder whenever there are
* one or more messages in the Outbox.
*/
public class ConversationsInOutboxTipView extends FrameLayout
implements ConversationSpecialItemView, SwipeableItemView {
private static int sScrollSlop = 0;
private static int sShrinkAnimationDuration;
private Account mAccount = null;
private AccountPreferences mAccountPreferences;
private AnimatedAdapter mAdapter;
private LoaderManager mLoaderManager;
private FolderSelector mFolderSelector;
private Folder mOutbox;
private int mOutboxCount = -1;
private View mSwipeableContent;
private TextView mText;
private int mAnimatedHeight = -1;
private View mTeaserRightEdge;
/** Whether we are on a tablet device or not */
private final boolean mTabletDevice;
/** When in conversation mode, true if the list is hidden */
private final boolean mListCollapsible;
private static final int LOADER_FOLDER_LIST =
AbstractActivityController.LAST_FRAGMENT_LOADER_ID + 100;
public ConversationsInOutboxTipView(final Context context) {
this(context, null);
}
public ConversationsInOutboxTipView(final Context context, final AttributeSet attrs) {
this(context, attrs, -1);
}
public ConversationsInOutboxTipView(
final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final Resources resources = context.getResources();
if (sScrollSlop == 0) {
sScrollSlop = resources.getInteger(R.integer.swipeScrollSlop);
sShrinkAnimationDuration = resources.getInteger(
R.integer.shrink_animation_duration);
}
mTabletDevice = Utils.useTabletUI(resources);
mListCollapsible = resources.getBoolean(R.bool.list_collapsible);
}
public void bind(final Account account, final FolderSelector folderSelector) {
mAccount = account;
mAccountPreferences = AccountPreferences.get(getContext(), account.getEmailAddress());
mFolderSelector = folderSelector;
}
@Override
public void onGetView() {
// Do nothing
}
@Override
protected void onFinishInflate() {
mSwipeableContent = findViewById(R.id.swipeable_content);
mText = (TextView) findViewById(R.id.outbox);
findViewById(R.id.outbox).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
goToOutbox();
}
});
findViewById(R.id.dismiss_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
mTeaserRightEdge = findViewById(R.id.teaser_right_edge);
}
private void goToOutbox() {
if (mOutbox != null) {
mFolderSelector.onFolderSelected(mOutbox);
}
}
@Override
public void onUpdate(Folder folder, ConversationCursor cursor) {
if (mLoaderManager != null && folder != null) {
if ((folder.type & UIProvider.FolderType.SENT) > 0) {
// Only display this tip if user is viewing the Sent folder
mLoaderManager.initLoader(LOADER_FOLDER_LIST, null, mFolderListLoaderCallbacks);
}
}
}
private final LoaderCallbacks<ObjectCursor<Folder>> mFolderListLoaderCallbacks =
new LoaderManager.LoaderCallbacks<ObjectCursor<Folder>>() {
@Override
public void onLoaderReset(final Loader<ObjectCursor<Folder>> loader) {
// Do nothing
}
@Override
public void onLoadFinished(final Loader<ObjectCursor<Folder>> loader,
final ObjectCursor<Folder> data) {
if (data != null && data.moveToFirst()) {
do {
final Folder folder = data.getModel();
if ((folder.type & UIProvider.FolderType.OUTBOX) > 0) {
mOutbox = folder;
onOutboxTotalCount(folder.totalCount);
}
} while (data.moveToNext());
}
}
@Override
public Loader<ObjectCursor<Folder>> onCreateLoader(final int id, final Bundle args) {
// This loads all folders in order to find 'Outbox'. We could consider adding a new
// query to load folders of a given type to make this more efficient, but should be
// okay for now since this is triggered infrequently (only when user visits the
// 'Sent' folder).
final ObjectCursorLoader<Folder> loader = new ObjectCursorLoader<Folder>(getContext(),
mAccount.folderListUri, UIProvider.FOLDERS_PROJECTION, Folder.FACTORY);
return loader;
}
};
private void onOutboxTotalCount(int outboxCount) {
if (mOutboxCount != outboxCount) {
mOutboxCount = outboxCount;
if (outboxCount > 0) {
if (mText != null) {
updateText();
}
}
}
if (outboxCount == 0) {
// Clear the last seen count, so that new messages in Outbox will always cause this
// tip to appear again.
mAccountPreferences.setLastSeenOutboxCount(0);
}
}
private void updateText() {
// Update the display text to reflect current mOutboxCount
final Resources resources = getContext().getResources();
final String subString = mOutbox.name;
final String entireString = resources.getString(
R.string.unsent_messages_in_outbox,
String.valueOf(mOutboxCount), subString);
final SpannableString text = new SpannableString(entireString);
final int index = entireString.indexOf(subString);
text.setSpan(
new TextAppearanceSpan(getContext(), R.style.LinksInTipTextAppearance),
index,
index + subString.length(),
0);
mText.setText(text);
}
@Override
public boolean getShouldDisplayInList() {
return (mOutboxCount > 0 && mOutboxCount != mAccountPreferences.getLastSeenOutboxCount());
}
@Override
public int getPosition() {
// We want this teaser to go before the first real conversation
return 0;
}
@Override
public void setAdapter(AnimatedAdapter adapter) {
mAdapter = adapter;
}
@Override
public void bindFragment(final LoaderManager loaderManager, final Bundle savedInstanceState) {
mLoaderManager = loaderManager;
}
@Override
public void cleanup() {
}
@Override
public void onConversationSelected() {
// DO NOTHING
}
@Override
public void onCabModeEntered() {
}
@Override
public void onCabModeExited() {
}
@Override
public void onConversationListVisibilityChanged(final boolean visible) {
// Do nothing
}
@Override
public void saveInstanceState(final Bundle outState) {
// Do nothing
}
@Override
public boolean acceptsUserTaps() {
return true;
}
@Override
public void dismiss() {
// Do not show this tip again until we have a new count. Note this is not quite
// ideal behavior since after a user dismisses an "1 unsent in outbox" tip,
// the message stuck in Outbox could get sent, and a new one gets stuck.
// If the user checks back on on Sent folder then, we don't reshow the message since count
// itself hasn't changed, but ideally we should since it's a different message than before.
// However if user checks the Sent folder in between (when there were 0 messages
// in Outbox), the preference is cleared (see {@link onOutboxTotalCount}).
mAccountPreferences.setLastSeenOutboxCount(mOutboxCount);
startDestroyAnimation();
}
@Override
public SwipeableView getSwipeableView() {
return SwipeableView.from(mSwipeableContent);
}
@Override
public boolean canChildBeDismissed() {
return true;
}
@Override
public float getMinAllowScrollDistance() {
return sScrollSlop;
}
private void startDestroyAnimation() {
final int start = getHeight();
final int end = 0;
mAnimatedHeight = start;
final ObjectAnimator heightAnimator =
ObjectAnimator.ofInt(this, "animatedHeight", start, end);
heightAnimator.setInterpolator(new DecelerateInterpolator(2.0f));
heightAnimator.setDuration(sShrinkAnimationDuration);
heightAnimator.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(final Animator animation) {
// Do nothing
}
@Override
public void onAnimationRepeat(final Animator animation) {
// Do nothing
}
@Override
public void onAnimationEnd(final Animator animation) {
// We should no longer exist, so notify the adapter
mAdapter.notifyDataSetChanged();
}
@Override
public void onAnimationCancel(final Animator animation) {
// Do nothing
}
});
heightAnimator.start();
}
/**
* This method is used by the animator. It is explicitly kept in proguard.flags to prevent it
* from being removed, inlined, or obfuscated.
* Edit ./vendor/unbundled/packages/apps/UnifiedGmail/proguard.flags
* In the future, we want to use @Keep
*/
public void setAnimatedHeight(final int height) {
mAnimatedHeight = height;
requestLayout();
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
if (Utils.getDisplayListRightEdgeEffect(mTabletDevice, mListCollapsible,
mAdapter.getViewMode())) {
mTeaserRightEdge.setVisibility(VISIBLE);
} else {
mTeaserRightEdge.setVisibility(GONE);
}
if (mAnimatedHeight == -1) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
setMeasuredDimension(View.MeasureSpec.getSize(widthMeasureSpec), mAnimatedHeight);
}
}
@Override
public boolean commitLeaveBehindItem() {
// This view has no leave-behind
return false;
}
}