Starting to Materialize the Share dialog.

TODO(rosswang): hella animations. Also, I have not tested discovery yet.

Change-Id: I2551a483a272dc263db03765e825eb82e8697add
diff --git a/app/build.gradle b/app/build.gradle
index 0393b7f..119d275 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -75,7 +75,7 @@
             'com.android.support:appcompat-v7:23.1.1',
             'com.android.support:design:23.1.1',
             'com.android.support:cardview-v7:23.1.1',
-            'com.android.support:recyclerview-v7:23.1.1',
+            'com.android.support:recyclerview-v7:23.2.1',
             'com.google.guava:guava:19.0'
     )
     firebaseCompile 'com.firebase:firebase-client-android:2.5.2'
diff --git a/app/src/main/res/layout/task_row.xml b/app/src/main/res/layout/task_row.xml
index 21415bd..cca2ed9 100644
--- a/app/src/main/res/layout/task_row.xml
+++ b/app/src/main/res/layout/task_row.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
-             android:layout_height="match_parent">
+             android:layout_height="wrap_content">
 
     <LinearLayout
         android:id="@+id/swipe_right"
diff --git a/app/src/main/res/layout/todo_list_row.xml b/app/src/main/res/layout/todo_list_row.xml
index 4dd003b..756dc05 100644
--- a/app/src/main/res/layout/todo_list_row.xml
+++ b/app/src/main/res/layout/todo_list_row.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="wrap_content">
     <LinearLayout
         android:id="@+id/swipe_right"
         android:layout_width="match_parent"
