package com.gianlu.aria2app.activities; import android.app.SearchManager; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.LinearLayout; import android.widget.SearchView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.RecyclerView; import com.gianlu.aria2app.PK; import com.gianlu.aria2app.R; import com.gianlu.aria2app.Utils; import com.gianlu.aria2app.adapters.SearchResultsAdapter; import com.gianlu.aria2app.api.AbstractClient; import com.gianlu.aria2app.api.AriaRequests; import com.gianlu.aria2app.api.aria2.Aria2Helper; import com.gianlu.aria2app.api.search.MissingSearchEngine; import com.gianlu.aria2app.api.search.SearchApi; import com.gianlu.aria2app.api.search.SearchEngine; import com.gianlu.aria2app.api.search.SearchResult; import com.gianlu.aria2app.api.search.Torrent; import com.gianlu.aria2app.main.MainActivity; import com.gianlu.commonutils.CommonUtils; import com.gianlu.commonutils.analytics.AnalyticsApplication; import com.gianlu.commonutils.dialogs.ActivityWithDialog; import com.gianlu.commonutils.misc.RecyclerMessageView; import com.gianlu.commonutils.misc.SuperTextView; import com.gianlu.commonutils.preferences.Prefs; import com.gianlu.commonutils.ui.Toaster; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import org.jetbrains.annotations.NotNull; import org.json.JSONException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; public class SearchActivity extends ActivityWithDialog implements SearchView.OnQueryTextListener, SearchView.OnCloseListener, SearchApi.OnSearch, SearchResultsAdapter.Listener, MenuItem.OnActionExpandListener { private static final String TAG = SearchActivity.class.getSimpleName(); private RecyclerMessageView rmv; private LinearLayout message; private String query = null; private SearchView searchView; private SearchApi searchApi; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search); setTitle(R.string.searchTorrent); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); message = findViewById(R.id.search_message); rmv = findViewById(R.id.search_rmv); rmv.stopLoading(); rmv.disableSwipeRefresh(); rmv.linearLayoutManager(RecyclerView.VERTICAL, false); rmv.dividerDecoration(RecyclerView.VERTICAL); Button messageMore = findViewById(R.id.search_messageMore); messageMore.setOnClickListener(view -> { try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://swaggerhub.com/apis/devgianlu/torrent-search-engine"))); } catch (ActivityNotFoundException ex) { messageMore.setVisibility(View.GONE); } }); searchApi = SearchApi.get(); } private void showEnginesDialog(@NotNull List<SearchEngine> engines) { CharSequence[] enginesNames = new CharSequence[engines.size()]; for (int i = 0; i < engines.size(); i++) { SearchEngine engine = engines.get(i); enginesNames[i] = engine.name + (engine.proxyed ? " (proxyed)" : ""); } final boolean[] checkedEngines = new boolean[engines.size()]; Set<String> checkedEnginesSet = Prefs.getSet(PK.A2_SEARCH_ENGINES, null); if (checkedEnginesSet == null) { Arrays.fill(checkedEngines, true); } else { for (String checkedEngine : checkedEnginesSet) for (int i = 0; i < engines.size(); i++) if (Objects.equals(engines.get(i).id, checkedEngine)) checkedEngines[i] = true; } MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(R.string.searchEngines) .setMultiChoiceItems(enginesNames, checkedEngines, (dialog, which, isChecked) -> checkedEngines[which] = isChecked) .setPositiveButton(R.string.apply, (dialog, which) -> { Set<String> set = new HashSet<>(); for (int i = 0; i < checkedEngines.length; i++) if (checkedEngines[i]) set.add(engines.get(i).id); if (set.isEmpty()) { Toaster.with(SearchActivity.this).message(R.string.noEnginesSelected).show(); } else { Prefs.putSet(PK.A2_SEARCH_ENGINES, set); if (query != null) onQueryTextSubmit(query); } }) .setNegativeButton(android.R.string.cancel, null); showDialog(builder); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.search, menu); SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); MenuItem searchItem = menu.findItem(R.id.search_search); searchItem.setOnActionExpandListener(this); searchView = (SearchView) searchItem.getActionView(); if (searchManager != null) { searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setIconifiedByDefault(false); searchView.setOnCloseListener(this); searchView.setOnQueryTextListener(this); } return true; } @Override public boolean onQueryTextSubmit(@NotNull String query) { message.setVisibility(View.GONE); rmv.startLoading(); this.query = query; searchApi.search(query.trim(), SearchApi.RESULTS_PER_REQUEST, Prefs.getSet(PK.A2_SEARCH_ENGINES, null), null, this); AnalyticsApplication.sendAnalytics(Utils.ACTION_SEARCH); return true; } @Override public boolean onQueryTextChange(String newText) { return false; } @Override public boolean onClose() { message.setVisibility(View.VISIBLE); rmv.stopLoading(); rmv.hideList(); rmv.hideMessage(); searchView.setQuery(null, false); return false; } @Override public void onResult(@Nullable String query, @NotNull List<SearchResult> results, List<MissingSearchEngine> missingEngines, @Nullable String nextPageToken) { if (!Objects.equals(this.query, query)) return; message.setVisibility(View.GONE); rmv.loadListData(new SearchResultsAdapter(this, results, nextPageToken, this)); notifyMissingEngines(missingEngines); } @Override public void onException(@NonNull Exception ex) { message.setVisibility(View.GONE); rmv.showError(R.string.searchEngine_offline); Log.e(TAG, "Failed search.", ex); } @Override public boolean onOptionsItemSelected(@NotNull MenuItem item) { if (item.getItemId() == R.id.search_engines) { showProgress(R.string.gathering_information); searchApi.listSearchEngines(this, new SearchApi.OnResult<List<SearchEngine>>() { @Override public void onResult(@NonNull List<SearchEngine> result) { dismissDialog(); showEnginesDialog(result); } @Override public void onException(@NonNull Exception ex) { dismissDialog(); Log.e(TAG, "Failed getting engines list.", ex); Toaster.with(SearchActivity.this).message(R.string.failedLoading).show(); } }); return true; } return super.onOptionsItemSelected(item); } @Override public void onResultSelected(@NonNull SearchResult result) { showProgress(R.string.gathering_information); searchApi.getTorrent(result, this, new SearchApi.OnResult<Torrent>() { @Override public void onResult(@NonNull Torrent torrent) { dismissDialog(); showTorrentDialog(torrent); } @Override public void onException(@NonNull Exception ex) { dismissDialog(); Log.e(TAG, "Failed getting torrent info.", ex); Toaster.with(SearchActivity.this).message(R.string.failedLoading).show(); } }); } @Override public void notifyMissingEngines(@NonNull final List<MissingSearchEngine> missingEngines) { if (missingEngines.isEmpty()) return; Snackbar.make(rmv, R.string.missingEngines_message, Snackbar.LENGTH_LONG) .setAction(R.string.show, view -> showMissingEnginesDialog(missingEngines)).show(); } private void showMissingEnginesDialog(List<MissingSearchEngine> missingEngines) { AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(R.string.missingEngines) .setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, missingEngines), null) .setPositiveButton(android.R.string.ok, null); showDialog(builder); } private void showTorrentDialog(@NotNull Torrent torrent) { LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.dialog_torrent, null, false); SuperTextView engine = layout.findViewById(R.id.torrentDialog_engine); SuperTextView size = layout.findViewById(R.id.torrentDialog_size); SuperTextView seeders = layout.findViewById(R.id.torrentDialog_seeders); SuperTextView leeches = layout.findViewById(R.id.torrentDialog_leeches); SearchEngine searchEngine = searchApi.findEngine(torrent.engineId); if (searchEngine != null) engine.setHtml(R.string.searchEngine, searchEngine.name); else engine.setVisibility(View.GONE); size.setHtml(R.string.size, CommonUtils.dimensionFormatter(torrent.size, false)); seeders.setHtml(R.string.numSeederShort, torrent.seeders); leeches.setHtml(R.string.numLeechesShort, torrent.leeches); AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(torrent.title) .setView(layout) .setNegativeButton(R.string.getMagnet, (dialogInterface, i) -> { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, torrent.magnet); sendIntent.setType("text/plain"); startActivity(sendIntent); AnalyticsApplication.sendAnalytics(Utils.ACTION_SEARCH_GET_MAGNET); }) .setPositiveButton(R.string.download, null); if (torrent.torrentFileUrl != null) { builder.setNeutralButton(R.string.getTorrent, (dialogInterface, i) -> { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(torrent.torrentFileUrl))); AnalyticsApplication.sendAnalytics(Utils.ACTION_SEARCH_GET_TORRENT); }); } final AlertDialog dialog = builder.create(); dialog.setOnShowListener(dialogInterface -> dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(view -> { try { Aria2Helper.instantiate(SearchActivity.this) .request(AriaRequests.addUri(Collections.singletonList(torrent.magnet), null, null), new AbstractClient.OnResult<String>() { @Override public void onResult(@NonNull String result) { dialogInterface.dismiss(); Snackbar.make(rmv, R.string.downloadAdded, Snackbar.LENGTH_SHORT) .setAction(R.string.show, view1 -> startActivity(new Intent(SearchActivity.this, MainActivity.class))).show(); } @Override public void onException(@NonNull Exception ex) { Log.e(TAG, "Failed adding URI.", ex); Toaster.with(SearchActivity.this).message(R.string.failedAddingDownload).show(); } }); } catch (Aria2Helper.InitializingException | JSONException ex) { Log.e(TAG, "Failed initializing/parsing.", ex); Toaster.with(SearchActivity.this).message(R.string.failedAddingDownload).show(); return; } AnalyticsApplication.sendAnalytics(Utils.ACTION_SEARCH_DOWNLOAD); })); showDialog(dialog); } @Override public boolean onMenuItemActionExpand(MenuItem menuItem) { return true; } @Override public boolean onMenuItemActionCollapse(MenuItem menuItem) { onClose(); return true; } }