Merge "New message view command layout"

This commit is contained in:
Makoto Onuki 2010-11-10 14:09:38 -08:00 committed by Android (Google) Code Review
commit 77303a483b
11 changed files with 260 additions and 182 deletions

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<!--
Landscape, where we show no buttons.
-->
<!-- extends RelativeLayout -->
<com.android.email.activity.MessageCommandButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
>
</com.android.email.activity.MessageCommandButtonView>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<!-- Portrait -->
<!-- extends RelativeLayout -->
<com.android.email.activity.MessageCommandButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:paddingTop="5dip"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:paddingBottom="1dip"
android:background="@android:drawable/bottom_bar"
>
<Button
android:id="@+id/move_to_newer_button"
android:text="@string/message_view_move_to_newer"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
/>
<Button
android:id="@+id/move_to_older_button"
android:text="@string/message_view_move_to_older"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
/>
<TextView
android:id="@+id/message_position"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_toRightOf="@id/move_to_newer_button"
android:layout_toLeftOf="@id/move_to_older_button"
android:gravity="center"
android:layout_centerVertical="true"
/>
</com.android.email.activity.MessageCommandButtonView>

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<!-- It extends LinearLayout -->
<com.android.email.activity.MessageCommandButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:paddingTop="5dip"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:paddingBottom="1dip"
android:background="@android:drawable/bottom_bar"
>
<Button
android:id="@+id/move_button"
android:text="@string/move_action"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/delete_button"
android:text="@string/delete_action"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/unread_button"
android:text="@string/unread_action"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/reply_button"
android:text="@string/reply_action"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1"
android:visibility="gone"
/>
<Button
android:id="@+id/reply_all_button"
android:text="@string/reply_all_action"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1"
android:visibility="gone"
/>
<Button
android:id="@+id/forward_button"
android:text="@string/forward_action"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1"
android:visibility="gone"
/>
<Button
android:id="@+id/move_to_newer_button"
android:text="@string/message_view_move_to_newer"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1"
/>
<Button
android:id="@+id/move_to_older_button"
android:text="@string/message_view_move_to_older"
android:layout_height="match_parent"
android:layout_width="0dip"
android:layout_weight="1"
/>
</com.android.email.activity.MessageCommandButtonView>

View File

@ -33,7 +33,7 @@
/>
<item
android:id="@+id/account_settings"
android:orderInCategory="300"
android:orderInCategory="2000"
android:title="@string/account_settings_action"
android:icon="@android:drawable/ic_menu_preferences"
/>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- STOPSHIP Need icons, Don't need text -->
<item
android:id="@+id/move"
android:orderInCategory="1000"
android:title="@string/move_action"
android:showAsAction="ifRoom"
/>
<item
android:id="@+id/delete"
android:orderInCategory="1100"
android:title="@string/delete_action"
android:showAsAction="ifRoom"
/>
<item
android:id="@+id/mark_as_unread"
android:orderInCategory="1200"
android:title="@string/mark_as_unread_action"
android:showAsAction="ifRoom"
/>
</menu>

View File

@ -120,7 +120,7 @@
<!-- Menu item -->
<string name="mark_as_unread_action">Mark as unread</string>
<!-- Menu item for moving messages to folders [CHAR LIMIT=10] -->
<string name="move_action" translatable="false">Move</string>
<string name="move_action">Move</string>
<!-- Menu item / button label for adding CC/BCC fields on message compose. [CHAR LIMIT=16] -->
<string name="add_cc_bcc_action">+ Cc/Bcc</string>
<!-- Menu item -->
@ -426,10 +426,10 @@ save attachment.</string>
<item quantity="other"><xliff:g id="size_in_gigabytes" example="279">%d</xliff:g>GB</item>
</plurals>
<!-- Do Not Translate. The label of the next button on the message view screen. -->
<string name="message_view_move_to_newer" translatable="false">&lt;</string>
<!-- Do Not Translate. The label of the previous button on the message view screen. -->
<string name="message_view_move_to_older" translatable="false">&gt;</string>
<!-- The label of the next button on the message view screen. -->
<string name="message_view_move_to_newer">Newer</string>
<!-- The label of the previous button on the message view screen. -->
<string name="message_view_move_to_older">Older</string>
<!-- The message snippet, of the form subject + separator + start of text -->
<string name="message_list_snippet"><xliff:g id="subject" example="Re: Foo">
@ -926,6 +926,9 @@ save attachment.</string>
<!-- General preference: Text zoom. Value is "huge" (+2) [CHAR LIMIT=32] -->
<string name="general_preference_text_zoom_huge">Huge</string>
<!-- Generic string for "current position" / "total number" [CHAR LIMIT=12] -->
<string name="position_of_count"><xliff:g example="1">%1$d</xliff:g> of <xliff:g
example="12">%2$s</xliff:g></string>
<!--
Strings for temporary UI
STOPSHIP Remove them or move them up and make translatable

