Bug 1044947 - Frontend for share handler. r=rnewman

This commit is contained in:
Chris Kitching 2014-08-25 04:24:47 -07:00
parent 95ee3f0beb
commit 3163c45e73
35 changed files with 982 additions and 10 deletions

View File

@ -372,6 +372,21 @@
</provider>
#ifdef MOZ_ANDROID_SHARE_OVERLAY
<!-- Share overlay activity -->
<activity android:name="org.mozilla.gecko.overlays.ui.ShareDialog"
android:label="@string/overlay_share_header"
android:theme="@style/ShareOverlayActivity"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|locale|layoutDirection"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<!-- Service to handle requests from overlays. -->
<service android:name="org.mozilla.gecko.overlays.service.OverlayActionService" />
#endif

View File

@ -90,6 +90,23 @@
to display browser chrome. -->
<!ENTITY locale_system_default "System default">
<!-- Localization note (overlay_share_bookmark_btn_label) : This string is
used in the share overlay menu to select an action. It is the verb
"to bookmark", not the noun "a bookmark". -->
<!ENTITY overlay_share_bookmark_btn_label "Bookmark">
<!ENTITY overlay_share_reading_list_btn_label "Add to Reading List">
<!ENTITY overlay_share_header "Send to &brandShortName;">
<!ENTITY overlay_share_send_other "Send to other devices">
<!-- Localization note (overlay_share_send_tab_btn_label) : Used on the
share overlay menu to represent the "Send Tab" action when the user
either has not set up Sync, or has no other devices to send a tab
to. -->
<!ENTITY overlay_share_send_tab_btn_label "Send to another device">
<!ENTITY overlay_share_no_url "No link found in this share">
<!ENTITY overlay_share_retry "Try again">
<!ENTITY overlay_share_select_device "Select device">
<!ENTITY pref_category_search3 "Search">
<!ENTITY pref_category_search_summary "Customize your search providers">
<!ENTITY pref_category_display "Display">

View File

@ -488,7 +488,11 @@ if CONFIG['MOZ_ANDROID_SHARE_OVERLAY']:
'overlays/service/sharemethods/ParcelableClientRecord.java',
'overlays/service/sharemethods/SendTab.java',
'overlays/service/sharemethods/ShareMethod.java',
'overlays/ui/OverlayToastHelper.java'
'overlays/ui/OverlayToastHelper.java',
'overlays/ui/SendTabDeviceListArrayAdapter.java',
'overlays/ui/SendTabList.java',
'overlays/ui/SendTabTargetSelectedListener.java',
'overlays/ui/ShareDialog.java',
]
gbjar.sources += sync_java_files

View File

