requests: factor code

Change-Id: Ib35c5ab65dc175d692edb2fede840df1d249c95d
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
index e61f416..a088dd5 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
@@ -24,6 +24,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -82,7 +83,7 @@
                         vms.add((SmartListViewModel) ob);
                     return vms;
                 }))
-                //.throttleLatest(150, TimeUnit.MILLISECONDS, mUiScheduler)
+                .throttleLatest(150, TimeUnit.MILLISECONDS, mUiScheduler)
                 .observeOn(mUiScheduler)
                 .subscribe(viewModels -> {
                     final MainView view = getView();
@@ -90,22 +91,20 @@
                     view.showContacts(viewModels);
                 }, e -> Log.w(TAG, "showConversations error ", e)));
 
-        Log.d(TAG, "getPendingSubject subscribe");
-        mCompositeDisposable.add(mConversationFacade.getCurrentAccountSubject()
-                .switchMap(a -> a.getPendingSubject()
-                        .map(pending -> {
-                            Log.d(TAG, "getPendingSubject " + pending.size());
-                            ArrayList<SmartListViewModel> viewmodel = new ArrayList<>(pending.size());
-                            for (Conversation c : pending) {
-                                mContactService.loadContactData(c.getContact(), c.getAccountId()).subscribe(() -> {}, e -> Log.e(TAG, "Can't load contact data"));
-                                viewmodel.add(new SmartListViewModel(c.getAccountId(), c.getContact(), null));
-                            }
-                            return viewmodel;
-                        }))
+        mCompositeDisposable.add(mConversationFacade.getPendingList()
+                .switchMap(viewModels -> viewModels.isEmpty() ? SmartListViewModel.EMPTY_RESULTS
+                        : Observable.combineLatest(viewModels, obs -> {
+                    List<SmartListViewModel> vms = new ArrayList<>(obs.length);
+                    for (Object ob : obs)
+                        vms.add((SmartListViewModel) ob);
+                    return vms;
+                }))
+                .throttleLatest(150, TimeUnit.MILLISECONDS, mUiScheduler)
                 .observeOn(mUiScheduler)
-                .subscribe(viewModels -> getView().showContactRequests(viewModels),
-                        e -> Log.d(TAG, "updateList getPendingSubject onError", e)));
-
+                .subscribe(viewModels -> {
+                    final MainView view = getView();
+                    view.showContactRequests(viewModels);
+                }, e -> Log.w(TAG, "showConversations error ", e)));
     }
 
     public void contactClicked(SmartListViewModel item) {
diff --git a/ring-android/app/src/main/res/layout/frag_conversation_tv.xml b/ring-android/app/src/main/res/layout/frag_conversation_tv.xml
index 81438cc..2265881 100644
--- a/ring-android/app/src/main/res/layout/frag_conversation_tv.xml
+++ b/ring-android/app/src/main/res/layout/frag_conversation_tv.xml
@@ -1,172 +1,167 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    tools:context=".tv.conversation.TvConversationFragment"
-    >
+    android:orientation="vertical"
+    tools:context=".tv.conversation.TvConversationFragment">
 
-    <LinearLayout
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="22sp"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp" />
+
+    <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:orientation="vertical">
+        android:layout_marginTop="15dp"
+        android:clipChildren="false"
+        android:clipToPadding="false">
 
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="22sp"
-            android:textStyle="bold"/>
+        <androidx.core.widget.NestedScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginTop="16dp"
+            android:isScrollContainer="true"
+            android:measureAllChildren="true"
+            app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
-        <TextView
-            android:id="@+id/subtitle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="18sp"/>
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/recycler_view"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:focusableInTouchMode="true"
+                android:nestedScrollingEnabled="true"
+                android:paddingBottom="10dp"
+                tools:listitem="@layout/item_conv_msg_peer_tv" />
+
+        </androidx.core.widget.NestedScrollView>
 
         <FrameLayout
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_marginTop="15dp"
+            android:layout_height="wrap_content"
+            android:background="@drawable/tv_header_bg"
             android:clipChildren="false"
             android:clipToPadding="false">
 
-            <androidx.core.widget.NestedScrollView
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:isScrollContainer="true"
-                android:measureAllChildren="true"
-                android:layout_marginTop="15dp"
-                app:layout_behavior="@string/appbar_scrolling_view_behavior">
+            <androidx.cardview.widget.CardView
+                android:layout_width="300dp"
+                android:layout_height="64dp"
+                android:layout_gravity="center_horizontal"
+                app:cardBackgroundColor="@color/conversation_primary_background"
+                app:cardCornerRadius="32dp"
+                app:cardElevation="4dp">
 
-                <androidx.recyclerview.widget.RecyclerView
-                    android:id="@+id/recycler_view"
+                <LinearLayout
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:paddingBottom="10dp"
-                    tools:listitem="@layout/item_conv_msg_peer_tv" />
-
-            </androidx.core.widget.NestedScrollView>
-
-            <FrameLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:background="@drawable/tv_header_bg"
-                android:clipToPadding="false"
-                android:clipChildren="false">
-
-                <androidx.cardview.widget.CardView
-                    android:layout_width="300dp"
-                    android:layout_height="65dp"
-                    app:cardCornerRadius="35dp"
-                    app:cardElevation="4dp"
-                    android:layout_gravity="center_horizontal"
-                    app:cardBackgroundColor="@color/conversation_primary_background"
-                    >
+                    android:layout_height="match_parent"
+                    android:baselineAligned="false"
+                    android:orientation="horizontal">
 
                     <LinearLayout
-                        android:layout_width="match_parent"
+                        android:id="@+id/text_container"
+                        android:layout_width="wrap_content"
                         android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:gravity="center"
                         android:orientation="horizontal">
 
-                        <LinearLayout
-                            android:id="@+id/text_container"
+                        <ImageButton
+                            android:id="@+id/button_text"
+                            android:layout_width="50dp"
+                            android:layout_height="50dp"
+                            android:alpha="0.85"
+                            android:background="@drawable/tv_button_shape"
+                            android:contentDescription="@string/tv_send_text"
+                            android:src="@drawable/baseline_chat_24"
+                            android:tint="@color/white" />
+
+                        <TextView
+                            android:id="@+id/text_text"
                             android:layout_width="wrap_content"
-                            android:layout_height="match_parent"
-                            android:orientation="horizontal"
-                            android:gravity="center"
-                            android:layout_weight="1">
+                            android:layout_height="wrap_content"
+                            android:layout_marginStart="5dp"
+                            android:text="@string/tv_send_text"
+                            android:textColor="@color/white"
+                            android:textSize="12sp"
+                            android:visibility="gone" />
+                    </LinearLayout>
 
-                            <ImageButton
-                                android:id="@+id/button_text"
-                                android:layout_width="50dp"
-                                android:layout_height="50dp"
-                                android:alpha="0.85"
-                                android:src="@drawable/baseline_chat_24"
-                                android:background="@drawable/tv_button_shape"
-                                android:tint="@color/white"
-                                />
+                    <LinearLayout
+                        android:id="@+id/audio_container"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:gravity="center"
+                        android:orientation="horizontal">
 
-                            <TextView
-                                android:id="@+id/text_text"
-                                android:layout_width="wrap_content"
-                                android:layout_height="wrap_content"
-                                android:text="@string/tv_send_text"
-                                android:textColor="@color/white"
-                                android:layout_marginStart="5dp"
-                                android:textSize="12sp"
-                                android:visibility="gone"/>
-                        </LinearLayout>
+                        <ImageButton
+                            android:id="@+id/button_audio"
+                            android:layout_width="50dp"
+                            android:layout_height="50dp"
+                            android:alpha="0.85"
+                            android:background="@drawable/tv_button_shape"
+                            android:contentDescription="@string/tv_send_audio"
+                            android:src="@drawable/baseline_mic_24"
+                            android:tint="@color/white" />
 
-                        <LinearLayout
-                            android:id="@+id/audio_container"
+                        <TextView
+                            android:id="@+id/text_audio"
                             android:layout_width="wrap_content"
-                            android:layout_height="match_parent"
-                            android:orientation="horizontal"
-                            android:gravity="center"
-                            android:layout_weight="1">
-
-                            <ImageButton
-                                android:id="@+id/button_audio"
-                                android:layout_width="50dp"
-                                android:layout_height="50dp"
-                                android:src="@drawable/baseline_mic_24"
-                                android:background="@drawable/tv_button_shape"
-                                android:alpha="0.85"
-                                android:tint="@color/white"
-                                />
-
-                            <TextView
-                                android:id="@+id/text_audio"
-                                android:layout_width="wrap_content"
-                                android:layout_height="wrap_content"
-                                android:text="@string/tv_send_audio"
-                                android:layout_marginStart="5dp"
-                                android:textColor="@color/white"
-                                android:textSize="12sp"
-                                android:visibility="gone"/>
-
-                        </LinearLayout>
-
-                        <LinearLayout
-                            android:id="@+id/video_container"
-                            android:layout_width="wrap_content"
-                            android:layout_height="match_parent"
-                            android:orientation="horizontal"
-                            android:gravity="center"
-                            android:layout_weight="1">
-
-                            <ImageButton
-                                android:id="@+id/button_video"
-                                android:layout_width="50dp"
-                                android:layout_height="50dp"
-                                android:src="@drawable/baseline_photo_camera_24"
-                                android:background="@drawable/tv_button_shape"
-                                android:alpha="0.85"
-                                android:tint="@color/white"
-                                />
-
-                            <TextView
-                                android:id="@+id/text_video"
-                                android:layout_width="wrap_content"
-                                android:layout_height="wrap_content"
-                                android:text="@string/tv_send_video"
-                                android:layout_marginStart="5dp"
-                                android:textColor="@color/white"
-                                android:textSize="12sp"
-                                android:visibility="gone"/>
-
-                        </LinearLayout>
+                            android:layout_height="wrap_content"
+                            android:layout_marginStart="5dp"
+                            android:text="@string/tv_send_audio"
+                            android:textColor="@color/white"
+                            android:textSize="12sp"
+                            android:visibility="gone" />
 
                     </LinearLayout>
 
-                </androidx.cardview.widget.CardView>
+                    <LinearLayout
+                        android:id="@+id/video_container"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:gravity="center"
+                        android:orientation="horizontal">
 
-            </FrameLayout>
+                        <ImageButton
+                            android:id="@+id/button_video"
+                            android:layout_width="50dp"
+                            android:layout_height="50dp"
+                            android:alpha="0.85"
+                            android:background="@drawable/tv_button_shape"
+                            android:contentDescription="@string/tv_send_video"
+                            android:src="@drawable/baseline_photo_camera_24"
+                            android:tint="@color/white" />
+
+                        <TextView
+                            android:id="@+id/text_video"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginStart="5dp"
+                            android:text="@string/tv_send_video"
+                            android:textColor="@color/white"
+                            android:textSize="12sp"
+                            android:visibility="gone" />
+
+                    </LinearLayout>
+
+                </LinearLayout>
+
+            </androidx.cardview.widget.CardView>
 
         </FrameLayout>
 
-    </LinearLayout>
+    </FrameLayout>
 
-</FrameLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/ring-android/libringclient/src/main/java/cx/ring/contactrequests/ContactRequestsPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/contactrequests/ContactRequestsPresenter.java
index 454b255..d777074 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/contactrequests/ContactRequestsPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/contactrequests/ContactRequestsPresenter.java
@@ -20,6 +20,7 @@
 package cx.ring.contactrequests;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -28,9 +29,11 @@
 import cx.ring.model.CallContact;
 import cx.ring.model.Conversation;
 import cx.ring.mvp.RootPresenter;
+import cx.ring.services.AccountService;
 import cx.ring.services.ContactService;
 import cx.ring.smartlist.SmartListViewModel;
 import cx.ring.utils.Log;
+import io.reactivex.Observable;
 import io.reactivex.Scheduler;
 import io.reactivex.disposables.CompositeDisposable;
 import io.reactivex.subjects.BehaviorSubject;
@@ -40,54 +43,34 @@
     static private final String TAG = ContactRequestsPresenter.class.getSimpleName();
 
     private final Scheduler mUiScheduler;
+    private final AccountService mAccountService;
     private final ConversationFacade mConversationFacade;
-    private final ContactService mContactService;
-
-    private final CompositeDisposable mContactDisposable = new CompositeDisposable();
+    private final BehaviorSubject<String> mAccount = BehaviorSubject.create();
 
     @Inject
-    ContactRequestsPresenter(ConversationFacade conversationFacade, ContactService contactService, @Named("UiScheduler") Scheduler scheduler) {
+    ContactRequestsPresenter(ConversationFacade conversationFacade, AccountService accountService, @Named("UiScheduler") Scheduler scheduler) {
         mConversationFacade = conversationFacade;
-        mContactService = contactService;
+        mAccountService = accountService;
         mUiScheduler = scheduler;
     }
 
-    private final BehaviorSubject<String> mAccount = BehaviorSubject.create();
-
     @Override
     public void bindView(ContactRequestsView view) {
         super.bindView(view);
-        mCompositeDisposable.add(mConversationFacade.getCurrentAccountSubject()
-                .switchMap(a -> a
-                        .getPendingSubject()
-                        .map(pending -> {
-                            ArrayList<SmartListViewModel> viewmodel = new ArrayList<>(pending.size());
-                            for (Conversation c : pending) {
-                                SmartListViewModel vm = new SmartListViewModel(c, true);
-                                viewmodel.add(vm);
-                            }
-                            return viewmodel;
-                        }))
+        mCompositeDisposable.add(mConversationFacade.getPendingList(mAccount.map(mAccountService::getAccount))
+                .switchMap(viewModels -> viewModels.isEmpty() ? SmartListViewModel.EMPTY_RESULTS
+                        : Observable.combineLatest(viewModels, obs -> {
+                    List<SmartListViewModel> vms = new ArrayList<>(obs.length);
+                    for (Object ob : obs)
+                        vms.add((SmartListViewModel) ob);
+                    return vms;
+                }))
                 .observeOn(mUiScheduler)
                 .subscribe(viewModels -> {
                     getView().updateView(viewModels);
-                    CompositeDisposable disposable = new CompositeDisposable();
-                    for (SmartListViewModel vm : viewModels) {
-                        disposable.add(mContactService.observeContact(vm.getAccountId(), vm.getContact(), true)
-                                .observeOn(mUiScheduler)
-                                .subscribe(contact -> getView().updateItem(vm), e -> Log.d(TAG, "updateContact onError", e)));
-                    }
-                    mContactDisposable.clear();
-                    mContactDisposable.add(disposable);
                 }, e -> Log.d(TAG, "updateList subscribe onError", e)));
     }
 
-    @Override
-    public void unbindView() {
-        super.unbindView();
-        mContactDisposable.dispose();
-    }
-
     public void updateAccount(String accountId) {
         mAccount.onNext(accountId);
     }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
index fe25bbe..95d2aa9 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
@@ -372,10 +372,19 @@
                         .map(conv -> observeConversation(account, conv, hasPresence))
                         .toList()));
     }
+    public Observable<List<Observable<SmartListViewModel>>> getPendingList(Observable<Account> currentAccount) {
+        return currentAccount.switchMap(account -> account.getPendingSubject()
+                .switchMapSingle(conversations -> Observable.fromIterable(conversations)
+                        .map(conv -> observeConversation(account, conv, false))
+                        .toList()));
+    }
 
     public Observable<List<Observable<SmartListViewModel>>> getSmartList(boolean hasPresence) {
         return getSmartList(mAccountService.getCurrentAccountSubject(), hasPresence);
     }
+    public Observable<List<Observable<SmartListViewModel>>> getPendingList() {
+        return getPendingList(mAccountService.getCurrentAccountSubject());
+    }
 
     private Single<List<Observable<SmartListViewModel>>> getSearchResults(Account account, String query) {
         Uri uri = new Uri(query);