View File

@ -20,50 +20,39 @@ import com.android.email.R;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* A View that is shown at the bottom of {@link MessageViewFragment} and contains buttons such
* as "Delete" "Mark unread".
* as "(move to) newer".
*
* This class is meant to hide layout differences between portrait and landscape, if any.
* e.g. We might combine some of the buttons when we have small real estate.
*/
public class MessageCommandButtonView extends LinearLayout implements View.OnClickListener {
public class MessageCommandButtonView extends RelativeLayout implements View.OnClickListener {
/**
* If false, we don't want to show anything, in which case all fields holding a view
* (e.g. {@link #mMoveToNewerButton}) are null.
*/
private boolean mShowPanel;
private View mMoveToNewerButton;
private View mMoveToOlderButton;
private View mForwardButton;
private View mReplyButton;
private View mReplyAllButton;
private View mDeleteButton;
private View mMarkUneadButton;
private View mMoveButton;
private TextView mMessagePosition;
private Callback mCallback = EmptyCallback.INSTANCE;
public interface Callback {
public void onMoveToNewer();
public void onMoveToOlder();
public void onForward();
public void onReply();
public void onReplyAll();
public void onDelete();
public void onMarkUnread();
public void onMove();
}
private static class EmptyCallback implements Callback {
public static final Callback INSTANCE = new EmptyCallback();
@Override public void onDelete() {}
@Override public void onForward() {}
@Override public void onMarkUnread() {}
@Override public void onMove() {}
@Override public void onMoveToNewer() {}
@Override public void onMoveToOlder() {}
@Override public void onReply() {}
@Override public void onReplyAll() {}
}
public MessageCommandButtonView(Context context, AttributeSet attrs, int defStyle) {
@ -83,37 +72,39 @@ public class MessageCommandButtonView extends LinearLayout implements View.OnCli
super.onFinishInflate();
mMoveToNewerButton = findViewById(R.id.move_to_newer_button);
if (mMoveToNewerButton == null) {
mShowPanel = false;
return;
}
mShowPanel = true;
mMoveToOlderButton = findViewById(R.id.move_to_older_button);
mForwardButton = findViewById(R.id.forward_button);
mReplyButton = findViewById(R.id.reply_button);
mReplyAllButton = findViewById(R.id.reply_all_button);
mDeleteButton = findViewById(R.id.delete_button);
mMarkUneadButton = findViewById(R.id.unread_button);
mMoveButton = findViewById(R.id.move_button);
mMessagePosition = (TextView) findViewById(R.id.message_position);
mMoveToNewerButton.setOnClickListener(this);
mMoveToOlderButton.setOnClickListener(this);
mForwardButton.setOnClickListener(this);
mReplyButton.setOnClickListener(this);
mReplyAllButton.setOnClickListener(this);
mDeleteButton.setOnClickListener(this);
mMarkUneadButton.setOnClickListener(this);
mMoveButton.setOnClickListener(this);
}
public void setCallback(Callback callback) {
mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
}
public void enableReplyForwardButtons(boolean enabled) {
mForwardButton.setEnabled(enabled);
mReplyButton.setEnabled(enabled);
mReplyAllButton.setEnabled(enabled);
}
public void enableNavigationButons(boolean enableMoveToNewer, boolean enableMoveToOlder) {
public void enableNavigationButtons(boolean enableMoveToNewer, boolean enableMoveToOlder,
int currentPosition, int countMessages) {
if (!mShowPanel) {
return;
}
mMoveToNewerButton.setEnabled(enableMoveToNewer);
mMoveToOlderButton.setEnabled(enableMoveToOlder);
// Show "POSITION of TOTAL"
final String positionOfCount;
if (countMessages == 0) {
positionOfCount = "";
} else {
positionOfCount = getContext().getResources().getString(R.string.position_of_count,
(currentPosition + 1), countMessages);
}
mMessagePosition.setText(positionOfCount);
}
@Override
@ -125,24 +116,6 @@ public class MessageCommandButtonView extends LinearLayout implements View.OnCli
case R.id.move_to_older_button:
mCallback.onMoveToOlder();
break;
case R.id.forward_button:
mCallback.onForward();
break;
case R.id.reply_button:
mCallback.onReply();
break;
case R.id.reply_all_button:
mCallback.onReplyAll();
break;
case R.id.delete_button:
mCallback.onDelete();
break;
case R.id.unread_button:
mCallback.onMarkUnread();
break;
case R.id.move_button:
mCallback.onMove();
break;
}
}
}

View File