@ -48,7 +48,7 @@ public class OverlayConstants {
// The optional extra Parcelable parameters for a ShareMethod.
public static final String EXTRA_PARAMETERS = "EXTRA";
// The extra field key used for holding one or more share method names (See above).
// The extra field key used for holding the ShareMethod.Type we wish to use for an operation.
public static final String EXTRA_SHARE_METHOD = "SHARE_METHOD";
/*

View File

@ -29,21 +29,48 @@ public class OverlayToastHelper {
* @param isTransient Should a retry button be presented?
* @param retryListener Listener to fire when the retry button is pressed.
*/
public static void showFailureToast(Context context, String failureMessage, boolean isTransient, View.OnClickListener retryListener) {
showToast(context, failureMessage, isTransient, retryListener);
public static void showFailureToast(Context context, String failureMessage, View.OnClickListener retryListener) {
showToast(context, failureMessage, false, retryListener);
}
public static void showFailureToast(Context context, String failureMessage, boolean isTransient) {
showFailureToast(context, failureMessage, isTransient, null);
public static void showFailureToast(Context context, String failureMessage) {
showFailureToast(context, failureMessage, null);
}
/**
* Show a toast indicating a successful share.
* @param successMesssage Message to show in the toast.
* @param successMessage Message to show in the toast.
*/
public static void showSuccessToast(Context context, String successMesssage) {
showToast(context, successMesssage, false, null);
public static void showSuccessToast(Context context, String successMessage) {
showToast(context, successMessage, true, null);
}
private static void showToast(Context context, String message, boolean withRetry, View.OnClickListener retryListener) {
private static void showToast(Context context, String message, boolean success, View.OnClickListener retryListener) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.overlay_share_toast, null);
TextView text = (TextView) layout.findViewById(R.id.overlay_toast_message);
text.setText(message);
if (retryListener == null) {
// Hide the retry button.
layout.findViewById(R.id.overlay_toast_separator).setVisibility(View.GONE);
layout.findViewById(R.id.overlay_toast_retry_btn).setVisibility(View.GONE);
} else {
// Set up the button to perform a retry.
Button retryBtn = (Button) layout.findViewById(R.id.overlay_toast_retry_btn);
retryBtn.setOnClickListener(retryListener);
}
if (!success) {
// Hide the happy green tick.
text.setCompoundDrawables(null, null, null, null);
}
Toast toast = new Toast(context);
toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.BOTTOM, 0, 0);
toast.setDuration(Toast.LENGTH_SHORT);
toast.setView(layout);
toast.show();
}
}

View File

@ -0,0 +1,178 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.overlays.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Assert;
import org.mozilla.gecko.R;
import org.mozilla.gecko.overlays.service.sharemethods.ParcelableClientRecord;
import java.util.Collection;
import static org.mozilla.gecko.overlays.ui.SendTabList.*;
public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClientRecord> {
private static final String LOGTAG = "GeckoSendTabAdapter";
private State currentState;
// String to display when in a "button-like" special state. Instead of using a
// ParcelableClientRecord we override the rendering using this string.
private String dummyRecordName;
private final SendTabTargetSelectedListener listener;
private Collection<ParcelableClientRecord> records;
// The AlertDialog to show in the event the record is pressed while in the SHOW_DEVICES state.
// This will show the user a prompt to select a device from a longer list of devices.
private AlertDialog dialog;
public SendTabDeviceListArrayAdapter(Context context, SendTabTargetSelectedListener aListener, int textViewResourceId) {
super(context, textViewResourceId);
listener = aListener;
// We do this manually and avoid multiple notifications when doing compound operations.
setNotifyOnChange(false);
}
/**
* Get an array of the contents of this adapter were it in the LIST state.
* Useful for determining the "real" contents of the adapter.
*/
public ParcelableClientRecord[] toArray() {
return records.toArray(new ParcelableClientRecord[records.size()]);
}
public void setClientRecordList(Collection<ParcelableClientRecord> clientRecordList) {
records = clientRecordList;
updateRecordList();
}
/**
* Ensure the contents of the Adapter are synchronised with the `records` field. This may not
* be the case if records has recently changed, or if we have experienced a state change.
*/
public void updateRecordList() {
if (currentState != State.LIST) {
return;
}
clear();
setNotifyOnChange(false); // So we don't notify for each add.
if (AppConstants.Versions.feature11Plus) {
addAll(records);
} else {
for (ParcelableClientRecord record : records) {
add(record);
}
}
notifyDataSetChanged();
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Context context = getContext();
// Reuse View objects if they exist.
TextView row = (TextView) convertView;
if (row == null) {
row = (TextView) View.inflate(context, R.layout.overlay_share_send_tab_item, null);
}
if (currentState != State.LIST) {
// If we're in a special "Button-like" state, use the override string and a generic icon.
row.setText(dummyRecordName);
row.setCompoundDrawablesWithIntrinsicBounds(R.drawable.overlay_send_tab_icon, 0, 0, 0);
}
// If we're just a button to launch the dialog, set the listener and abort.
if (currentState == State.SHOW_DEVICES) {
row.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
dialog.show();
}
});
return row;
}
// The remaining states delegate to the SentTabTargetSelectedListener.
final String listenerGUID;
ParcelableClientRecord clientRecord = getItem(position);
if (currentState == State.LIST) {
row.setText(clientRecord.name);
row.setCompoundDrawablesWithIntrinsicBounds(getImage(clientRecord), 0, 0, 0);
listenerGUID = clientRecord.guid;
} else {
listenerGUID = null;
}
row.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
listener.onSendTabTargetSelected(listenerGUID);
}
});
return row;
}
private static int getImage(ParcelableClientRecord record) {
if ("mobile".equals(record.type)) {
return R.drawable.sync_mobile;
}
return R.drawable.sync_desktop;
}
public void switchState(State newState) {
if (currentState == newState) {
return;
}
currentState = newState;
switch (newState) {
case LIST:
updateRecordList();
break;
case NONE:
showDummyRecord(getContext().getResources().getString(R.string.overlay_share_send_tab_btn_label));
break;
case SHOW_DEVICES:
showDummyRecord(getContext().getResources().getString(R.string.overlay_share_send_other));
break;
default:
Assert.isTrue(false, "Unexpected state transition: " + newState);
}
}
/**
* Set the dummy override string to the given value and clear the list.
*/
private void showDummyRecord(String name) {
dummyRecordName = name;
clear();
add(null);
notifyDataSetChanged();
}
public void setDialog(AlertDialog aDialog) {
dialog = aDialog;
}
}