diff --git a/app/src/syncbase/java/io/v/todos/sharing/ContactAdapter.java b/app/src/syncbase/java/io/v/todos/sharing/ContactAdapter.java
new file mode 100644
index 0000000..2bab5c9
--- /dev/null
+++ b/app/src/syncbase/java/io/v/todos/sharing/ContactAdapter.java
@@ -0,0 +1,311 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.sharing;
+
+import android.support.annotation.StringRes;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import io.v.todos.R;
+
+public class ContactAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+    public static final int
+            VIEW_TYPE_SUBHEADER = 0,
+            VIEW_TYPE_CONTACT = 1;
+
+    public interface ContactTouchListener {
+        void onContactTouch(RecyclerView.ViewHolder viewHolder);
+    }
+
+    private static class SubheaderViewHolder extends RecyclerView.ViewHolder {
+        public final TextView category;
+
+        public SubheaderViewHolder(ViewGroup parent) {
+            super(LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.sharing_subheader, parent, false));
+            category = (TextView) itemView.findViewById(R.id.category);
+        }
+    }
+
+    private static class ContactViewHolder extends RecyclerView.ViewHolder {
+        public final TextView name;
+
+        public ContactViewHolder(ViewGroup parent) {
+            super(LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.sharing_entry, parent, false));
+            name = (TextView) itemView.findViewById(R.id.name);
+        }
+    }
+
+    // TODO(rosswang): Save expanded state, and/or manage more intelligently.
+    // TODO(rosswang): Potentially show condensed avatars in collapsed state.
+    private static class Sublist {
+        @StringRes
+        public final int header;
+        public final List<String> items = new ArrayList<>();
+
+        public Sublist(@StringRes int header) {
+            this.header = header;
+        }
+
+        public int getEffectiveSize() {
+            return 1 + items.size();
+        }
+    }
+
+    private final Sublist
+            mSharingAlready = new Sublist(R.string.sharing_already),
+            mSharingPossible = new Sublist(R.string.sharing_possible);
+
+    private final Sublist[] mSections = new Sublist[]{
+            mSharingAlready,
+            mSharingPossible
+    };
+
+    private Collection<String> mSharedAlready;
+    private final Set<String> mSharesAdded, mSharesRemoved, mSharesRecent;
+    private final Multiset<String> mDiscoCounter = HashMultiset.create();
+    private ContactTouchListener mContactTouchListener;
+
+    public ContactAdapter(Collection<String> sharedAlready, Set<String> sharesAdded,
+                          Set<String> sharesRemoved, Set<String> sharesRecent) {
+        mSharesAdded = sharesAdded;
+        mSharesRemoved = sharesRemoved;
+        mSharesRecent = sharesRecent;
+
+        Set<String> recentUnaccountedFor = new HashSet<>(sharesRecent);
+        recentUnaccountedFor.removeAll(sharedAlready);
+        recentUnaccountedFor.removeAll(sharesAdded);
+        recentUnaccountedFor.removeAll(sharesRemoved);
+        mSharingPossible.items.addAll(recentUnaccountedFor);
+        mSharingPossible.items.addAll(sharesRemoved);
+        Collections.sort(mSharingPossible.items);
+
+        setSharedAlreadyData(sharedAlready);
+    }
+
+    private void setSharedAlreadyData(Collection<String> sharedAlready) {
+        mSharedAlready = sharedAlready;
+        mSharingAlready.items.clear();
+        mSharingAlready.items.addAll(sharedAlready);
+        mSharingAlready.items.removeAll(mSharesRemoved);
+        mSharingAlready.items.addAll(mSharesAdded);
+        Collections.sort(mSharingAlready.items);
+    }
+
+    public void filterDeltas() {
+        mSharesAdded.removeAll(mSharedAlready);
+        mSharesRemoved.retainAll(mSharedAlready);
+    }
+
+    private static class SublistEntry {
+        public final Sublist sublist;
+        public final int itemPosition;
+        public final String item;
+
+        public SublistEntry(Sublist sublist, int itemPosition, String item) {
+            this.sublist = sublist;
+            this.itemPosition = itemPosition;
+            this.item = item;
+        }
+    }
+
+    private SublistEntry getSublistItemPosition(int position) {
+        for (Sublist section : mSections) {
+            if (position == 0) {
+                return new SublistEntry(section, -1, null);
+            }
+            position--;
+            if (position < section.items.size()) {
+                return new SublistEntry(section, position, section.items.get(position));
+            }
+            position -= section.items.size();
+        }
+        throw new IndexOutOfBoundsException("No sublist at position " + position);
+    }
+
+    /**
+     * Inverse of {@link #getSublistItemPosition(int)}.
+     */
+    private int getViewPosition(Sublist section, int itemPosition) {
+        int offset = 1;
+        for (Sublist cursor: mSections) {
+            if (cursor == section) {
+                return offset + itemPosition;
+            } else {
+                offset += cursor.getEffectiveSize();
+            }
+        }
+        throw new NoSuchElementException("Section is not in list");
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        if (viewType == VIEW_TYPE_SUBHEADER) {
+            return new SubheaderViewHolder(parent);
+        } else {
+            final ContactViewHolder cvh = new ContactViewHolder(parent);
+            cvh.itemView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mContactTouchListener.onContactTouch(cvh);
+                }
+            });
+            return cvh;
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
+        final SublistEntry entry = getSublistItemPosition(position);
+        if (entry.item == null) {
+            final SubheaderViewHolder svh = (SubheaderViewHolder) holder;
+            svh.category.setText(entry.sublist.header);
+        } else {
+            final ContactViewHolder cvh = (ContactViewHolder) holder;
+            cvh.name.setText(entry.item);
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        int count = 0;
+        for (Sublist section : mSections) {
+            count += section.getEffectiveSize();
+        }
+        return count;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return getSublistItemPosition(position).item == null ?
+                VIEW_TYPE_SUBHEADER : VIEW_TYPE_CONTACT;
+    }
+
+    public void setContactTouchListener(ContactTouchListener listener) {
+        mContactTouchListener = listener;
+    }
+
+    public void onNearbyDeviceLost(String email) {
+        // short-circuit note; remove must happen
+        if (mDiscoCounter.remove(email, 1) > 1 ||
+                Collections.binarySearch(mSharingAlready.items, email) >= 0) {
+            return;
+        }
+        int i = Collections.binarySearch(mSharingPossible.items, email);
+        if (i >= 0) {
+            mSharingPossible.items.remove(i);
+            notifyItemRemoved(getViewPosition(mSharingPossible, i));
+        }
+    }
+
+    public void onNearbyDeviceDiscovered(String email) {
+        // short-circuit note; add must happen
+        if (mDiscoCounter.add(email, 1) > 0 ||
+                Collections.binarySearch(mSharingAlready.items, email) >= 0) {
+            return;
+        }
+        int i = Collections.binarySearch(mSharingPossible.items, email);
+        if (i < 0) {
+            i = ~i;
+            mSharingPossible.items.add(i, email);
+            notifyItemInserted(getViewPosition(mSharingPossible, i));
+        }
+    }
+
+    private int shareWithPossible(int i) {
+        String email = mSharingPossible.items.remove(i);
+        // Animate movement by moving the item.
+        int j = ~Collections.binarySearch(mSharingAlready.items, email);
+        int oldPosition = getViewPosition(mSharingPossible, i);
+        int newPosition = getViewPosition(mSharingAlready, j);
+        mSharingAlready.items.add(j, email);
+        notifyItemMoved(oldPosition, newPosition);
+        registerShare(email);
+        return newPosition;
+    }
+
+    private void unshare(int i) {
+        String email = mSharingAlready.items.remove(i);
+        int j = ~Collections.binarySearch(mSharingPossible.items, email);
+        mSharingPossible.items.add(j, email);
+        registerUnshare(email);
+
+        // animate movement
+        int oldPosition = getViewPosition(mSharingAlready, i);
+        int newPosition = getViewPosition(mSharingPossible, j);
+        notifyItemMoved(oldPosition, newPosition);
+    }
+
+    private int insertShare(String email) {
+        int i = Collections.binarySearch(mSharingAlready.items, email);
+        if (i >= 0) {
+            return getViewPosition(mSharingAlready, i);
+        } else {
+            i = ~i;
+            mSharingAlready.items.add(i, email);
+            i = getViewPosition(mSharingAlready, i);
+            notifyItemInserted(i);
+            registerShare(email);
+            return i;
+        }
+    }
+
+    private void registerShare(String email) {
+        mSharesRecent.add(email);
+        if (!mSharesRemoved.remove(email)) {
+            mSharesAdded.add(email);
+        }
+    }
+
+    private void registerUnshare(String email) {
+        if (!mSharesAdded.remove(email)) {
+            mSharesRemoved.add(email);
+        }
+    }
+
+    /**
+     * @return the position of the e-mail in the adapter
+     */
+    public int onCustomShare(String email) {
+        int i = Collections.binarySearch(mSharingPossible.items, email);
+        if (i >= 0) {
+            return shareWithPossible(i);
+        } else {
+            return insertShare(email);
+        }
+    }
+
+    public void setSharedTo(Collection<String> sharedAlready) {
+        setSharedAlreadyData(sharedAlready);
+        // TODO(rosswang): list differ
+
+        notifyDataSetChanged();
+    }
+
+    // TODO(rosswang): this is a hacky abstraction
+    public void toggleContact(int position) {
+        SublistEntry entry = getSublistItemPosition(position);
+        if (entry.sublist == mSharingAlready) {
+            unshare(entry.itemPosition);
+        } else {
+            shareWithPossible(entry.itemPosition);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/syncbase/java/io/v/todos/sharing/ShareListDialogFragment.java b/app/src/syncbase/java/io/v/todos/sharing/ShareListDialogFragment.java
index e380e05..cd654a4 100644
--- a/app/src/syncbase/java/io/v/todos/sharing/ShareListDialogFragment.java
+++ b/app/src/syncbase/java/io/v/todos/sharing/ShareListDialogFragment.java
@@ -8,13 +8,12 @@
 import android.app.DialogFragment;
 import android.app.FragmentManager;
 import android.content.DialogInterface;
-import android.graphics.Paint;
 import android.os.Bundle;
 import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.DefaultItemAnimator;
 import android.support.v7.widget.RecyclerView;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -25,11 +24,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import javax.annotation.Nullable;
@@ -48,24 +43,23 @@
  * allow entry of any value. Confirming this dialog sends the set of added and removed emails. Tap
  * to add/remove.
  */
-public class ShareListDialogFragment extends DialogFragment {
+public class ShareListDialogFragment extends DialogFragment
+        implements ContactAdapter.ContactTouchListener {
     public static final String FRAGMENT_TAG = ShareListDialogFragment.class.getSimpleName();
 
     public static ShareListDialogFragment find(FragmentManager fragmentManager) {
         return (ShareListDialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
     }
 
-    private Set<String> mRemoved;
-    private List<String> mNearby = new ArrayList<>();
-    private ArrayList<String> mTyped;
-    private Set<String> mAdded;
+    private RecyclerView mContacts;
+    private Set<String> mRemoved, mAdded, mRecent;
 
     private static final String
             REMOVED_KEY = "removedShares",
-            TYPED_KEY = "explicitShares",
-            ADDED_KEY = "addedShares";
+            ADDED_KEY = "addedShares",
+            RECENT_KEY = "recentShares";
 
-    private ContactAdapter mAlreadyAdapter, mPossibleAdapter;
+    private ContactAdapter mAdapter;
 
     private VContext mScanContext;
 
@@ -84,8 +78,8 @@
         super.onSaveInstanceState(outState);
 
         outState.putStringArrayList(REMOVED_KEY, new ArrayList<>(mRemoved));
-        outState.putStringArrayList(TYPED_KEY, mTyped);
         outState.putStringArrayList(ADDED_KEY, new ArrayList<>(mAdded));
+        outState.putStringArrayList(RECENT_KEY, new ArrayList<>(mRecent));
     }
 
     @Override
@@ -95,20 +89,30 @@
         if (savedInstanceState == null) {
             mRemoved = new HashSet<>();
             mAdded = new HashSet<>();
-            mTyped = new ArrayList<>();
+            mRecent = new HashSet<>();
         } else {
             mRemoved = new HashSet<>(savedInstanceState.getStringArrayList(REMOVED_KEY));
             mAdded = new HashSet<>(savedInstanceState.getStringArrayList(ADDED_KEY));
-            mTyped = savedInstanceState.getStringArrayList(TYPED_KEY);
+            mRecent = new HashSet<>(savedInstanceState.getStringArrayList(RECENT_KEY));
         }
 
-        mAlreadyAdapter = new ContactAdapter(getParent().getSharedTo(), mRemoved, true);
-        RecyclerView rvAlready = (RecyclerView) view.findViewById(R.id.recycler_already);
-        rvAlready.setAdapter(mAlreadyAdapter);
-        mNearby.clear();
-        final RecyclerView rvPossible = (RecyclerView) view.findViewById(R.id.recycler_possible);
-        mPossibleAdapter = new ContactAdapter(mNearby, mTyped, mAdded, false);
-        rvPossible.setAdapter(mPossibleAdapter);
+        mAdapter = new ContactAdapter(getParent().getSharedTo(), mAdded, mRemoved, mRecent);
+        mAdapter.setContactTouchListener(this);
+        mContacts = (RecyclerView) view.findViewById(R.id.recycler);
+        mContacts.setAdapter(mAdapter);
+        mContacts.setItemAnimator(new DefaultItemAnimator() {
+            @Override
+            public boolean animateAdd(RecyclerView.ViewHolder holder) {
+                dispatchAddFinished(holder);
+                return false;
+            }
+
+            @Override
+            public boolean animateRemove(RecyclerView.ViewHolder holder) {
+                dispatchRemoveFinished(holder);
+                return false;
+            }
+        });
 
         mScanContext = SyncbasePersistence.getAppVContext().withCancel();
         try {
@@ -116,39 +120,17 @@
                     Sharing.getDiscovery().scan(mScanContext,
                             "v.InterfaceName = \"" + Sharing.getPresenceInterface() + "\""),
                     new InputChannelCallback<Update>() {
-                        private final Map<String, Integer> counterMap = new HashMap<>();
-
                         @Override
                         public ListenableFuture<Void> onNext(Update result) {
                             final String email = Iterables.getOnlyElement(result.getAddresses());
-                            if (email == null) {
+                            if (email == null ||
+                                    email.equals(SyncbasePersistence.getPersonalEmail())) {
                                 return null;
                             }
-                            // Note: binarySearch returns -|correct insert index| - 1 if it fails
-                            // to find a match. For Java ints, this is the bitwise complement of the
-                            // "correct" insertion index.
-                            int searchIndex = Collections.binarySearch(mNearby, email);
                             if (result.isLost()) {
-                                Integer old = counterMap.get(email);
-                                counterMap.put(email, old == null ? 0 : Math.max(0, counterMap
-                                        .get(email) - 1));
-                                // Remove the email if the counter indicates that we should.
-                                if (counterMap.get(email) == 0 && searchIndex >= 0) {
-                                    mNearby.remove(searchIndex);
-                                    mPossibleAdapter.notifyItemRemoved(searchIndex);
-                                }
+                                mAdapter.onNearbyDeviceLost(email);
                             } else {
-                                Integer old = counterMap.get(email);
-                                counterMap.put(email, old == null ? 1 : counterMap.get(email) + 1);
-                                // Show the email if it's a new one and not equal to our email.
-                                // TODO(alexfandrianto): This still lets you see emails of those
-                                // nearby who you've already invited.
-                                if (searchIndex < 0 && !email.equals(getParent().getEmail())) {
-                                    int insertIndex = ~searchIndex;
-                                    mNearby.add(insertIndex, email);
-                                    //mNearby.add(email);
-                                    mPossibleAdapter.notifyItemInserted(insertIndex);
-                                }
+                                mAdapter.onNearbyDeviceDiscovered(email);
                             }
                             return null;
                         }
@@ -174,11 +156,7 @@
                 boolean handled = false;
                 if (actionId == EditorInfo.IME_ACTION_SEND) {
                     String email = editText.getText().toString();
-                    if (!mTyped.contains(email)) {
-                        mTyped.add(email);
-                    }
-                    mAdded.add(email);
-                    rvPossible.getAdapter().notifyDataSetChanged();
+                    mAdapter.onCustomShare(email);
                     editText.setText("");
                     handled = true;
                 }
@@ -190,6 +168,7 @@
                 .setView(view)
                 .setPositiveButton("Save", new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int whichButton) {
+                        mAdapter.filterDeltas();
                         getParent().persistence.shareTodoList(mAdded);
                         // TODO(alexfandrianto/rosswang): removal
                     }
@@ -207,90 +186,16 @@
     }
 
     public void onSharedToChanged() {
-        //TODO(rosswang)
-    }
-
-    private static class ContactAdapter extends RecyclerView.Adapter<ContactViewHolder> {
-        private final List<String> backup;
-        private final List<String> bonus;
-        private final Set<String> toggledOn;
-        private final boolean strikethrough; // If false, then bold.
-
-        public ContactAdapter(List<String> backup, Set<String> toggledOn, boolean strikethrough) {
-            super();
-            this.backup = backup;
-            this.bonus = null;
-            this.toggledOn = toggledOn;
-            this.strikethrough = strikethrough;
-        }
-
-        public ContactAdapter(List<String> backup, List<String> bonus, Set<String> toggledOn,
-                              boolean strikethrough) {
-            super();
-            this.backup = backup;
-            this.bonus = bonus;
-            this.toggledOn = toggledOn;
-            this.strikethrough = strikethrough;
-        }
-
-        @Override
-        public ContactViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-            return new ContactViewHolder(new TextView(parent.getContext()));
-        }
-
-        @Override
-        public void onBindViewHolder(final ContactViewHolder holder, int position) {
-            final String name = position < backup.size() ? backup.get(position) :
-                    bonus.get(position - backup.size());
-            final boolean present = toggledOn.contains(name);
-            holder.bindString(name, present, strikethrough, new View.OnClickListener() {
-
-                @Override
-                public void onClick(View view) {
-                    if (present) {
-                        toggledOn.remove(name);
-                    } else {
-                        toggledOn.add(name);
-                    }
-                    notifyItemChanged(holder.getAdapterPosition());
-                }
-            });
-        }
-
-        @Override
-        public int getItemCount() {
-            int extra = bonus == null ? 0 : bonus.size();
-            return backup.size() + extra;
-        }
-    }
-
-    private static class ContactViewHolder extends RecyclerView.ViewHolder {
-        public ContactViewHolder(View itemView) {
-            super(itemView);
-        }
-
-        public void bindString(String name, boolean isActive, boolean strikethrough, View
-                .OnClickListener listener) {
-            TextView text = (TextView) itemView;
-
-            text.setText(name);
-            text.setTextSize(18);
-            if (strikethrough) {
-                if (isActive) {
-                    text.setPaintFlags(text.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
-                } else {
-                    text.setPaintFlags(text.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
-                }
-            } else {
-                // We should bold!
-                if (isActive) {
-                    text.setTypeface(null, 1); // 1 is bold
-                } else {
-                    text.setTypeface(null, 0); // 0 is default text style
-                }
+        mContacts.post(new Runnable() {
+            @Override
+            public void run() {
+                mAdapter.setSharedTo(getParent().getSharedTo());
             }
+        });
+    }
 
-            text.setOnClickListener(listener);
-        }
+    @Override
+    public void onContactTouch(RecyclerView.ViewHolder viewHolder) {
+        mAdapter.toggleContact(viewHolder.getAdapterPosition());
     }
 }
diff --git a/app/src/syncbase/res/drawable-hdpi/ic_expand_less_black_24dp.png b/app/src/syncbase/res/drawable-hdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 0000000..57139a7
--- /dev/null
+++ b/app/src/syncbase/res/drawable-hdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-hdpi/ic_expand_more_black_24dp.png b/app/src/syncbase/res/drawable-hdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 0000000..9625f14
--- /dev/null
+++ b/app/src/syncbase/res/drawable-hdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-mdpi/ic_expand_less_black_24dp.png b/app/src/syncbase/res/drawable-mdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 0000000..08c16a3
--- /dev/null
+++ b/app/src/syncbase/res/drawable-mdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-mdpi/ic_expand_more_black_24dp.png b/app/src/syncbase/res/drawable-mdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 0000000..feb85a7
--- /dev/null
+++ b/app/src/syncbase/res/drawable-mdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-xhdpi/ic_expand_less_black_24dp.png b/app/src/syncbase/res/drawable-xhdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 0000000..323360e
--- /dev/null
+++ b/app/src/syncbase/res/drawable-xhdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-xhdpi/ic_expand_more_black_24dp.png b/app/src/syncbase/res/drawable-xhdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 0000000..d3ee65e
--- /dev/null
+++ b/app/src/syncbase/res/drawable-xhdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-xxhdpi/ic_expand_less_black_24dp.png b/app/src/syncbase/res/drawable-xxhdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 0000000..ee92f4e
--- /dev/null
+++ b/app/src/syncbase/res/drawable-xxhdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-xxhdpi/ic_expand_more_black_24dp.png b/app/src/syncbase/res/drawable-xxhdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 0000000..5cd142c
--- /dev/null
+++ b/app/src/syncbase/res/drawable-xxhdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png b/app/src/syncbase/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 0000000..99c6e3e
--- /dev/null
+++ b/app/src/syncbase/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png b/app/src/syncbase/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 0000000..ad852e3
--- /dev/null
+++ b/app/src/syncbase/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/app/src/syncbase/res/layout/sharing.xml b/app/src/syncbase/res/layout/sharing.xml
index 584e60e..af1fffe 100644
--- a/app/src/syncbase/res/layout/sharing.xml
+++ b/app/src/syncbase/res/layout/sharing.xml
@@ -1,51 +1,35 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:orientation="vertical" android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+              xmlns:app="http://schemas.android.com/apk/res-auto"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical">
 
-    <TextView
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:textStyle="bold"
-        android:textSize="22sp"
-        android:textColor="#000000"
-        android:layout_margin="5dp"
-        android:text="@string/sharing_already" />
-
-    <android.support.v7.widget.RecyclerView android:id="@+id/recycler_already"
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/recycler"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        app:layoutManager="LinearLayoutManager" />
-
-    <TextView
-        android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:textStyle="bold"
-        android:textSize="22sp"
-        android:textColor="#000000"
-        android:layout_margin="5dp"
-        android:text="@string/sharing_possible" />
+        android:paddingTop="8dp"
+        app:layoutManager="LinearLayoutManager"/>
 
-    <android.support.v7.widget.RecyclerView android:id="@+id/recycler_possible"
+    <android.support.design.widget.TextInputLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        app:layoutManager="LinearLayoutManager" />
-
-    <EditText android:id="@+id/custom_email"
-        android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:textStyle="bold"
-        android:textSize="22sp"
-        android:textColor="#000000"
-        android:hint="@string/sharing_custom_hint"
-        android:layout_margin="5dp"
-        android:inputType="text"
-        android:imeOptions="actionSend" />
+        android:paddingEnd="20dp"
+        android:paddingStart="20dp"
+        android:paddingTop="8dp">
+
+        <EditText
+            android:id="@+id/custom_email"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom"
+            android:hint="@string/sharing_custom_hint"
+            android:imeOptions="actionSend"
+            android:inputType="textEmailAddress"
+            android:textColor="#000000"
+            android:textSize="22sp"
+            android:textStyle="bold"/>
+    </android.support.design.widget.TextInputLayout>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/app/src/syncbase/res/layout/sharing_entry.xml b/app/src/syncbase/res/layout/sharing_entry.xml
new file mode 100644
index 0000000..a2eb525
--- /dev/null
+++ b/app/src/syncbase/res/layout/sharing_entry.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="48dp"
+             android:paddingEnd="24dp"
+             android:paddingStart="24dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:id="@+id/name"
+        android:layout_gravity="center_vertical"
+        android:textSize="16sp"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/syncbase/res/layout/sharing_subheader.xml b/app/src/syncbase/res/layout/sharing_subheader.xml
new file mode 100644
index 0000000..50c81c0
--- /dev/null
+++ b/app/src/syncbase/res/layout/sharing_subheader.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="48dp"
+              android:orientation="horizontal"
+              android:paddingEnd="24dp"
+              android:paddingStart="24dp">
+
+    <TextView
+        android:id="@+id/category"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="14sp"
+        android:textStyle="bold"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/syncbase/res/values/strings.xml b/app/src/syncbase/res/values/strings.xml
index bc20f9d..b0e644b 100644
--- a/app/src/syncbase/res/values/strings.xml
+++ b/app/src/syncbase/res/values/strings.xml
@@ -2,9 +2,9 @@
     <string name="app_name">Syncbase Todos</string>
     <string name="share_location">Share Presence</string>
     <!-- For Sharing Menu -->
-    <string name="sharing_already">Sharing With</string>
-    <string name="sharing_possible">Nearby</string>
-    <string name="sharing_custom_hint">Add an email address...</string>
+    <string name="sharing_already">Shared With</string>
+    <string name="sharing_possible">Available</string>
+    <string name="sharing_custom_hint">Add an email address&#8230;</string>
     <!-- Errors -->
     <string name="err_share_location">Could not share location</string>
     <string name="err_scan_nearby">Unable to scan for nearby users</string>