@ -310,10 +310,10 @@ public class MessageListXL extends Activity implements
return;
}
if (mOrderManager == null) {
f.enableNavigationButons(false, false); // shouldn't happen, but just in case
f.enableNavigationButons(false, false, 0, 0); // shouldn't happen, but just in case
} else {
f.enableNavigationButons(mOrderManager.canMoveToNewer(),
mOrderManager.canMoveToOlder());
f.enableNavigationButons(mOrderManager.canMoveToNewer(), mOrderManager.canMoveToOlder(),
mOrderManager.getCurrentPosition(), mOrderManager.getTotalMessageCount());
}
}

View File

@ -59,6 +59,10 @@ public class MessageOrderManager {
private long mCurrentMessageId = -1;
private int mTotalMessageCount;
private int mCurrentPosition;
private boolean mClosed = false;
public interface Callback {
@ -94,6 +98,20 @@ public class MessageOrderManager {
return mMailboxId;
}
/**
* @return the total number of messages.
*/
public int getTotalMessageCount() {
return mTotalMessageCount;
}
/**
* @return current cursor position, starting from 0.
*/
public int getCurrentPosition() {
return mCurrentPosition;
}
/**
* @return a {@link Handler} for {@link ContentObserver}.
*
@ -171,6 +189,7 @@ public class MessageOrderManager {
}
private void adjustCursorPosition() {
mCurrentPosition = 0;
if (mCurrentMessageId == -1) {
return; // Current ID not specified yet.
}
@ -182,8 +201,10 @@ public class MessageOrderManager {
mCursor.moveToPosition(-1);
while (mCursor.moveToNext()
&& mCursor.getLong(EmailContent.ID_PROJECTION_COLUMN) != mCurrentMessageId) {
mCurrentPosition++;
}
if (mCursor.isAfterLast()) {
mCurrentPosition = 0;
mCallback.onMessageNotFound(); // Message not found... Already deleted?
} else {
mCallback.onMessagesChanged();
@ -214,6 +235,7 @@ public class MessageOrderManager {
*/
public boolean moveToOlder() {
if (canMoveToOlder() && mCursor.moveToNext()) {
mCurrentPosition++;
setCurrentMessageIdFromCursor();
mCallback.onMessagesChanged();
return true;
@ -229,6 +251,7 @@ public class MessageOrderManager {
*/
public boolean moveToNewer() {
if (canMoveToNewer() && mCursor.moveToPrevious()) {
mCurrentPosition--;
setCurrentMessageIdFromCursor();
mCallback.onMessagesChanged();
return true;
@ -289,9 +312,12 @@ public class MessageOrderManager {
try {
closeCursor();
if (cursor == null || cursor.isClosed()) {
mTotalMessageCount = 0;
mCurrentPosition = 0;
return; // Task canceled
}
mCursor = cursor;
mTotalMessageCount = mCursor.getCount();
mCursor.registerContentObserver(mObserver);
adjustCursorPosition();
} finally {

View File

@ -30,6 +30,9 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@ -188,6 +191,11 @@ public class MessageViewFragment extends MessageViewFragmentBase {
super.onResume();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.message_view_fragment_option, menu);
}
private void enableReplyForwardButtons(boolean enabled) {
// We don't have disabled button assets, so let's hide them for now
final int visibility = enabled ? View.VISIBLE : View.GONE;
@ -220,6 +228,12 @@ public class MessageViewFragment extends MessageViewFragmentBase {
synchronized (mLock) {
super.clearContent();
mMessageIdToOpen = -1;
// Hide the menu.
// This isn't really necessary if we're really hiding the fragment. However,
// for now, we're actually *not* hiding the fragment (just hiding the root view of it),
// so need to remove menus manually.
setHasOptionsMenu(false);
}
}
@ -259,10 +273,15 @@ public class MessageViewFragment extends MessageViewFragmentBase {
// Disable forward/reply buttons as necessary.
enableReplyForwardButtons(Mailbox.isMailboxTypeReplyAndForwardable(mailboxType));
// Show the menu when it's showing a message.
setHasOptionsMenu(true);
}
public void enableNavigationButons(boolean enableMoveToNewer, boolean enableMoveToOlder) {
mCommandButtons.enableNavigationButons(enableMoveToNewer, enableMoveToOlder);
public void enableNavigationButons(boolean enableMoveToNewer, boolean enableMoveToOlder,
int currentPosition, int countMessages) {
mCommandButtons.enableNavigationButtons(enableMoveToNewer, enableMoveToOlder,
currentPosition, countMessages);
}
/**
@ -358,6 +377,36 @@ public class MessageViewFragment extends MessageViewFragmentBase {
super.onClick(view);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.move:
onMove();
return true;
case R.id.delete:
onDelete();
return true;
case R.id.mark_as_unread:
onMarkAsUnread();
return true;
}
return super.onOptionsItemSelected(item);
}
private void onMove() {
mCallback.onMoveMessage();
}
private void onDelete() {
mCallback.onBeforeMessageDelete();
ActivityHelper.deleteMessage(getActivity(), mCurrentMessageId);
}
private void onMarkAsUnread() {
onMarkMessageAsRead(false);
mCallback.onMessageSetUnread();
}
/**
* {@inheritDoc}
*
@ -390,34 +439,5 @@ public class MessageViewFragment extends MessageViewFragmentBase {
public void onMoveToOlder() {
mCallback.onMoveToOlder();
}
@Override
public void onDelete() {
mCallback.onBeforeMessageDelete();
ActivityHelper.deleteMessage(getActivity(), mCurrentMessageId);
}
@Override
public void onMove() {
mCallback.onMoveMessage();
}
@Override
public void onForward() {
}
@Override
public void onReply() {
}
@Override
public void onReplyAll() {
}
@Override
public void onMarkUnread() {
onMarkMessageAsRead(false);
mCallback.onMessageSetUnread();
}
}
}

View File

@ -70,6 +70,10 @@ public class MessageOrderManagerTest extends ProviderTestCase2<EmailProvider> {
// Both callbacks shouldn't have called.
mCallback.assertCallbacksCalled(false, false);
// Cursor not open yet, so these are both 0.
assertEquals(0, mom.getCurrentPosition());
assertEquals(0, mom.getTotalMessageCount());
}
public void testSelection() {
@ -93,50 +97,61 @@ public class MessageOrderManagerTest extends ProviderTestCase2<EmailProvider> {
MyCursor cursor = new MyCursor(11, 22, 33, 44); // Newer to older
mom.onCursorOpenDone(cursor);
assertEquals(0, mom.getCurrentPosition());
assertEquals(4, mom.getTotalMessageCount());
// Current message id not set yet, so callback should have called yet.
mCallback.assertCallbacksCalled(false, false);
// Set current message id -- now onMessagesChanged() should get called.
mom.moveTo(22);
assertEquals(1, mom.getCurrentPosition());
mCallback.assertCallbacksCalled(true, false);
assertEquals(22, mom.getCurrentMessageId());
assertCanMove(mom, true, true);
// Move to row 1
assertTrue(mom.moveToNewer());
assertEquals(0, mom.getCurrentPosition());
assertEquals(11, mom.getCurrentMessageId());
assertCanMove(mom, false, true);
mCallback.assertCallbacksCalled(true, false);
// Try to move to newer, but no newer messages
assertFalse(mom.moveToNewer());
assertEquals(0, mom.getCurrentPosition());
assertEquals(11, mom.getCurrentMessageId()); // Still row 1
mCallback.assertCallbacksCalled(false, false);
// Move to row 2
assertTrue(mom.moveToOlder());
assertEquals(1, mom.getCurrentPosition());
assertEquals(22, mom.getCurrentMessageId());
assertCanMove(mom, true, true);
mCallback.assertCallbacksCalled(true, false);
// Move to row 3
assertTrue(mom.moveToOlder());
assertEquals(2, mom.getCurrentPosition());
assertEquals(33, mom.getCurrentMessageId());
assertCanMove(mom, true, true);
mCallback.assertCallbacksCalled(true, false);
// Move to row 4
assertTrue(mom.moveToOlder());
assertEquals(3, mom.getCurrentPosition());
assertEquals(44, mom.getCurrentMessageId());
assertCanMove(mom, true, false);
mCallback.assertCallbacksCalled(true, false);
// Try to move older, but no Older messages
assertFalse(mom.moveToOlder());
assertEquals(3, mom.getCurrentPosition());
mCallback.assertCallbacksCalled(false, false);
// Move to row 3
assertTrue(mom.moveToNewer());
assertEquals(2, mom.getCurrentPosition());
assertEquals(33, mom.getCurrentMessageId());
assertCanMove(mom, true, true);
mCallback.assertCallbacksCalled(true, false);
@ -158,6 +173,10 @@ public class MessageOrderManagerTest extends ProviderTestCase2<EmailProvider> {
mCallback.assertCallbacksCalled(false, false); // Cursor not open, callback not called yet.
assertEquals(22, mom.getCurrentMessageId());
// cursor not open yet
assertEquals(0, mom.getCurrentPosition());
assertEquals(0, mom.getTotalMessageCount());
// Inject mock cursor. (Imitate async query done.)
MyCursor cursor = new MyCursor(11, 22, 33, 44); // Newer to older
mom.onCursorOpenDone(cursor);
@ -165,6 +184,9 @@ public class MessageOrderManagerTest extends ProviderTestCase2<EmailProvider> {
// As soon as the cursor opens, callback gets called.
mCallback.assertCallbacksCalled(true, false);
assertEquals(22, mom.getCurrentMessageId());
assertEquals(1, mom.getCurrentPosition());
assertEquals(4, mom.getTotalMessageCount());
}
public void testContentChanged() {