View File

@ -0,0 +1,170 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.overlays.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ListAdapter;
import android.widget.ListView;
import org.mozilla.gecko.Assert;
import org.mozilla.gecko.R;
import org.mozilla.gecko.overlays.service.sharemethods.ParcelableClientRecord;
import java.util.Arrays;
import static org.mozilla.gecko.overlays.ui.SendTabList.State.LIST;
import static org.mozilla.gecko.overlays.ui.SendTabList.State.LOADING;
import static org.mozilla.gecko.overlays.ui.SendTabList.State.NONE;
import static org.mozilla.gecko.overlays.ui.SendTabList.State.SHOW_DEVICES;
/**
* The SendTab button has a few different states depending on the available devices (and whether
* we've loaded them yet...)
*
* Initially, the view resembles a disabled button. (the LOADING state)
* Once state is loaded from Sync's database, we know how many devices the user may send their tab
* to.
*
* If there are no targets, the user was found to not have a Sync account, or their Sync account is
* in a state that prevents it from being able to send a tab, we enter the NONE state and display
* a generic button which launches an appropriate activity to fix the situation when tapped (such
* as the set up Sync wizard).
*
* If the number of targets does not MAX_INLINE_SYNC_TARGETS, we present a button for each of them.
* (the LIST state)
*
* Otherwise, we enter the SHOW_DEVICES state, in which we display a "Send to other devices" button
* that takes the user to a menu for selecting a target device from their complete list of many
* devices.
*/
public class SendTabList extends ListView {
private static final String LOGTAG = "SendTabList";
// The maximum number of target devices to show in the main list. Further devices are available
// from a secondary menu.
public static final int MAXIMUM_INLINE_ELEMENTS = 2;
private SendTabDeviceListArrayAdapter clientListAdapter;
// Listener to fire when a share target is selected (either directly or via the prompt)
private SendTabTargetSelectedListener listener;
private State currentState = LOADING;
/**
* Enum defining the states this view may occupy.
*/
public enum State {
// State when no sync targets exist (a generic "Send to Firefox Sync" button which launches
// an activity to set it up)
NONE,
// As NONE, but disabled. Initial state. Used until we get information from Sync about what
// we really want.
LOADING,
// A list of devices to share to.
LIST,
// A single button prompting the user to select a device to share to.
SHOW_DEVICES
}
public SendTabList(Context context) {
super(context);
}
public SendTabList(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setAdapter(ListAdapter adapter) {
Assert.isTrue(adapter instanceof SendTabDeviceListArrayAdapter);
clientListAdapter = (SendTabDeviceListArrayAdapter) adapter;
super.setAdapter(adapter);
}
public void setSendTabTargetSelectedListener(SendTabTargetSelectedListener aListener) {
listener = aListener;
}
public void switchState(State state) {
if (state == currentState) {
return;
}
clientListAdapter.switchState(state);
if (state == SHOW_DEVICES) {
clientListAdapter.setDialog(getDialog());
}
}
public void setSyncClients(ParcelableClientRecord[] clients) {
if (clients == null) {
clients = new ParcelableClientRecord[0];
}
int size = clients.length;
if (size == 0) {
// Just show a button to set up sync (or whatever).
switchState(NONE);
return;
}
clientListAdapter.setClientRecordList(Arrays.asList(clients));
if (size <= MAXIMUM_INLINE_ELEMENTS) {
// Show the list of devices inline.
switchState(LIST);
return;
}
// Just show a button to launch the list of devices to choose one from.
switchState(SHOW_DEVICES);
}
/**
* Get an AlertDialog listing all devices, allowing the user to select the one they want.
* Used when more than MAXIMUM_INLINE_ELEMENTS devices are found (to avoid displaying them all
* inline and looking crazy.
*/
public AlertDialog getDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
final ParcelableClientRecord[] records = clientListAdapter.toArray();
final String[] dialogElements = new String[records.length];
for (int i = 0; i < records.length; i++) {
dialogElements[i] = records[i].name;
}
builder.setTitle(R.string.overlay_share_select_device)
.setItems(dialogElements, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int index) {
listener.onSendTabTargetSelected(records[index].guid);
}
});
return builder.create();
}
/**
* Prevent scrolling of this ListView.
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
return true;
}
return super.dispatchTouchEvent(ev);
}
}

View File

@ -0,0 +1,18 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mozilla.gecko.overlays.ui;
/**
* Interface for classes that wish to listen for the selection of an element from a SendTabList.
*/
public interface SendTabTargetSelectedListener {
/**
* Called when a row in the SendTabList is clicked.
*
* @param targetGUID The GUID of the ClientRecord the element represents (if any, otherwise null)
*/
public void onSendTabTargetSelected(String targetGUID);
}

