ChannelDiscoveryActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.app.AlertDialog;
  4import android.content.Context;
  5import android.content.Intent;
  6import android.content.SharedPreferences;
  7import android.net.Uri;
  8import android.os.Bundle;
  9import android.preference.PreferenceManager;
 10import android.text.Html;
 11import android.text.method.LinkMovementMethod;
 12import android.view.KeyEvent;
 13import android.view.Menu;
 14import android.view.MenuItem;
 15import android.view.View;
 16import android.view.inputmethod.InputMethodManager;
 17import android.widget.EditText;
 18import android.widget.TextView;
 19import android.widget.Toast;
 20
 21import androidx.annotation.NonNull;
 22import androidx.core.content.ContextCompat;
 23import androidx.databinding.DataBindingUtil;
 24
 25import com.google.android.material.color.MaterialColors;
 26import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 27import com.google.common.base.Strings;
 28
 29import java.util.Collections;
 30import java.util.HashMap;
 31import java.util.List;
 32import java.util.concurrent.atomic.AtomicReference;
 33
 34import eu.siacs.conversations.Config;
 35import eu.siacs.conversations.R;
 36import eu.siacs.conversations.databinding.ActivityChannelDiscoveryBinding;
 37import eu.siacs.conversations.entities.Account;
 38import eu.siacs.conversations.entities.Bookmark;
 39import eu.siacs.conversations.entities.Conversation;
 40import eu.siacs.conversations.entities.Room;
 41import eu.siacs.conversations.services.ChannelDiscoveryService;
 42import eu.siacs.conversations.services.QuickConversationsService;
 43import eu.siacs.conversations.ui.adapter.ChannelSearchResultAdapter;
 44import eu.siacs.conversations.ui.util.PendingItem;
 45import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
 46import eu.siacs.conversations.utils.AccountUtils;
 47import eu.siacs.conversations.xmpp.Jid;
 48
 49public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.OnActionExpandListener, TextView.OnEditorActionListener, ChannelDiscoveryService.OnChannelSearchResultsFound, ChannelSearchResultAdapter.OnChannelSearchResultSelected {
 50
 51    private static final String CHANNEL_DISCOVERY_OPT_IN = "channel_discovery_opt_in";
 52
 53    private final ChannelSearchResultAdapter adapter = new ChannelSearchResultAdapter();
 54    private final PendingItem<String> mInitialSearchValue = new PendingItem<>();
 55    private ActivityChannelDiscoveryBinding binding;
 56    private MenuItem mMenuSearchView;
 57    private EditText mSearchEditText;
 58
 59    private String[] pendingServices = null;
 60    private ChannelDiscoveryService.Method method = ChannelDiscoveryService.Method.LOCAL_SERVER;
 61    private HashMap<Jid, Account> mucServices = null;
 62
 63    private boolean optedIn = false;
 64
 65    @Override
 66    protected void refreshUiReal() {
 67
 68    }
 69
 70    @Override
 71    protected void onBackendConnected() {
 72        if (pendingServices != null) {
 73            mucServices = new HashMap<>();
 74            for (int i = 0; i < pendingServices.length; i += 2) {
 75                mucServices.put(Jid.of(pendingServices[i]), xmppConnectionService.findAccountByJid(Jid.of(pendingServices[i+1])));
 76            }
 77        }
 78
 79        this.method = getMethod(this);
 80
 81        if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
 82            final String query;
 83            if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
 84                query = mSearchEditText.getText().toString();
 85            } else {
 86                query = mInitialSearchValue.peek();
 87            }
 88            toggleLoadingScreen();
 89            xmppConnectionService.discoverChannels(query, this.method, this.mucServices, this);
 90        }
 91    }
 92
 93    @Override
 94    protected void onCreate(final Bundle savedInstanceState) {
 95        super.onCreate(savedInstanceState);
 96        binding = DataBindingUtil.setContentView(this, R.layout.activity_channel_discovery);
 97        setSupportActionBar(binding.toolbar);
 98        Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
 99        configureActionBar(getSupportActionBar(), true);
100        binding.list.setAdapter(this.adapter);
101        this.adapter.setOnChannelSearchResultSelectedListener(this);
102        this.optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false);
103
104        final String search = savedInstanceState == null ? null : savedInstanceState.getString("search");
105        if (search != null) {
106            mInitialSearchValue.push(search);
107        }
108
109        pendingServices = getIntent().getStringArrayExtra("services");
110    }
111
112    private ChannelDiscoveryService.Method getMethod(final Context c) {
113        if (this.mucServices != null) return ChannelDiscoveryService.Method.LOCAL_SERVER;
114        if ( Strings.isNullOrEmpty(Config.CHANNEL_DISCOVERY)) {
115            return ChannelDiscoveryService.Method.LOCAL_SERVER;
116        }
117        if (QuickConversationsService.isQuicksy()) {
118            return ChannelDiscoveryService.Method.JABBER_NETWORK;
119        }
120        final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(c);
121        final String m = p.getString("channel_discovery_method", c.getString(R.string.default_channel_discovery));
122        try {
123            return ChannelDiscoveryService.Method.valueOf(m);
124        } catch (IllegalArgumentException e) {
125            return ChannelDiscoveryService.Method.JABBER_NETWORK;
126        }
127    }
128
129    @Override
130    public boolean onCreateOptionsMenu(final Menu menu) {
131        getMenuInflater().inflate(R.menu.channel_discovery_activity, menu);
132        AccountUtils.showHideMenuItems(menu);
133        mMenuSearchView = menu.findItem(R.id.action_search);
134        final View mSearchView = mMenuSearchView.getActionView();
135        mSearchEditText = mSearchView.findViewById(R.id.search_field);
136        mSearchEditText.setHint(R.string.search_channels);
137        final String initialSearchValue = mInitialSearchValue.pop();
138        if (initialSearchValue != null) {
139            mMenuSearchView.expandActionView();
140            mSearchEditText.append(initialSearchValue);
141            mSearchEditText.requestFocus();
142            if ((optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) && xmppConnectionService != null) {
143                xmppConnectionService.discoverChannels(initialSearchValue, this.method, this.mucServices, this);
144            }
145        }
146        mSearchEditText.setOnEditorActionListener(this);
147        mMenuSearchView.setOnActionExpandListener(this);
148        return true;
149    }
150
151    @Override
152    public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
153        mSearchEditText.post(() -> {
154            mSearchEditText.requestFocus();
155            final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
156            imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
157        });
158        return true;
159    }
160
161    @Override
162    public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
163        final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
164        imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
165        mSearchEditText.setText("");
166        toggleLoadingScreen();
167        if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
168            xmppConnectionService.discoverChannels(null, this.method, this.mucServices, this);
169        }
170        return true;
171    }
172
173    private void toggleLoadingScreen() {
174        adapter.submitList(Collections.emptyList());
175        binding.progressBar.setVisibility(View.VISIBLE);
176        binding.list.setBackgroundColor(MaterialColors.getColor(binding.list, com.google.android.material.R.attr.colorSurface));
177    }
178
179    @Override
180    public void onStart() {
181        super.onStart();
182        this.method = getMethod(this);
183        if (pendingServices == null && !optedIn && method == ChannelDiscoveryService.Method.JABBER_NETWORK) {
184            final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
185            builder.setTitle(R.string.channel_discovery_opt_in_title);
186            builder.setMessage(Html.fromHtml(getString(R.string.channel_discover_opt_in_message)));
187            builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish());
188            builder.setPositiveButton(R.string.confirm, (dialog, which) -> optIn());
189            builder.setOnCancelListener(dialog -> finish());
190            final androidx.appcompat.app.AlertDialog dialog = builder.create();
191            dialog.setOnShowListener(d -> {
192                final TextView textView = dialog.findViewById(android.R.id.message);
193                if (textView == null) {
194                    return;
195                }
196                textView.setMovementMethod(LinkMovementMethod.getInstance());
197            });
198            dialog.setCanceledOnTouchOutside(false);
199            dialog.show();
200            holdLoading();
201        }
202    }
203
204    private void holdLoading() {
205        adapter.submitList(Collections.emptyList());
206        binding.progressBar.setVisibility(View.GONE);
207        binding.list.setBackgroundColor(MaterialColors.getColor(binding.list, com.google.android.material.R.attr.colorSurface));
208    }
209
210    @Override
211    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
212        if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
213            savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
214        }
215        super.onSaveInstanceState(savedInstanceState);
216    }
217
218    private void optIn() {
219        SharedPreferences preferences = getPreferences();
220        preferences.edit().putBoolean(CHANNEL_DISCOVERY_OPT_IN, true).apply();
221        optedIn = true;
222        toggleLoadingScreen();
223        xmppConnectionService.discoverChannels(null, this.method, this.mucServices, this);
224    }
225
226    @Override
227    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
228        if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
229            toggleLoadingScreen();
230            SoftKeyboardUtils.hideSoftKeyboard(this);
231            xmppConnectionService.discoverChannels(v.getText().toString(), this.method, this.mucServices, this);
232        }
233        return true;
234    }
235
236    @Override
237    public void onChannelSearchResultsFound(final List<Room> results) {
238        runOnUiThread(() -> {
239            adapter.submitList(results);
240            binding.progressBar.setVisibility(View.GONE);
241            if (results.isEmpty()) {
242                binding.list.setBackground(ContextCompat.getDrawable(this,R.drawable.background_no_results));
243            } else {
244                binding.list.setBackgroundColor(MaterialColors.getColor(binding.list, com.google.android.material.R.attr.colorSurface));
245            }
246        });
247
248    }
249
250    @Override
251    public void onChannelSearchResult(final Room result) {
252        final List<String> accounts = AccountUtils.getEnabledAccounts(xmppConnectionService);
253        if (accounts.size() == 1) {
254            joinChannelSearchResult(accounts.get(0), result);
255        } else if (accounts.isEmpty()) {
256            Toast.makeText(this, R.string.please_enable_an_account, Toast.LENGTH_LONG).show();
257        } else {
258            final AtomicReference<String> account = new AtomicReference<>(accounts.get(0));
259            final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
260            builder.setTitle(R.string.choose_account);
261            builder.setSingleChoiceItems(accounts.toArray(new CharSequence[0]), 0, (dialog, which) -> account.set(accounts.get(which)));
262            builder.setPositiveButton(R.string.join, (dialog, which) -> joinChannelSearchResult(account.get(), result));
263            builder.setNegativeButton(R.string.cancel, null);
264            builder.create().show();
265        }
266
267    }
268
269    @Override
270    public boolean onContextItemSelected(@NonNull MenuItem item) {
271        final Room room = adapter.getCurrent();
272        if (room == null) {
273            return false;
274        }
275        final int itemId = item.getItemId();
276        if (itemId == R.id.share_with) {
277            StartConversationActivity.shareAsChannel(this, room.address);
278            return true;
279        } else if (itemId == R.id.open_join_dialog) {
280            final Intent intent = new Intent(this, StartConversationActivity.class);
281            intent.setAction(Intent.ACTION_VIEW);
282            intent.putExtra("force_dialog", true);
283            intent.setData(Uri.parse(String.format("xmpp:%s?join", room.address)));
284            startActivity(intent);
285            return true;
286        } else {
287            return false;
288        }
289    }
290
291    public void joinChannelSearchResult(final String selectedAccount, final Room result) {
292        final Jid jid = Jid.ofEscaped(selectedAccount);
293        final Account account = xmppConnectionService.findAccountByJid(jid);
294        final Conversation conversation =
295                xmppConnectionService.findOrCreateConversation(
296                        account, result.getRoom(), true, true, true);
297        final var existingBookmark = conversation.getBookmark();
298        if (existingBookmark == null) {
299            final var bookmark = new Bookmark(account, conversation.getJid().asBareJid());
300            bookmark.setAutojoin(true);
301            xmppConnectionService.createBookmark(account, bookmark);
302        } else {
303            if (!existingBookmark.autojoin()) {
304                existingBookmark.setAutojoin(true);
305                xmppConnectionService.createBookmark(account, existingBookmark);
306            }
307        }
308        switchToConversation(conversation);
309    }
310}