diff options
author | Christian Schneppe <christian@pix-art.de> | 2019-04-26 23:12:05 +0200 |
---|---|---|
committer | Christian Schneppe <christian@pix-art.de> | 2019-04-26 23:12:22 +0200 |
commit | f9f07063876ec5f3917ce72385b015e1b5f31d7e (patch) | |
tree | 093f9ded3021800b20e1e8dcb22d4d8ac17dc7db | |
parent | 35276c6519afa1da6042dd485ddae09b06c68aeb (diff) |
implement channel discovery
refactor muc search to use http
cache channel search results
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> |