View File

@ -0,0 +1,284 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.overlays.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
import android.widget.Toast;
import org.mozilla.gecko.Assert;
import org.mozilla.gecko.R;
import org.mozilla.gecko.overlays.OverlayConstants;
import org.mozilla.gecko.overlays.service.OverlayActionService;
import org.mozilla.gecko.overlays.service.sharemethods.ParcelableClientRecord;
import org.mozilla.gecko.overlays.service.sharemethods.SendTab;
import org.mozilla.gecko.overlays.service.sharemethods.ShareMethod;
import org.mozilla.gecko.LocaleAware;
import org.mozilla.gecko.sync.setup.activities.WebURLFinder;
/**
* A transparent activity that displays the share overlay.
*/
public class ShareDialog extends LocaleAware.LocaleAwareActivity implements SendTabTargetSelectedListener {
private static final String LOGTAG = "GeckoShareDialog";
private String url;
private String title;
// The override intent specified by SendTab (if any). See SendTab.java.
private Intent sendTabOverrideIntent;
// Flag set during animation to prevent animation multiple-start.
private boolean isAnimating;
// BroadcastReceiver to receive callbacks from ShareMethods which are changing state.
private final BroadcastReceiver uiEventListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ShareMethod.Type originShareMethod = intent.getParcelableExtra(OverlayConstants.EXTRA_SHARE_METHOD);
switch (originShareMethod) {
case SEND_TAB:
handleSendTabUIEvent(intent);
break;
default:
throw new IllegalArgumentException("UIEvent broadcast from ShareMethod that isn't thought to support such broadcasts.");
}
}
};
/**
* Called when a UI event broadcast is received from the SendTab ShareMethod.
*/
protected void handleSendTabUIEvent(Intent intent) {
sendTabOverrideIntent = intent.getParcelableExtra(SendTab.OVERRIDE_INTENT);
SendTabList sendTabList = (SendTabList) findViewById(R.id.overlay_send_tab_btn);
ParcelableClientRecord[] clientrecords = (ParcelableClientRecord[]) intent.getParcelableArrayExtra(SendTab.EXTRA_CLIENT_RECORDS);
sendTabList.setSyncClients(clientrecords);
}
@Override
protected void onDestroy() {
// Remove the listener when the activity is destroyed: we no longer care.
// Note: The activity can be destroyed without onDestroy being called. However, this occurs
// only when the application is killed, something which also kills the registered receiver
// list, and the service, and everything else: so we don't care.
LocalBroadcastManager.getInstance(this).unregisterReceiver(uiEventListener);
super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setWindowAnimations(0);
Intent intent = getIntent();
// The URL is usually hiding somewhere in the extra text. Extract it.
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
String pageUrl = new WebURLFinder(extraText).bestWebURL();
if (TextUtils.isEmpty(pageUrl)) {
Log.e(LOGTAG, "Unable to process shared intent. No URL found!");
// Display toast notifying the user of failure (most likely a developer who screwed up
// trying to send a share intent).
Toast toast = Toast.makeText(this, getResources().getText(R.string.overlay_share_no_url), Toast.LENGTH_SHORT);
toast.show();
return;
}
setContentView(R.layout.overlay_share_dialog);
LocalBroadcastManager.getInstance(this).registerReceiver(uiEventListener,
new IntentFilter(OverlayConstants.SHARE_METHOD_UI_EVENT));
// Have the service start any initialisation work that's necessary for us to show the correct
// UI. The results of such work will come in via the BroadcastListener.
Intent serviceStartupIntent = new Intent(this, OverlayActionService.class);
serviceStartupIntent.setAction(OverlayConstants.ACTION_PREPARE_SHARE);
startService(serviceStartupIntent);
// If provided, we use the subject text to give us something nice to display.
// If not, we wing it with the URL.
// TODO: Consider polling Fennec databases to find better information to display.
String subjectText = intent.getStringExtra(Intent.EXTRA_SUBJECT);
if (subjectText != null) {
((TextView) findViewById(R.id.title)).setText(subjectText);
}
title = subjectText;
url = pageUrl;
// Set the subtitle text on the view and cause it to marquee if it's too long (which it will
// be, since it's a URL).
TextView subtitleView = (TextView) findViewById(R.id.subtitle);
subtitleView.setText(pageUrl);
subtitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
subtitleView.setSingleLine(true);
subtitleView.setMarqueeRepeatLimit(5);
subtitleView.setSelected(true);
// Start the slide-up animation.
Animation anim = AnimationUtils.loadAnimation(this, R.anim.overlay_slide_up);
findViewById(R.id.sharedialog).startAnimation(anim);
// Add button event listeners.
findViewById(R.id.overlay_share_bookmark_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
addBookmark();
}
});
findViewById(R.id.overlay_share_reading_list_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
addToReadingList();
}
});
// Send tab.
SendTabList sendTabList = (SendTabList) findViewById(R.id.overlay_send_tab_btn);
// Register ourselves as both the listener and the context for the Adapter.
SendTabDeviceListArrayAdapter adapter = new SendTabDeviceListArrayAdapter(this, this, R.layout.sync_list_item);
sendTabList.setAdapter(adapter);
sendTabList.setSendTabTargetSelectedListener(this);
}
/**
* Helper method to get an overlay service intent populated with the data held in this dialog.
*/
private Intent getServiceIntent(ShareMethod.Type method) {
final Intent serviceIntent = new Intent(this, OverlayActionService.class);
serviceIntent.setAction(OverlayConstants.ACTION_SHARE);
serviceIntent.putExtra(OverlayConstants.EXTRA_SHARE_METHOD, (Parcelable) method);
serviceIntent.putExtra(OverlayConstants.EXTRA_URL, url);
serviceIntent.putExtra(OverlayConstants.EXTRA_TITLE, title);
return serviceIntent;
}
@Override
public void finish() {
super.finish();
// Don't perform an activity-dismiss animation.
overridePendingTransition(0, 0);
}
/*
* Button handlers. Send intents to the background service responsible for processing requests
* on Fennec in the background. (a nice extensible mechanism for "doing stuff without properly
* launching Fennec").
*/
public void sendTab(String targetGUID) {
// If an override intent has been set, dispatch it.
if (sendTabOverrideIntent != null) {
startActivity(sendTabOverrideIntent);
finish();
return;
}
// targetGUID being null with no override intent should be an impossible state.
Assert.isTrue(targetGUID != null);
Intent serviceIntent = getServiceIntent(ShareMethod.Type.SEND_TAB);
// Currently, only one extra parameter is necessary (the GUID of the target device).
Bundle extraParameters = new Bundle();
// Future: Handle multiple-selection. Bug 1061297.
extraParameters.putStringArray(SendTab.SEND_TAB_TARGET_DEVICES, new String[] { targetGUID });
serviceIntent.putExtra(OverlayConstants.EXTRA_PARAMETERS, extraParameters);
startService(serviceIntent);
slideOut();
}
@Override
public void onSendTabTargetSelected(String targetGUID) {
sendTab(targetGUID);
}
public void addToReadingList() {
startService(getServiceIntent(ShareMethod.Type.ADD_TO_READING_LIST));
slideOut();
}
public void addBookmark() {
startService(getServiceIntent(ShareMethod.Type.ADD_BOOKMARK));
slideOut();
}
/**
* Slide the overlay down off the screen and destroy it.
*/
private void slideOut() {
if (isAnimating) {
return;
}
isAnimating = true;
Animation anim = AnimationUtils.loadAnimation(this, R.anim.overlay_slide_down);
findViewById(R.id.sharedialog).startAnimation(anim);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// Unused. I can haz Miranda method?
}
@Override
public void onAnimationEnd(Animation animation) {
finish();
}
@Override
public void onAnimationRepeat(Animation animation) {
// Unused.
}
});
}
/**
* Close the dialog if back is pressed.
*/
@Override
public void onBackPressed() {
slideOut();
}
/**
* Close the dialog if the anything that isn't a button is tapped.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
slideOut();
return true;
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime"
android:fromYDelta="0"
android:toYDelta="100%p" />

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime"
android:fromYDelta="100%p"
android:toYDelta="0" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- Serves to position the content on the screen (bottom, centered) and provide the drop-shadow -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sharedialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="-12dp"
android:paddingTop="30dp"
android:layout_gravity="bottom|center"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/share_overlay_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:orientation="vertical"
android:background="@drawable/share_overlay_background">
<!-- Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:background="@color/background_light"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="15dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:layout_gravity="center">
<!-- Title -->
<TextView
android:id="@id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="7dp"
android:ellipsize="end"
android:maxLines="2"
android:scrollHorizontally="true"
android:textColor="@color/text_color_primary"
android:textSize="17sp"/>
<!-- Subtitle (url) -->
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color_secondary"/>
</LinearLayout>
<!-- Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/overlay_share_background_colour"
android:orientation="vertical">
<!-- TODO: Once API 11 is available, stick "showDividers=middle" into the parent and get rid
of these evil separator views. -->
<!-- "Send to Firefox Sync" -->
<org.mozilla.gecko.overlays.ui.SendTabList
style="@style/ShareOverlayButton"
android:id="@+id/overlay_send_tab_btn"
android:background="@color/overlay_share_background_colour"
android:padding="0dp"/>
<!-- Evil separator -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/background_light"/>
<!-- "Add to reading list" -->
<TextView
style="@style/ShareOverlayButton.Text"
android:id="@+id/overlay_share_reading_list_btn"
android:text="@string/overlay_share_reading_list_btn_label"
android:drawableLeft="@drawable/overlay_readinglist_icon"/>
<!-- Evil separator -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/background_light"/>
<!-- "Add bookmark" -->
<TextView
style="@style/ShareOverlayButton.Text"
android:id="@+id/overlay_share_bookmark_btn"
android:text="@string/overlay_share_bookmark_btn_label"
android:drawableLeft="@drawable/overlay_bookmark_icon"/>
</LinearLayout>
</LinearLayout>
<!-- Firefox logo (has to appear higher in the z-order than the content. -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/share_overlay_content"
android:scaleType="center"
android:layout_centerHorizontal="true"
android:src="@drawable/icon"
android:layout_marginBottom="-6dp"/>
</RelativeLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ListView
style="@style/ShareOverlayButton"
android:id="@+id/device_list"
android:padding="0dp" >
</ListView>
</LinearLayout>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/ShareOverlayButton.Text"/>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/overlay_share_toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/share_overlay_background"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_gravity="bottom|center">
<!-- Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:background="@color/background_light"
android:orientation="horizontal"
android:paddingLeft="5dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<!-- Large attractive green tick with label to the right -->
<TextView
style="@style/ShareOverlayButton.Text"
android:id="@+id/overlay_toast_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/text_color_primary"
android:textSize="14sp"
android:drawableLeft="@drawable/overlay_check"/>
<!-- Evil separator -->
<View
android:id="@+id/overlay_toast_separator"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:layout_height="match_parent"
android:layout_width="1dp"
android:background="@color/background_light"/>
<!-- Retry button -->
<Button
android:id="@+id/overlay_toast_retry_btn"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/overlay_share_retry"
android:onClick="selfDestruct" />
</LinearLayout>
</LinearLayout>

View File

@ -48,6 +48,12 @@
<color name="text_color_secondary_inverse">#DDDDDD</color>
<color name="text_color_tertiary_inverse">#A4A7A9</color>
<!-- Colour used for share overlay button labels -->
<color name="text_color_overlaybtn">#666666</color>
<!-- Colour used for share overlay button background -->
<color name="overlay_share_background_colour">#FFD0CECB</color>
<!-- Disabled colors -->
<color name="text_color_primary_disable_only">#999999</color>

View File

@ -763,6 +763,29 @@
<item name="android:gravity">right</item>
</style>
<style name="ShareOverlayButton">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:minHeight">60dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:paddingLeft">15dp</item>
<item name="android:paddingRight">15dp</item>
<item name="android:paddingTop">17dp</item>
<item name="android:paddingBottom">17dp</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:clickable">true</item>
<item name="android:background">@android:drawable/list_selector_background</item>
<item name="android:layout_margin">0dp</item>
</style>
<style name="ShareOverlayButton.Text">
<item name="android:drawablePadding">15dp</item>
<item name="android:maxLines">1</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/text_color_overlaybtn</item>
<item name="android:ellipsize">marquee</item>
</style>
<style name="TabInput"></style>
<style name="TabInput.TabWidget">
@ -780,6 +803,13 @@
<item name="android:paddingTop">0dp</item>
</style>
<!-- Make the share overlay activity appear like an overlay. -->
<style name="ShareOverlayActivity">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
<style name="OnboardStartLayout">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>

View File

@ -110,6 +110,15 @@
<string name="media_pause">&media_pause;</string>
<string name="media_stop">&media_stop;</string>
<string name="overlay_share_send_other">&overlay_share_send_other;</string>
<string name="overlay_share_header">&overlay_share_header;</string>
<string name="overlay_share_bookmark_btn_label">&overlay_share_bookmark_btn_label;</string>
<string name="overlay_share_reading_list_btn_label">&overlay_share_reading_list_btn_label;</string>
<string name="overlay_share_send_tab_btn_label">&overlay_share_send_tab_btn_label;</string>
<string name="overlay_share_no_url">&overlay_share_no_url;</string>
<string name="overlay_share_retry">&overlay_share_retry;</string>
<string name="overlay_share_select_device">&overlay_share_select_device;</string>
<string name="settings">&settings;</string>
<string name="settings_title">&settings_title;</string>
<string name="pref_category_advanced">&pref_category_advanced;</string>