cache channel search results

Daniel Gultsch created

Change summary

build.gradle                                                                    |   1 
proguard-rules.pro                                                              |   2 
src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java        |  21 
src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java      | 106 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java        |  66 
src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java           |  24 
src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java |   2 
src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java        |   4 
src/main/res/layout/activity_channel_discovery.xml                              |  10 
9 files changed, 156 insertions(+), 80 deletions(-)

Detailed changes

build.gradle 🔗

@@ -66,6 +66,7 @@ dependencies {
     implementation "com.leinardi.android:speed-dial:2.0.1"
     implementation 'com.squareup.retrofit2:retrofit:2.5.0'
     implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
+    implementation 'com.google.guava:guava:27.1-android'
     quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.1'
 }
 

proguard-rules.pro 🔗

@@ -18,3 +18,5 @@
 -dontwarn org.bouncycastle.cert.dane.**
 -dontwarn rocks.xmpp.addr.**
 -dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
+-dontwarn java.lang.**
+-dontwarn javax.lang.**

src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java 🔗

@@ -1,5 +1,7 @@
 package eu.siacs.conversations.http.services;
 
+import com.google.common.base.Objects;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -31,12 +33,8 @@ public interface MuclumbusService {
     class Room implements AvatarService.Avatarable {
 
         public String address;
-        public int nusers;
-        public boolean is_open;
-        public String anonymity_mode;
         public String name;
         public String description;
-        public String language;
 
         public String getName() {
             return name;
@@ -59,6 +57,21 @@ public interface MuclumbusService {
             Jid room = getRoom();
             return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name);
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Room room = (Room) o;
+            return Objects.equal(address, room.address) &&
+                    Objects.equal(name, room.name) &&
+                    Objects.equal(description, room.description);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(address, name, description);
+        }
     }
 
     class SearchRequest {

src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java 🔗

@@ -0,0 +1,106 @@
+package eu.siacs.conversations.services;
+
+import android.util.Log;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.http.services.MuclumbusService;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class ChannelDiscoveryService {
+
+    private final XmppConnectionService service;
+
+
+    private final MuclumbusService muclumbusService;
+
+    private final Cache<String, List<MuclumbusService.Room>> cache;
+
+    public ChannelDiscoveryService(XmppConnectionService service) {
+        this.service = service;
+        Retrofit retrofit = new Retrofit.Builder()
+                .baseUrl(Config.CHANNEL_DISCOVERY)
+                .addConverterFactory(GsonConverterFactory.create())
+                .callbackExecutor(Executors.newSingleThreadExecutor())
+                .build();
+        this.muclumbusService = retrofit.create(MuclumbusService.class);
+        this.cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
+    }
+
+    public void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) {
+        final boolean all = query == null || query.trim().isEmpty();
+        Log.d(Config.LOGTAG, "discover channels. query=" + query);
+        List<MuclumbusService.Room> result = cache.getIfPresent(all ? "" : query);
+        if (result != null) {
+            onChannelSearchResultsFound.onChannelSearchResultsFound(result);
+            return;
+        }
+        if (all) {
+            discoverChannels(onChannelSearchResultsFound);
+        } else {
+            discoverChannels(query, onChannelSearchResultsFound);
+        }
+    }
+
+    private void discoverChannels(OnChannelSearchResultsFound listener) {
+        Call<MuclumbusService.Rooms> call = muclumbusService.getRooms(1);
+        try {
+            call.enqueue(new Callback<MuclumbusService.Rooms>() {
+                @Override
+                public void onResponse(Call<MuclumbusService.Rooms> call, Response<MuclumbusService.Rooms> response) {
+                    final MuclumbusService.Rooms body = response.body();
+                    if (body == null) {
+                        return;
+                    }
+                    cache.put("", body.items);
+                    listener.onChannelSearchResultsFound(body.items);
+                }
+
+                @Override
+                public void onFailure(Call<MuclumbusService.Rooms> call, Throwable throwable) {
+
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void discoverChannels(final String query, OnChannelSearchResultsFound listener) {
+        Call<MuclumbusService.SearchResult> searchResultCall = muclumbusService.search(new MuclumbusService.SearchRequest(query));
+
+        searchResultCall.enqueue(new Callback<MuclumbusService.SearchResult>() {
+            @Override
+            public void onResponse(Call<MuclumbusService.SearchResult> call, Response<MuclumbusService.SearchResult> response) {
+                System.out.println(response.message());
+                MuclumbusService.SearchResult body = response.body();
+                if (body == null) {
+                    return;
+                }
+                cache.put(query, body.result.items);
+                listener.onChannelSearchResultsFound(body.result.items);
+            }
+
+            @Override
+            public void onFailure(Call<MuclumbusService.SearchResult> call, Throwable throwable) {
+                throwable.printStackTrace();
+            }
+        });
+    }
+
+    public interface OnChannelSearchResultsFound {
+        void onChannelSearchResultsFound(List<MuclumbusService.Room> results);
+    }
+}

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -209,6 +209,7 @@ public class XmppConnectionService extends Service {
     private FileBackend fileBackend = new FileBackend(this);
     private MemorizingTrustManager mMemorizingTrustManager;
     private NotificationService mNotificationService = new NotificationService(this);
+    private ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService(this);
     private ShortcutService mShortcutService = new ShortcutService(this);
     private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false);
     private AtomicBoolean mForceForegroundService = new AtomicBoolean(false);
@@ -242,7 +243,6 @@ public class XmppConnectionService extends Service {
     private AvatarService mAvatarService = new AvatarService(this);
     private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
     private PushManagementService mPushManagementService = new PushManagementService(this);
-    private MuclumbusService muclumbusService;
     private QuickConversationsService mQuickConversationsService = new QuickConversationsService(this);
     private final ConversationsFileObserver fileObserver = new ConversationsFileObserver(
             Environment.getExternalStorageDirectory().getAbsolutePath()
@@ -805,61 +805,8 @@ public class XmppConnectionService extends Service {
         return pingNow;
     }
 
-    public void discoverChannels(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) {
-        Log.d(Config.LOGTAG,"discover channels. query="+query);
-        if (query == null || query.trim().isEmpty()) {
-            discoverChannelsInternal(onChannelSearchResultsFound);
-        } else {
-            discoverChannelsInternal(query, onChannelSearchResultsFound);
-        }
-    }
-
-    private void discoverChannelsInternal(OnChannelSearchResultsFound listener) {
-        Call<MuclumbusService.Rooms> call = muclumbusService.getRooms(1);
-        try {
-            call.enqueue(new Callback<MuclumbusService.Rooms>() {
-                @Override
-                public void onResponse(Call<MuclumbusService.Rooms> call, Response<MuclumbusService.Rooms> response) {
-                    final MuclumbusService.Rooms body = response.body();
-                    if (body == null) {
-                        return;
-                    }
-                    listener.onChannelSearchResultsFound(body.items);
-                }
-
-                @Override
-                public void onFailure(Call<MuclumbusService.Rooms> call, Throwable throwable) {
-
-                }
-            });
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void discoverChannelsInternal(String query, OnChannelSearchResultsFound listener) {
-        Call<MuclumbusService.SearchResult> searchResultCall = muclumbusService.search(new MuclumbusService.SearchRequest(query));
-
-        searchResultCall.enqueue(new Callback<MuclumbusService.SearchResult>() {
-            @Override
-            public void onResponse(Call<MuclumbusService.SearchResult> call, Response<MuclumbusService.SearchResult> response) {
-                System.out.println(response.message());
-                MuclumbusService.SearchResult body = response.body();
-                if (body == null) {
-                    return;
-                }
-                listener.onChannelSearchResultsFound(body.result.items);
-            }
-
-            @Override
-            public void onFailure(Call<MuclumbusService.SearchResult> call, Throwable throwable) {
-                throwable.printStackTrace();
-            }
-        });
-    }
-
-    public interface OnChannelSearchResultsFound {
-        void onChannelSearchResultsFound(List<MuclumbusService.Room> results);
+    public void discoverChannels(String query, ChannelDiscoveryService.OnChannelSearchResultsFound onChannelSearchResultsFound) {
+        mChannelDiscoveryService.discover(query, onChannelSearchResultsFound);
     }
 
     public boolean isDataSaverDisabled() {
@@ -1130,13 +1077,6 @@ public class XmppConnectionService extends Service {
         }
         mForceDuringOnCreate.set(false);
         toggleForegroundService();
-
-        Retrofit retrofit = new Retrofit.Builder()
-                .baseUrl(Config.CHANNEL_DISCOVERY)
-                .addConverterFactory(GsonConverterFactory.create())
-                .callbackExecutor(Executors.newSingleThreadExecutor())
-                .build();
-        muclumbusService = retrofit.create(MuclumbusService.class);
     }
 
     private void checkForDeletedFiles() {

src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java 🔗

@@ -2,7 +2,6 @@ package eu.siacs.conversations.ui;
 
 import android.app.AlertDialog;
 import android.content.Context;
-import android.content.Intent;
 import android.content.SharedPreferences;
 import android.databinding.DataBindingUtil;
 import android.os.Bundle;
@@ -25,19 +24,21 @@ import eu.siacs.conversations.databinding.ActivityChannelDiscoveryBinding;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.http.services.MuclumbusService;
-import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.services.ChannelDiscoveryService;
 import eu.siacs.conversations.ui.adapter.ChannelSearchResultAdapter;
 import eu.siacs.conversations.ui.util.PendingItem;
 import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
 import eu.siacs.conversations.utils.AccountUtils;
 import rocks.xmpp.addr.Jid;
 
-public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.OnActionExpandListener, TextView.OnEditorActionListener, XmppConnectionService.OnChannelSearchResultsFound, ChannelSearchResultAdapter.OnChannelSearchResultSelected {
+public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.OnActionExpandListener, TextView.OnEditorActionListener, ChannelDiscoveryService.OnChannelSearchResultsFound, ChannelSearchResultAdapter.OnChannelSearchResultSelected {
 
     private static final String CHANNEL_DISCOVERY_OPT_IN = "channel_discovery_opt_in";
 
     private final ChannelSearchResultAdapter adapter = new ChannelSearchResultAdapter();
 
+    private ActivityChannelDiscoveryBinding binding;
+
     private final PendingItem<String> mInitialSearchValue = new PendingItem<>();
 
     private MenuItem mMenuSearchView;
@@ -66,7 +67,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
     @Override
     protected void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        ActivityChannelDiscoveryBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_channel_discovery);
+        binding = DataBindingUtil.setContentView(this, R.layout.activity_channel_discovery);
         setSupportActionBar((Toolbar) binding.toolbar);
         configureActionBar(getSupportActionBar(), true);
         binding.list.setAdapter(this.adapter);
@@ -116,13 +117,18 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
         final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
         imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
         mSearchEditText.setText("");
-        adapter.submitList(Collections.emptyList());
+        toggleLoadingScreen();
         if (optedIn) {
             xmppConnectionService.discoverChannels(null, this);
         }
         return true;
     }
 
+    private void toggleLoadingScreen() {
+        adapter.submitList(Collections.emptyList());
+        binding.progressBar.setVisibility(View.VISIBLE);
+    }
+
     @Override
     public void onStart() {
         super.onStart();
@@ -159,14 +165,18 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
         if (optedIn) {
             xmppConnectionService.discoverChannels(v.getText().toString(), this);
         }
-        adapter.submitList(Collections.emptyList());
+        toggleLoadingScreen();
         SoftKeyboardUtils.hideSoftKeyboard(this);
         return true;
     }
 
     @Override
     public void onChannelSearchResultsFound(List<MuclumbusService.Room> results) {
-        runOnUiThread(() -> adapter.submitList(results));
+        runOnUiThread(() -> {
+            adapter.submitList(results);
+            binding.list.setVisibility(View.VISIBLE);
+            binding.progressBar.setVisibility(View.GONE);
+        });
 
     }
 

src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java 🔗

@@ -22,7 +22,7 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo
     private static final DiffUtil.ItemCallback<MuclumbusService.Room> DIFF = new DiffUtil.ItemCallback<MuclumbusService.Room>() {
         @Override
         public boolean areItemsTheSame(@NonNull MuclumbusService.Room a, @NonNull MuclumbusService.Room b) {
-            return false;
+            return a.address != null && a.address.equals(b.address);
         }
 
         @Override

src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java 🔗

@@ -1,6 +1,5 @@
 package eu.siacs.conversations.ui.adapter;
 
-import android.app.Activity;
 import android.content.Context;
 import android.content.res.Resources;
 import android.databinding.DataBindingUtil;
@@ -8,7 +7,6 @@ import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
-import android.support.annotation.AttrRes;
 import android.support.annotation.NonNull;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
@@ -17,7 +15,6 @@ import android.widget.ImageView;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.RejectedExecutionException;
 
@@ -26,7 +23,6 @@ import eu.siacs.conversations.databinding.MediaPreviewBinding;
 import eu.siacs.conversations.ui.ConversationFragment;
 import eu.siacs.conversations.ui.XmppActivity;
 import eu.siacs.conversations.ui.util.Attachment;
-import eu.siacs.conversations.ui.util.StyledAttributes;
 
 public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapter.MediaPreviewViewHolder> {
 

src/main/res/layout/activity_channel_discovery.xml 🔗

@@ -14,18 +14,26 @@
             layout="@layout/toolbar" />
 
 
+        <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_anchor="@+id/list"
+            android:layout_gravity="center_horizontal"/>
+
         <android.support.design.widget.CoordinatorLayout
             android:id="@+id/coordinator"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:background="?attr/color_background_primary">
-
             <android.support.v7.widget.RecyclerView
                 android:id="@+id/list"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:background="?attr/color_background_primary"
                 android:orientation="vertical"
+                android:visibility="gone"
                 app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
         </android.support.design.widget.CoordinatorLayout>