aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Schneppe <christian@pix-art.de>2019-04-26 23:12:05 +0200
committerChristian Schneppe <christian@pix-art.de>2019-04-26 23:12:22 +0200
commitf9f07063876ec5f3917ce72385b015e1b5f31d7e (patch)
tree093f9ded3021800b20e1e8dcb22d4d8ac17dc7db
parent35276c6519afa1da6042dd485ddae09b06c68aeb (diff)
implement channel discovery
refactor muc search to use http cache channel search results
-rw-r--r--build.gradle3
-rw-r--r--proguard-rules.pro2
-rw-r--r--src/main/AndroidManifest.xml3
-rw-r--r--src/main/java/de/pixart/messenger/Config.java1
-rw-r--r--src/main/java/de/pixart/messenger/http/services/MuclumbusService.java92
-rw-r--r--src/main/java/de/pixart/messenger/services/AvatarService.java12
-rw-r--r--src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java103
-rw-r--r--src/main/java/de/pixart/messenger/services/XmppConnectionService.java16
-rw-r--r--src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java196
-rw-r--r--src/main/java/de/pixart/messenger/ui/StartConversationActivity.java6
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java78
-rw-r--r--src/main/java/de/pixart/messenger/utils/AccountUtils.java15
-rw-r--r--src/main/res/layout/activity_channel_discovery.xml40
-rw-r--r--src/main/res/layout/search_result_item.xml52
-rw-r--r--src/main/res/menu/channel_discovery_activity.xml11
-rw-r--r--src/main/res/menu/start_conversation_fab_submenu.xml4
-rw-r--r--src/main/res/values/strings.xml4
17 files changed, 635 insertions, 3 deletions
diff --git a/build.gradle b/build.gradle
index a27b439b0..53875273a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -83,6 +83,9 @@ dependencies {
implementation 'com.leinardi.android:speed-dial:2.0.1'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.squareup.okhttp3:okhttp:3.12.2' // versions > 3.12.x don't support API level < 21 anymore
+ 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'
}
ext {
diff --git a/proguard-rules.pro b/proguard-rules.pro
index e60465e6c..336771bcd 100644
--- a/proguard-rules.pro
+++ b/proguard-rules.pro
@@ -19,6 +19,8 @@
-dontwarn org.bouncycastle.cert.dane.**
-dontwarn rocks.xmpp.addr.**
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
+-dontwarn java.lang.**
+-dontwarn javax.lang.**
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 71b2e5909..217f90d1e 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -312,6 +312,9 @@
<activity
android:name=".ui.MucUsersActivity"
android:label="@string/group_chat_members" />
+ <activity
+ android:name=".ui.ChannelDiscoveryActivity"
+ android:label="@string/discover_channels" />
<service android:name=".services.ExportBackupService" />
<service android:name=".services.ImportBackupService" />
diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java
index a5d25786b..e61b1d28e 100644
--- a/src/main/java/de/pixart/messenger/Config.java
+++ b/src/main/java/de/pixart/messenger/Config.java
@@ -54,6 +54,7 @@ public final class Config {
public static final String DOMAIN_LOCK = null; //BuildConfig.DOMAIN_LOCK; //only allow account creation for this domain
public static final String MAGIC_CREATE_DOMAIN = "blabber.im";
public static final String QUICKSY_DOMAIN = "quicksy.im";
+ public static final String CHANNEL_DISCOVERY = "https://search.jabbercat.org";
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
public static final boolean USE_RANDOM_RESOURCE_ON_EVERY_BIND = false;
diff --git a/src/main/java/de/pixart/messenger/http/services/MuclumbusService.java b/src/main/java/de/pixart/messenger/http/services/MuclumbusService.java
new file mode 100644
index 000000000..7c8ca9e32
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/http/services/MuclumbusService.java
@@ -0,0 +1,92 @@
+package de.pixart.messenger.http.services;
+
+import com.google.common.base.Objects;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import de.pixart.messenger.services.AvatarService;
+import de.pixart.messenger.utils.UIHelper;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Query;
+import rocks.xmpp.addr.Jid;
+
+public interface MuclumbusService {
+
+ @GET("/api/1.0/rooms/unsafe")
+ Call<Rooms> getRooms(@Query("p") int page);
+
+ @POST("/api/1.0/search")
+ Call<SearchResult> search(@Body SearchRequest searchRequest);
+
+ class Rooms {
+ int page;
+ int total;
+ int pages;
+ public List<Room> items;
+ }
+
+ class Room implements AvatarService.Avatarable {
+
+ public String address;
+ public String name;
+ public String description;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Jid getRoom() {
+ try {
+ return Jid.of(address);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public int getAvatarBackgroundColor() {
+ 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 {
+ public Set<String> keywords;
+
+ public SearchRequest(String keyword) {
+ this.keywords = Collections.singleton(keyword);
+ }
+ }
+
+ class SearchResult {
+ public Result result;
+ }
+
+ class Result {
+ public List<Room> items;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/services/AvatarService.java b/src/main/java/de/pixart/messenger/services/AvatarService.java
index b37811b7f..c38fdcc99 100644
--- a/src/main/java/de/pixart/messenger/services/AvatarService.java
+++ b/src/main/java/de/pixart/messenger/services/AvatarService.java
@@ -38,6 +38,7 @@ import de.pixart.messenger.entities.Conversational;
import de.pixart.messenger.entities.ListItem;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.http.services.MuclumbusService;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xmpp.OnAdvancedStreamFeaturesLoaded;
import de.pixart.messenger.xmpp.XmppConnection;
@@ -82,10 +83,21 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
return get((ListItem) avatarable, size, cachedOnly);
} else if (avatarable instanceof MucOptions.User) {
return get((MucOptions.User) avatarable, size, cachedOnly);
+ } else if (avatarable instanceof MuclumbusService.Room) {
+ return get((MuclumbusService.Room) avatarable, size, cachedOnly);
}
throw new AssertionError("AvatarService does not know how to generate avatar from "+avatarable.getClass().getName());
}
+ private Bitmap get(final MuclumbusService.Room result, final int size, boolean cacheOnly) {
+ final Jid room = result.getRoom();
+ Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null;
+ if (conversation != null) {
+ return get(conversation, size, cacheOnly);
+ }
+ return get(result.getName(), room != null ? room.asBareJid().toEscapedString() : result.getName(), size, cacheOnly);
+ }
+
private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
if (contact.isSelf()) {
return get(contact.getAccount(), size, cachedOnly);
diff --git a/src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java b/src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java
new file mode 100644
index 000000000..21711c647
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java
@@ -0,0 +1,103 @@
+package de.pixart.messenger.services;
+
+import android.util.Log;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.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);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
index 5de9a7501..1456d5a8e 100644
--- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
+++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
@@ -78,6 +78,7 @@ import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
@@ -110,6 +111,7 @@ import de.pixart.messenger.generator.MessageGenerator;
import de.pixart.messenger.generator.PresenceGenerator;
import de.pixart.messenger.http.CustomURLStreamHandlerFactory;
import de.pixart.messenger.http.HttpConnectionManager;
+import de.pixart.messenger.http.services.MuclumbusService;
import de.pixart.messenger.parser.AbstractParser;
import de.pixart.messenger.parser.IqParser;
import de.pixart.messenger.parser.MessageParser;
@@ -162,13 +164,14 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket;
import de.pixart.messenger.xmpp.stanzas.MessagePacket;
import de.pixart.messenger.xmpp.stanzas.PresencePacket;
import me.leolin.shortcutbadger.ShortcutBadger;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.ui.SettingsActivity.CHAT_STATES;
import static de.pixart.messenger.ui.SettingsActivity.CONFIRM_MESSAGES;
import static de.pixart.messenger.ui.SettingsActivity.ENABLE_MULTI_ACCOUNTS;
import static de.pixart.messenger.ui.SettingsActivity.INDICATE_RECEIVED;
-import static de.pixart.messenger.ui.SettingsActivity.ENABLE_MULTI_ACCOUNTS;
public class XmppConnectionService extends Service {
@@ -255,9 +258,11 @@ public class XmppConnectionService extends Service {
private long mLastActivity = 0;
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);
protected AtomicBoolean mForceForegroundService = new AtomicBoolean(false);
+ private AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false);
private OnMessagePacketReceived mMessageParser = new MessageParser(this);
private OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
private IqParser mIqParser = new IqParser(this);
@@ -834,6 +839,10 @@ public class XmppConnectionService extends Service {
editor.commit();
}
+ public void discoverChannels(String query, ChannelDiscoveryService.OnChannelSearchResultsFound onChannelSearchResultsFound) {
+ mChannelDiscoveryService.discover(query, onChannelSearchResultsFound);
+ }
+
public boolean isDataSaverDisabled() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
@@ -1144,6 +1153,7 @@ public class XmppConnectionService extends Service {
if (Compatibility.runsTwentySix()) {
mNotificationService.initializeChannels();
}
+ mForceDuringOnCreate.set(Compatibility.runsAndTargetsTwentySix(this));
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
@@ -1213,6 +1223,8 @@ public class XmppConnectionService extends Service {
intentFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
registerReceiver(this.mInternalEventReceiver, intentFilter);
}
+ mForceDuringOnCreate.set(false);
+ toggleForegroundService();
//start export log service every day at given time
ScheduleAutomaticExport();
// cancel scheduled exporter
@@ -1307,7 +1319,7 @@ public class XmppConnectionService extends Service {
private void toggleForegroundService(boolean force) {
final boolean status;
- if (force || mForceForegroundService.get() || (Compatibility.keepForegroundService(this)/* && hasEnabledAccounts()*/)) {
+ if (force || mForceDuringOnCreate.get() || mForceForegroundService.get() || (Compatibility.keepForegroundService(this) && hasEnabledAccounts())) {
final Notification notification = this.mNotificationService.createForegroundNotification();
startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, notification);
if (!mForceForegroundService.get()) {
diff --git a/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java b/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java
new file mode 100644
index 000000000..e0b70b6ac
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java
@@ -0,0 +1,196 @@
+package de.pixart.messenger.ui;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.v7.widget.Toolbar;
+import android.text.Html;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.ActivityChannelDiscoveryBinding;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.http.services.MuclumbusService;
+import de.pixart.messenger.services.ChannelDiscoveryService;
+import de.pixart.messenger.ui.adapter.ChannelSearchResultAdapter;
+import de.pixart.messenger.ui.util.PendingItem;
+import de.pixart.messenger.ui.util.SoftKeyboardUtils;
+import de.pixart.messenger.utils.AccountUtils;
+import rocks.xmpp.addr.Jid;
+
+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;
+ private EditText mSearchEditText;
+ private boolean optedIn = false;
+
+ @Override
+ protected void refreshUiReal() {
+
+ }
+
+ @Override
+ void onBackendConnected() {
+ if (optedIn) {
+ String query;
+ if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
+ query = mSearchEditText.getText().toString();
+ } else {
+ query = mInitialSearchValue.peek();
+ }
+ xmppConnectionService.discoverChannels(query, this);
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_channel_discovery);
+ setSupportActionBar((Toolbar) binding.toolbar);
+ configureActionBar(getSupportActionBar(), true);
+ binding.list.setAdapter(this.adapter);
+ this.adapter.setOnChannelSearchResultSelectedListener(this);
+ optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false);
+
+ final String search = savedInstanceState == null ? null : savedInstanceState.getString("search");
+ if (search != null) {
+ mInitialSearchValue.push(search);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.muc_users_activity, menu);
+ mMenuSearchView = menu.findItem(R.id.action_search);
+ final View mSearchView = mMenuSearchView.getActionView();
+ mSearchEditText = mSearchView.findViewById(R.id.search_field);
+ mSearchEditText.setHint(R.string.search_channels);
+ String initialSearchValue = mInitialSearchValue.pop();
+ if (initialSearchValue != null) {
+ mMenuSearchView.expandActionView();
+ mSearchEditText.append(initialSearchValue);
+ mSearchEditText.requestFocus();
+ if (optedIn) {
+ xmppConnectionService.discoverChannels(initialSearchValue, this);
+ }
+ }
+ mSearchEditText.setOnEditorActionListener(this);
+ mMenuSearchView.setOnActionExpandListener(this);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ mSearchEditText.post(() -> {
+ mSearchEditText.requestFocus();
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
+ mSearchEditText.setText("");
+ 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();
+ if (!optedIn) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.channel_discovery_opt_in_title);
+ builder.setMessage(Html.fromHtml(getString(R.string.channel_discover_opt_in_message)));
+ builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish());
+ builder.setPositiveButton(R.string.confirm, (dialog, which) -> optIn());
+ builder.setOnCancelListener(dialog -> finish());
+ final AlertDialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.show();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
+ savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
+ }
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ private void optIn() {
+ SharedPreferences preferences = getPreferences();
+ preferences.edit().putBoolean(CHANNEL_DISCOVERY_OPT_IN, true).apply();
+ optedIn = true;
+ xmppConnectionService.discoverChannels(null, this);
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (optedIn) {
+ xmppConnectionService.discoverChannels(v.getText().toString(), this);
+ }
+ toggleLoadingScreen();
+ SoftKeyboardUtils.hideSoftKeyboard(this);
+ return true;
+ }
+
+ @Override
+ public void onChannelSearchResultsFound(List<MuclumbusService.Room> results) {
+ runOnUiThread(() -> {
+ adapter.submitList(results);
+ binding.list.setVisibility(View.VISIBLE);
+ binding.progressBar.setVisibility(View.GONE);
+ });
+ }
+
+ @Override
+ public void onChannelSearchResult(final MuclumbusService.Room result) {
+ List<String> accounts = AccountUtils.getEnabledAccounts(xmppConnectionService);
+ if (accounts.size() == 1) {
+ joinChannelSearchResult(accounts.get(0), result);
+ } else if (accounts.size() > 0) {
+ final AtomicReference<String> account = new AtomicReference<>(accounts.get(0));
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.choose_account);
+ builder.setSingleChoiceItems(accounts.toArray(new CharSequence[0]), 0, (dialog, which) -> account.set(accounts.get(which)));
+ builder.setPositiveButton(R.string.join, (dialog, which) -> joinChannelSearchResult(account.get(), result));
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.create().show();
+ }
+ }
+
+ public void joinChannelSearchResult(String accountJid, MuclumbusService.Room result) {
+ Account account = xmppConnectionService.findAccountByJid(Jid.of(accountJid));
+ final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, result.getRoom(), true, true, true);
+ switchToConversation(conversation);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
index 6706d004c..75e7c2f52 100644
--- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
@@ -70,6 +70,7 @@ import de.pixart.messenger.ui.interfaces.OnBackendConnected;
import de.pixart.messenger.ui.util.JidDialog;
import de.pixart.messenger.ui.util.PendingItem;
import de.pixart.messenger.ui.util.SoftKeyboardUtils;
+import de.pixart.messenger.utils.AccountUtils;
import de.pixart.messenger.utils.MenuDoubleTabUtil;
import de.pixart.messenger.utils.XmppUri;
import de.pixart.messenger.xmpp.OnUpdateBlocklist;
@@ -307,6 +308,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
prefilled = null;
}
switch (actionItem.getId()) {
+ case R.id.discover_public_channels:
+ startActivity(new Intent(this, ChannelDiscoveryActivity.class));
+ break;
case R.id.join_public_channel:
showJoinConferenceDialog(prefilled);
break;
@@ -763,7 +767,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
this.mPostponedActivityResult = null;
}
- this.mActivatedAccounts.clear();
+ this.mActivatedAccounts.addAll(AccountUtils.getEnabledAccounts(xmppConnectionService));
for (Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
if (Config.DOMAIN_LOCK != null) {
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java
new file mode 100644
index 000000000..d137e3a6f
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java
@@ -0,0 +1,78 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.databinding.DataBindingUtil;
+import android.support.annotation.NonNull;
+import android.support.v7.recyclerview.extensions.ListAdapter;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.SearchResultItemBinding;
+import de.pixart.messenger.http.services.MuclumbusService;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
+
+
+public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Room, ChannelSearchResultAdapter.ViewHolder> {
+
+ private OnChannelSearchResultSelected listener;
+
+ 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 a.address != null && a.address.equals(b.address);
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull MuclumbusService.Room a, @NonNull MuclumbusService.Room b) {
+ return a.equals(b);
+ }
+ };
+
+ public ChannelSearchResultAdapter() {
+ super(DIFF);
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+ return new ViewHolder(DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.search_result_item, viewGroup, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
+ final MuclumbusService.Room searchResult = getItem(position);
+ viewHolder.binding.name.setText(searchResult.getName());
+ final String description = searchResult.getDescription();
+ if (TextUtils.isEmpty(description)) {
+ viewHolder.binding.description.setVisibility(View.GONE);
+ } else {
+ viewHolder.binding.description.setText(description);
+ viewHolder.binding.description.setVisibility(View.VISIBLE);
+ }
+ viewHolder.binding.room.setText(searchResult.getRoom().asBareJid().toString());
+ AvatarWorkerTask.loadAvatar(searchResult, viewHolder.binding.avatar, R.dimen.avatar);
+ viewHolder.binding.getRoot().setOnClickListener(v -> listener.onChannelSearchResult(searchResult));
+ }
+
+ public void setOnChannelSearchResultSelectedListener(OnChannelSearchResultSelected listener) {
+ this.listener = listener;
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+
+ private final SearchResultItemBinding binding;
+
+ private ViewHolder(SearchResultItemBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+ }
+
+ public interface OnChannelSearchResultSelected {
+ void onChannelSearchResult(MuclumbusService.Room result);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/AccountUtils.java b/src/main/java/de/pixart/messenger/utils/AccountUtils.java
index d675ed680..593d3a089 100644
--- a/src/main/java/de/pixart/messenger/utils/AccountUtils.java
+++ b/src/main/java/de/pixart/messenger/utils/AccountUtils.java
@@ -4,8 +4,10 @@ import android.app.Activity;
import android.content.Intent;
import android.widget.Toast;
+import java.util.ArrayList;
import java.util.List;
+import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.services.XmppConnectionService;
@@ -18,6 +20,19 @@ public class AccountUtils {
MANAGE_ACCOUNT_ACTIVITY = getManageAccountActivityClass();
}
+ public static List<String> getEnabledAccounts(final XmppConnectionService service) {
+ ArrayList<String> accounts = new ArrayList<>();
+ for (Account account : service.getAccounts()) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ if (Config.DOMAIN_LOCK != null) {
+ accounts.add(account.getJid().getLocal());
+ } else {
+ accounts.add(account.getJid().asBareJid().toString());
+ }
+ }
+ }
+ return accounts;
+ }
public static Account getFirstEnabled(XmppConnectionService service) {
final List<Account> accounts = service.getAccounts();
diff --git a/src/main/res/layout/activity_channel_discovery.xml b/src/main/res/layout/activity_channel_discovery.xml
new file mode 100644
index 000000000..ee253f3dd
--- /dev/null
+++ b/src/main/res/layout/activity_channel_discovery.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="?attr/color_background_secondary"
+ android:orientation="vertical">
+
+ <include
+ android:id="@+id/toolbar"
+ layout="@layout/toolbar" />
+
+ <ProgressBar
+ android:id="@+id/progressBar"
+ style="?android:attr/progressBarStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ app:layout_anchor="@+id/list" />
+
+ <android.support.design.widget.CoordinatorLayout
+ android:id="@+id/coordinator"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/color_background_secondary">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/color_background_secondary"
+ android:orientation="vertical"
+ android:visibility="gone"
+ app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
+ </android.support.design.widget.CoordinatorLayout>
+
+ </LinearLayout>
+</layout> \ No newline at end of file
diff --git a/src/main/res/layout/search_result_item.xml b/src/main/res/layout/search_result_item.xml
new file mode 100644
index 000000000..856aa7ab6
--- /dev/null
+++ b/src/main/res/layout/search_result_item.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?selectableItemBackground"
+ android:padding="@dimen/list_padding">
+
+ <com.makeramen.roundedimageview.RoundedImageView
+ android:id="@+id/avatar"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentLeft="true"
+ android:scaleType="centerCrop"
+ app:riv_corner_radius="2dp" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/avatar_item_distance"
+ android:layout_marginLeft="@dimen/avatar_item_distance"
+ android:layout_toEndOf="@+id/avatar"
+ android:layout_toRightOf="@+id/avatar"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.Conversations.Subhead" />
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:textAppearance="@style/TextAppearance.Conversations.Body1" />
+
+ <TextView
+ android:id="@+id/room"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary" />
+ </LinearLayout>
+
+ </RelativeLayout>
+</layout> \ No newline at end of file
diff --git a/src/main/res/menu/channel_discovery_activity.xml b/src/main/res/menu/channel_discovery_activity.xml
new file mode 100644
index 000000000..93e044f27
--- /dev/null
+++ b/src/main/res/menu/channel_discovery_activity.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/action_search"
+ android:icon="?attr/icon_search"
+ android:title="@string/search"
+ app:actionLayout="@layout/actionview_search"
+ app:showAsAction="collapseActionView|always" />
+</menu> \ No newline at end of file
diff --git a/src/main/res/menu/start_conversation_fab_submenu.xml b/src/main/res/menu/start_conversation_fab_submenu.xml
index 212a94a7e..2cf545d68 100644
--- a/src/main/res/menu/start_conversation_fab_submenu.xml
+++ b/src/main/res/menu/start_conversation_fab_submenu.xml
@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
+ android:id="@+id/discover_public_channels"
+ android:icon="@drawable/ic_search_white_24dp"
+ android:title="@string/discover_channels" />
+ <item
android:id="@+id/join_public_channel"
android:icon="@drawable/ic_input_white_24dp"
android:title="@string/join_public_channel" />
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 90bcb50b7..4abc42743 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -904,4 +904,8 @@
<string name="delete_locally">Delete locally</string>
<string name="delete_from_server">Delete from server</string>
<string name="go_online_to_delete">You have to activate and/or go online with the account you want to delete from server.</string>
+ <string name="discover_channels">Discover channels</string>
+ <string name="search_channels">Search channels</string>
+ <string name="channel_discovery_opt_in_title">Privacy notice</string>
+ <string name="channel_discover_opt_in_message"><![CDATA[Channel discovery uses a third party service called <a href="https://search.jabbercat.org">search.jabbercat.org</a>.<br><br>Using this feature will transmit your IP address and search terms to that service. See their <a href="https://search.jabbercat.org/privacy">Privacy Policy</a> for more information.]]></string>
</resources>