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