diff options
Diffstat (limited to 'app/src/main/java/de')
33 files changed, 1000 insertions, 1155 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java deleted file mode 100644 index f85a1cd77..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java +++ /dev/null @@ -1,218 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.ComponentName; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.ParcelFileDescriptor; -import com.google.android.material.snackbar.Snackbar; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; -import android.view.MenuItem; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.FileChannel; -import java.util.Arrays; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.PodDBAdapter; - -/** - * Displays the 'import/export' screen - */ -public class ImportExportActivity extends AppCompatActivity { - private static final int REQUEST_CODE_RESTORE = 43; - private static final int REQUEST_CODE_BACKUP_DOCUMENT = 44; - private static final String EXPORT_FILENAME = "AntennaPodBackup.db"; - private static final String TAG = ImportExportActivity.class.getSimpleName(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayShowHomeEnabled(true); - } - setContentView(R.layout.import_export_activity); - - findViewById(R.id.button_export).setOnClickListener(view -> backup()); - findViewById(R.id.button_import).setOnClickListener(view -> restore()); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void backup() { - if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType("application/x-sqlite3") - .putExtra(Intent.EXTRA_TITLE, EXPORT_FILENAME); - - startActivityForResult(intent, REQUEST_CODE_BACKUP_DOCUMENT); - } else { - try { - File sd = Environment.getExternalStorageDirectory(); - File backupDB = new File(sd, EXPORT_FILENAME); - writeBackupTo(new FileOutputStream(backupDB)); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } - } - } - - private void restore() { - if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - startActivityForResult(intent, REQUEST_CODE_RESTORE); - } else { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - startActivityForResult(Intent.createChooser(intent, - getString(R.string.import_select_file)), REQUEST_CODE_RESTORE); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent resultData) { - if (resultCode != RESULT_OK || resultData == null) { - return; - } - Uri uri = resultData.getData(); - - if (requestCode == REQUEST_CODE_RESTORE) { - restoreFrom(uri); - } else if (requestCode == REQUEST_CODE_BACKUP_DOCUMENT) { - backupToDocument(uri); - } - } - - private void restoreFrom(Uri inputUri) { - InputStream inputStream = null; - try { - if (!validateDB(inputUri)) { - displayBadFileDialog(); - return; - } - - File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME); - inputStream = getContentResolver().openInputStream(inputUri); - FileUtils.copyInputStreamToFile(inputStream, currentDB); - displayImportSuccessDialog(); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - - private static final byte[] SQLITE3_MAGIC = "SQLite format 3\0".getBytes(); - private boolean validateDB(Uri inputUri) throws IOException { - try (InputStream inputStream = getContentResolver().openInputStream(inputUri)) { - byte[] magicBuf = new byte[SQLITE3_MAGIC.length]; - if (inputStream.read(magicBuf) == magicBuf.length) { - return Arrays.equals(SQLITE3_MAGIC, magicBuf); - } - } - - return false; - } - - private void displayBadFileDialog() { - AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this); - d.setMessage(R.string.import_bad_file) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, ((dialogInterface, i) -> { - // do nothing - })) - .show(); - } - - private void displayImportSuccessDialog() { - AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this); - d.setMessage(R.string.import_ok); - d.setCancelable(false); - d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { - Intent intent = new Intent(getApplicationContext(), SplashActivity.class); - ComponentName cn = intent.getComponent(); - Intent mainIntent = Intent.makeRestartActivityTask(cn); - startActivity(mainIntent); - }); - d.show(); - } - - private void backupToDocument(Uri uri) { - ParcelFileDescriptor pfd = null; - FileOutputStream fileOutputStream = null; - try { - pfd = getContentResolver().openFileDescriptor(uri, "w"); - fileOutputStream = new FileOutputStream(pfd.getFileDescriptor()); - writeBackupTo(fileOutputStream); - - Snackbar.make(findViewById(R.id.import_export_layout), - R.string.export_ok, Snackbar.LENGTH_SHORT).show(); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(fileOutputStream); - - if (pfd != null) { - try { - pfd.close(); - } catch (IOException e) { - Log.d(TAG, "Unable to close ParcelFileDescriptor"); - } - } - } - } - - private void writeBackupTo(FileOutputStream outFileStream) { - FileChannel src = null; - FileChannel dst = null; - try { - File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME); - - if (currentDB.exists()) { - src = new FileInputStream(currentDB).getChannel(); - dst = outFileStream.getChannel(); - dst.transferFrom(src, 0, src.size()); - - Snackbar.make(findViewById(R.id.import_export_layout), - R.string.export_ok, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.import_export_layout), - "Can not access current database", Snackbar.LENGTH_SHORT).show(); - } - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(src); - IOUtils.closeQuietly(dst); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index fab84078e..62fd4b515 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -111,20 +111,13 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi private Toolbar toolbar; private ExternalPlayerFragment externalPlayerFragment; private DrawerLayout drawerLayout; - private View navDrawer; private ListView navList; private NavListAdapter navAdapter; private int mPosition = -1; - private ActionBarDrawerToggle drawerToggle; - private CharSequence currentTitle; - - private ProgressDialog pd; - private Disposable disposable; - private long lastBackButtonPressTime = 0; @NonNull @@ -282,10 +275,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); } - public List<Feed> getFeeds() { - return (navDrawerData != null) ? navDrawerData.feeds : null; - } - private void loadFragment(int index, Bundle args) { Log.d(TAG, "loadFragment(index: " + index + ", args: " + args + ")"); if (index < navAdapter.getSubscriptionOffset()) { @@ -518,9 +507,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi if (disposable != null) { disposable.dispose(); } - if(pd != null) { - pd.dismiss(); - } } @@ -849,9 +835,4 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi super.onNewIntent(intent); setIntent(intent); } - - @VisibleForTesting - public void updateNavDrawer() { - navAdapter.notifyDataSetChanged(); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 538ed1231..a5ae5e58c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -358,10 +358,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite); } - boolean sleepTimerSet = controller.sleepTimerActive(); - boolean sleepTimerNotSet = controller.sleepTimerNotActive(); - menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet); - menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet); + menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive()); + menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive()); if (this instanceof AudioplayerActivity) { int[] attrs = {R.attr.action_bar_icon_color}; @@ -422,30 +420,9 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements .show(); } break; - case R.id.disable_sleeptimer_item: - if (controller.serviceAvailable()) { - - new AlertDialog.Builder(this) - .setTitle(R.string.sleep_timer_label) - .setMessage(getString(R.string.time_left_label) - + Converter.getDurationStringLong((int) controller - .getSleepTimerTimeLeft())) - .setPositiveButton(R.string.disable_sleeptimer_label, (dialog, which) - -> controller.disableSleepTimer()) - .setNegativeButton(R.string.cancel_label, null) - .show(); - } - break; + case R.id.disable_sleeptimer_item: // Fall-through case R.id.set_sleeptimer_item: - if (controller.serviceAvailable()) { - SleepTimerDialog td = new SleepTimerDialog(this) { - @Override - public void onTimerSet(long millis, boolean shakeToReset, boolean vibrate) { - controller.setSleepTimer(millis, shakeToReset, vibrate); - } - }; - td.createNewDialog().show(); - } + new SleepTimerDialog().show(getSupportFragmentManager(), "SleepTimerDialog"); break; case R.id.audio_controls: boolean isPlayingVideo = controller.getMedia().getMediaType() == MediaType.VIDEO; @@ -793,7 +770,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements private float prog; @Override - public void onProgressChanged (SeekBar seekBar,int progress, boolean fromUser) { + public void onProgressChanged(SeekBar seekBar,int progress, boolean fromUser) { if (controller == null || txtvLength == null) { return; } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java index 21f4ad5be..75819425c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java @@ -28,8 +28,6 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; -import com.viewpagerindicator.CirclePageIndicator; - import java.util.List; import de.danoeh.antennapod.R; @@ -38,6 +36,7 @@ import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.MessageEvent; +import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -59,6 +58,7 @@ import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; import de.danoeh.antennapod.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.view.PagerIndicatorView; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -102,6 +102,7 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem private int mPosition = -1; private ViewPager pager; + private PagerIndicatorView pageIndicator; private MediaplayerInfoPagerAdapter pagerAdapter; private Disposable disposable; @@ -261,8 +262,10 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem pager.setOffscreenPageLimit(3); pagerAdapter = new MediaplayerInfoPagerAdapter(getSupportFragmentManager()); pager.setAdapter(pagerAdapter); - CirclePageIndicator pageIndicator = findViewById(R.id.page_indicator); + pageIndicator = findViewById(R.id.page_indicator); pageIndicator.setViewPager(pager); + pageIndicator.setOnClickListener(v + -> pager.setCurrentItem((pager.getCurrentItem() + 1) % pager.getChildCount())); loadLastFragment(); pager.onSaveInstanceState(); @@ -270,6 +273,16 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem } @Override + boolean loadMediaInfo() { + if (controller != null && controller.getMedia() != null) { + List<Chapter> chapters = controller.getMedia().getChapters(); + boolean hasChapters = chapters != null && !chapters.isEmpty(); + pageIndicator.setDisabledPage(hasChapters ? -1 : 2); + } + return super.loadMediaInfo(); + } + + @Override protected void onReloadNotification(int notificationCode) { if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) { Log.d(TAG, "ReloadNotification received, switching to Videoplayer now"); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 50a8d0965..adb127acb 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -14,7 +14,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; -import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; @@ -48,6 +47,7 @@ import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -126,11 +126,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (feedUrl == null) { Log.e(TAG, "feedUrl is null."); - new AlertDialog.Builder(OnlineFeedViewActivity.this). - setNeutralButton(android.R.string.ok, - (dialog, which) -> finish()). - setTitle(R.string.error_label). - setMessage(R.string.null_value_podcast_error).create().show(); + new AlertDialog.Builder(OnlineFeedViewActivity.this) + .setNeutralButton(android.R.string.ok, (dialog, which) -> finish()) + .setTitle(R.string.error_label) + .setMessage(R.string.null_value_podcast_error) + .show(); } else { Log.d(TAG, "Activity was started with url " + feedUrl); setLoadingLayout(); @@ -230,7 +230,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { url = URLChecker.prepareURL(url); feed = new Feed(url, null); if (username != null && password != null) { - feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, username, password)); + feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, username, password)); } String fileUrl = new File(getExternalCacheDir(), FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index d7a4b9517..03e6b89db 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -4,35 +4,53 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Environment; +import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.core.app.ActivityCompat; import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; - -import org.apache.commons.lang3.ArrayUtils; - -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; - +import androidx.core.app.ActivityCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.OpmlFeedQueuer; import de.danoeh.antennapod.asynctask.OpmlImportWorker; import de.danoeh.antennapod.core.export.opml.OpmlElement; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.LangUtils; +import org.apache.commons.lang3.ArrayUtils; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; /** - * Base activity for Opml Import - e.g. with code what to do afterwards + * Activity for Opml Import. * */ -public class OpmlImportBaseActivity extends AppCompatActivity { - +public class OpmlImportActivity extends AppCompatActivity { private static final String TAG = "OpmlImportBaseActivity"; private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; - private OpmlImportWorker importWorker; @Nullable private Uri uri; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + Uri uri = getIntent().getData(); + if (uri != null && uri.toString().startsWith("/")) { + uri = Uri.parse("file://" + uri.toString()); + } else { + String extraText = getIntent().getStringExtra(Intent.EXTRA_TEXT); + if (extraText != null) { + uri = Uri.parse(extraText); + } + } + importUri(uri); + } + /** * Handles the choices made by the user in the OpmlFeedChooserActivity and * starts the OpmlFeedQueuer if necessary. @@ -42,9 +60,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { Log.d(TAG, "Received result"); if (resultCode == RESULT_CANCELED) { Log.d(TAG, "Activity was cancelled"); - if (finishWhenCanceled()) { - finish(); - } + finish(); } else { int[] selected = data.getIntArrayExtra(OpmlFeedChooserActivity.EXTRA_SELECTED_ITEMS); if (selected != null && selected.length > 0) { @@ -53,7 +69,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { @Override protected void onPostExecute(Void result) { super.onPostExecute(result); - Intent intent = new Intent(OpmlImportBaseActivity.this, MainActivity.class); + Intent intent = new Intent(OpmlImportActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); @@ -68,7 +84,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { } void importUri(@Nullable Uri uri) { - if(uri == null) { + if (uri == null) { new AlertDialog.Builder(this) .setMessage(R.string.opml_import_error_no_file) .setPositiveButton(android.R.string.ok, null) @@ -76,9 +92,10 @@ public class OpmlImportBaseActivity extends AppCompatActivity { return; } this.uri = uri; - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { - int permission = ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { + int permission = ActivityCompat.checkSelfPermission(this, + android.Manifest.permission.READ_EXTERNAL_STORAGE); if (permission != PackageManager.PERMISSION_GRANTED) { requestPermission(); return; @@ -93,9 +110,8 @@ public class OpmlImportBaseActivity extends AppCompatActivity { } @Override - public void onRequestPermissionsResult(int requestCode, - String[] permissions, - int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { if (requestCode != PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { return; } @@ -113,8 +129,8 @@ public class OpmlImportBaseActivity extends AppCompatActivity { /** Starts the import process. */ private void startImport() { try { - Reader mReader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); - importWorker = new OpmlImportWorker(this, mReader) { + Reader reader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); + OpmlImportWorker importWorker = new OpmlImportWorker(this, reader) { @Override protected void onPostExecute(ArrayList<OpmlElement> result) { @@ -123,7 +139,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { Log.d(TAG, "Parsing was successful"); OpmlImportHolder.setReadElements(result); startActivityForResult(new Intent( - OpmlImportBaseActivity.this, + OpmlImportActivity.this, OpmlFeedChooserActivity.class), 0); } else { Log.d(TAG, "Parser error occurred"); @@ -140,10 +156,4 @@ public class OpmlImportBaseActivity extends AppCompatActivity { .show(); } } - - boolean finishWhenCanceled() { - return false; - } - - } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java deleted file mode 100644 index 557510808..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; - -import de.danoeh.antennapod.core.preferences.UserPreferences; - -/** - * Lets the user start the OPML-import process. - */ -public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { - - private static final String TAG = "OpmlImportFromIntentAct"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - Uri uri = getIntent().getData(); - if (uri != null && uri.toString().startsWith("/")) { - uri = Uri.parse("file://" + uri.toString()); - } else { - String extraText = getIntent().getStringExtra(Intent.EXTRA_TEXT); - if(extraText != null) { - uri = Uri.parse(extraText); - } - } - importUri(uri); - } - - @Override - protected boolean finishWhenCanceled() { - return true; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java deleted file mode 100644 index 158e3b7a4..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java +++ /dev/null @@ -1,111 +0,0 @@ -package de.danoeh.antennapod.activity; - -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.Button; -import android.widget.TextView; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.StorageUtils; - -/** - * Lets the user start the OPML-import process from a path - */ -public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { - - private static final String TAG = "OpmlImportFromPathAct"; - - private static final int CHOOSE_OPML_FILE = 1; - - private Intent intentGetContentAction; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.opml_import); - - final TextView txtvHeaderExplanation = findViewById(R.id.txtvHeadingExplanation); - final TextView txtvExplanation = findViewById(R.id.txtvExplanation); - final TextView txtvHeaderExplanationOpenWith = findViewById(R.id.txtvHeadingExplanationOpenWith); - - Button butChooseFilesystem = findViewById(R.id.butChooseFileFromFilesystem); - butChooseFilesystem.setOnClickListener(v -> chooseFileFromExternal()); - - int nextOption = 1; - String optionLabel = getString(R.string.opml_import_option); - intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); - intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); - intentGetContentAction.setType("*/*"); - - if (IntentUtils.isCallable(getApplicationContext(), intentGetContentAction)) { - txtvHeaderExplanation.setText(String.format(optionLabel, nextOption)); - nextOption++; - } else { - txtvHeaderExplanation.setVisibility(View.GONE); - txtvExplanation.setVisibility(View.GONE); - findViewById(R.id.divider).setVisibility(View.GONE); - butChooseFilesystem.setVisibility(View.GONE); - } - - txtvHeaderExplanationOpenWith.setText(String.format(optionLabel, nextOption)); - } - - @Override - protected void onResume() { - super.onResume(); - StorageUtils.checkStorageAvailability(this); - } - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return false; - } - } - - private void chooseFileFromExternal() { - try { - startActivityForResult(intentGetContentAction, CHOOSE_OPML_FILE); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - } - - /** - * Gets the path of the file chosen with chooseFileToImport() - */ - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK && requestCode == CHOOSE_OPML_FILE) { - Uri uri = data.getData(); - if(uri != null && uri.toString().startsWith("/")) { - uri = Uri.parse("file://" + uri.toString()); - } - importUri(uri); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index a0f9bf6d8..dd91b7e2a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -16,6 +16,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.fragment.preferences.AutoDownloadPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.GpodderPreferencesFragment; +import de.danoeh.antennapod.fragment.preferences.ImportExportPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.IntegrationsPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.MainPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.NetworkPreferencesFragment; @@ -59,6 +60,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe prefFragment = new NetworkPreferencesFragment(); } else if (screen == R.xml.preferences_storage) { prefFragment = new StoragePreferencesFragment(); + } else if (screen == R.xml.preferences_import_export) { + prefFragment = new ImportExportPreferencesFragment(); } else if (screen == R.xml.preferences_autodownload) { prefFragment = new AutoDownloadPreferencesFragment(); } else if (screen == R.xml.preferences_gpodder) { @@ -79,6 +82,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe return R.string.playback_pref; case R.xml.preferences_storage: return R.string.storage_pref; + case R.xml.preferences_import_export: + return R.string.import_export_pref; case R.xml.preferences_user_interface: return R.string.user_interface_label; case R.xml.preferences_integrations: diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java index 0c6ae2645..8fb7aa73f 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -98,7 +98,7 @@ public class VideoplayerActivity extends MediaplayerActivity { } @Override - public void onUserLeaveHint () { + public void onUserLeaveHint() { if (!PictureInPictureUtil.isInPictureInPictureMode(this) && UserPreferences.getVideoBackgroundBehavior() == UserPreferences.VideoBackgroundBehavior.PICTURE_IN_PICTURE) { compatEnterPictureInPicture(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java index 227eea6e0..c49d2f39d 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java @@ -3,10 +3,12 @@ package de.danoeh.antennapod.adapter; import android.content.Context; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.view.PieChartView; +import java.util.List; + /** * Adapter for the download statistics list. */ @@ -27,17 +29,17 @@ public class DownloadStatisticsListAdapter extends StatisticsListAdapter { } @Override - PieChartView.PieChartData generateChartData(DBReader.StatisticsData statisticsData) { - float[] dataValues = new float[statisticsData.feeds.size()]; - for (int i = 0; i < statisticsData.feeds.size(); i++) { - DBReader.StatisticsItem item = statisticsData.feeds.get(i); + PieChartView.PieChartData generateChartData(List<StatisticsItem> statisticsData) { + float[] dataValues = new float[statisticsData.size()]; + for (int i = 0; i < statisticsData.size(); i++) { + StatisticsItem item = statisticsData.get(i); dataValues[i] = item.totalDownloadSize; } return new PieChartView.PieChartData(dataValues); } @Override - void onBindFeedViewHolder(StatisticsHolder holder, DBReader.StatisticsItem item) { + void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem item) { holder.value.setText(Converter.byteToString(item.totalDownloadSize)); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java index 8471569d3..c5a73c53e 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java @@ -4,10 +4,12 @@ import android.content.Context; import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.view.PieChartView; +import java.util.List; + /** * Adapter for the playback statistics list. */ @@ -34,17 +36,17 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { } @Override - PieChartView.PieChartData generateChartData(DBReader.StatisticsData statisticsData) { - float[] dataValues = new float[statisticsData.feeds.size()]; - for (int i = 0; i < statisticsData.feeds.size(); i++) { - DBReader.StatisticsItem item = statisticsData.feeds.get(i); + PieChartView.PieChartData generateChartData(List<StatisticsItem> statisticsData) { + float[] dataValues = new float[statisticsData.size()]; + for (int i = 0; i < statisticsData.size(); i++) { + StatisticsItem item = statisticsData.get(i); dataValues[i] = countAll ? item.timePlayedCountAll : item.timePlayed; } return new PieChartView.PieChartData(dataValues); } @Override - void onBindFeedViewHolder(StatisticsHolder holder, DBReader.StatisticsItem statsItem) { + void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem statsItem) { long time = countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed; holder.value.setText(Converter.shortLocalizedDuration(context, time)); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java index db65190f2..5f019d1db 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java @@ -14,9 +14,11 @@ import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.view.PieChartView; +import java.util.List; + /** * Parent Adapter for the playback and download statistics list. */ @@ -24,7 +26,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle private static final int TYPE_HEADER = 0; private static final int TYPE_FEED = 1; final Context context; - private DBReader.StatisticsData statisticsData; + private List<StatisticsItem> statisticsData; PieChartView.PieChartData pieChartData; StatisticsListAdapter(Context context) { @@ -33,14 +35,14 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle @Override public int getItemCount() { - return statisticsData.feeds.size() + 1; + return statisticsData.size() + 1; } - public DBReader.StatisticsItem getItem(int position) { + public StatisticsItem getItem(int position) { if (position == 0) { return null; } - return statisticsData.feeds.get(position - 1); + return statisticsData.get(position - 1); } @Override @@ -69,7 +71,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle holder.totalTime.setText(getHeaderValue()); } else { StatisticsHolder holder = (StatisticsHolder) h; - DBReader.StatisticsItem statsItem = statisticsData.feeds.get(position - 1); + StatisticsItem statsItem = statisticsData.get(position - 1); Glide.with(context) .load(statsItem.feed.getImageLocation()) .apply(new RequestOptions() @@ -86,8 +88,8 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle } } - public void update(DBReader.StatisticsData statistics) { - this.statisticsData = statistics; + public void update(List<StatisticsItem> statistics) { + statisticsData = statistics; pieChartData = generateChartData(statistics); notifyDataSetChanged(); } @@ -122,7 +124,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle abstract String getHeaderValue(); - abstract PieChartView.PieChartData generateChartData(DBReader.StatisticsData statisticsData); + abstract PieChartView.PieChartData generateChartData(List<StatisticsItem> statisticsData); - abstract void onBindFeedViewHolder(StatisticsHolder holder, DBReader.StatisticsItem item); + abstract void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem item); } diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java index 339a98dfa..3ac05e842 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java @@ -45,7 +45,7 @@ public class DocumentFileExportWorker { throw new IOException(); } writer = new OutputStreamWriter(outputStream, LangUtils.UTF_8); - exportWriter.writeDocument(DBReader.getFeedList(), writer); + exportWriter.writeDocument(DBReader.getFeedList(), writer, context); subscriber.onNext(output); } catch (IOException e) { subscriber.onError(e); diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java index 40b101ddf..f81a52402 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.asynctask; +import android.content.Context; import androidx.annotation.NonNull; import android.util.Log; @@ -25,15 +26,17 @@ public class ExportWorker { private final @NonNull ExportWriter exportWriter; private final @NonNull File output; + private final Context context; - public ExportWorker(@NonNull ExportWriter exportWriter) { + public ExportWorker(@NonNull ExportWriter exportWriter, Context context) { this(exportWriter, new File(UserPreferences.getDataFolder(EXPORT_DIR), - DEFAULT_OUTPUT_NAME + "." + exportWriter.fileExtension())); + DEFAULT_OUTPUT_NAME + "." + exportWriter.fileExtension()), context); } - private ExportWorker(@NonNull ExportWriter exportWriter, @NonNull File output) { + private ExportWorker(@NonNull ExportWriter exportWriter, @NonNull File output, Context context) { this.exportWriter = exportWriter; this.output = output; + this.context = context; } public Observable<File> exportObservable() { @@ -45,7 +48,7 @@ public class ExportWorker { OutputStreamWriter writer = null; try { writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8); - exportWriter.writeDocument(DBReader.getFeedList(), writer); + exportWriter.writeDocument(DBReader.getFeedList(), writer, context); subscriber.onNext(output); } catch (IOException e) { subscriber.onError(e); diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java index b88b58537..e037eb392 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -16,84 +16,78 @@ import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.export.opml.OpmlElement; import de.danoeh.antennapod.core.export.opml.OpmlReader; -public class OpmlImportWorker extends - AsyncTask<Void, Void, ArrayList<OpmlElement>> { - private static final String TAG = "OpmlImportWorker"; +public class OpmlImportWorker extends AsyncTask<Void, Void, ArrayList<OpmlElement>> { + private static final String TAG = "OpmlImportWorker"; - private final Context context; - private Exception exception; + private final Context context; + private Exception exception; + private ProgressDialog progDialog; - private ProgressDialog progDialog; - - private final Reader mReader; + private final Reader reader; public OpmlImportWorker(Context context, Reader reader) { super(); this.context = context; - this.mReader=reader; + this.reader = reader; } - @Override - protected ArrayList<OpmlElement> doInBackground(Void... params) { - Log.d(TAG, "Starting background work"); + @Override + protected ArrayList<OpmlElement> doInBackground(Void... params) { + Log.d(TAG, "Starting background work"); - if (mReader==null) { + if (reader == null) { return null; } - OpmlReader opmlReader = new OpmlReader(); - try { - ArrayList<OpmlElement> result = opmlReader.readDocument(mReader); - mReader.close(); - return result; - } catch (XmlPullParserException e) { - e.printStackTrace(); - exception = e; - return null; - } catch (IOException e) { - e.printStackTrace(); - exception = e; - return null; - } - - } - - @Override - protected void onPostExecute(ArrayList<OpmlElement> result) { - if (mReader != null) { + OpmlReader opmlReader = new OpmlReader(); + try { + ArrayList<OpmlElement> result = opmlReader.readDocument(reader); + reader.close(); + return result; + } catch (XmlPullParserException e) { + e.printStackTrace(); + exception = e; + return null; + } catch (IOException e) { + e.printStackTrace(); + exception = e; + return null; + } + + } + + @Override + protected void onPostExecute(ArrayList<OpmlElement> result) { + if (reader != null) { try { - mReader.close(); + reader.close(); } catch (IOException e) { e.printStackTrace(); } } - progDialog.dismiss(); - if (exception != null) { - Log.d(TAG, "An error occurred while trying to parse the opml document"); - AlertDialog.Builder alert = new AlertDialog.Builder(context); - alert.setTitle(R.string.error_label); - alert.setMessage(context.getString(R.string.opml_reader_error) - + exception.getMessage()); - alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.create().show(); - } - } - - @Override - protected void onPreExecute() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.reading_opml_label)); - progDialog.setIndeterminate(true); - progDialog.setCancelable(false); - progDialog.show(); - } - - public boolean wasSuccessful() { - return exception != null; - } - - public void executeAsync() { - executeOnExecutor(THREAD_POOL_EXECUTOR); - } + progDialog.dismiss(); + if (exception != null) { + Log.d(TAG, "An error occurred while trying to parse the opml document"); + AlertDialog.Builder alert = new AlertDialog.Builder(context); + alert.setTitle(R.string.error_label); + alert.setMessage(context.getString(R.string.opml_reader_error) + + exception.getMessage()); + alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.create().show(); + } + } + + @Override + protected void onPreExecute() { + progDialog = new ProgressDialog(context); + progDialog.setMessage(context.getString(R.string.please_wait)); + progDialog.setIndeterminate(true); + progDialog.setCancelable(false); + progDialog.show(); + } + + public void executeAsync() { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java index 34b102ca8..cbba1637f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -245,7 +245,7 @@ public class EpisodesApplyActionFragment extends Fragment { } @Override - public void onPrepareOptionsMenu (Menu menu) { + public void onPrepareOptionsMenu(Menu menu) { // Prepare icon for select toggle button int[] icon = new int[1]; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java index 8d176c708..d36f97c7a 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -1,67 +1,101 @@ package de.danoeh.antennapod.dialog; +import android.app.Dialog; import android.content.Context; +import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; - +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; -import org.greenrobot.eventbus.EventBus; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; -public abstract class SleepTimerDialog { - - private static final String TAG = SleepTimerDialog.class.getSimpleName(); +import java.util.concurrent.TimeUnit; - private final Context context; +public class SleepTimerDialog extends DialogFragment { + private PlaybackController controller; + private Disposable timeUpdater; - private AlertDialog dialog; private EditText etxtTime; private Spinner spTimeUnit; private CheckBox cbShakeToReset; private CheckBox cbVibrate; private CheckBox chAutoEnable; + private LinearLayout timeSetup; + private LinearLayout timeDisplay; + private TextView time; + public SleepTimerDialog() { - protected SleepTimerDialog(Context context) { - this.context = context; } - public AlertDialog createNewDialog() { - View content = View.inflate(context, R.layout.time_dialog, null); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.set_sleeptimer_label); - builder.setView(content); - builder.setNegativeButton(R.string.cancel_label, (dialog, which) -> dialog.dismiss()); - builder.setPositiveButton(R.string.set_sleeptimer_label, (dialog, which) -> { - try { - savePreferences(); - long input = SleepTimerPreferences.timerMillis(); - onTimerSet(input, cbShakeToReset.isChecked(), cbVibrate.isChecked()); - dialog.dismiss(); - } catch (NumberFormatException e) { - e.printStackTrace(); - Toast toast = Toast.makeText(context, R.string.time_dialog_invalid_input, - Toast.LENGTH_LONG); - toast.show(); + @Override + public void onStart() { + super.onStart(); + controller = new PlaybackController(getActivity(), false) { + @Override + public void setupGUI() { + updateTime(); } - }); - dialog = builder.create(); + + @Override + public void onSleepTimerUpdate() { + updateTime(); + } + }; + controller.init(); + timeUpdater = Observable.interval(1, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(tick -> updateTime()); + } + + @Override + public void onStop() { + super.onStop(); + if (controller != null) { + controller.release(); + } + if (timeUpdater != null) { + timeUpdater.dispose(); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + View content = View.inflate(getContext(), R.layout.time_dialog, null); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(R.string.sleep_timer_label); + builder.setView(content); + builder.setPositiveButton(android.R.string.ok, null); etxtTime = content.findViewById(R.id.etxtTime); spTimeUnit = content.findViewById(R.id.spTimeUnit); cbShakeToReset = content.findViewById(R.id.cbShakeToReset); cbVibrate = content.findViewById(R.id.cbVibrate); chAutoEnable = content.findViewById(R.id.chAutoEnable); + timeSetup = content.findViewById(R.id.timeSetup); + timeDisplay = content.findViewById(R.id.timeDisplay); + time = content.findViewById(R.id.time); + AlertDialog dialog = builder.create(); etxtTime.setText(SleepTimerPreferences.lastTimerValue()); etxtTime.addTextChangedListener(new TextWatcher() { @Override @@ -78,15 +112,15 @@ public abstract class SleepTimerDialog { } }); etxtTime.postDelayed(() -> { - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT); }, 100); String[] spinnerContent = new String[] { - context.getString(R.string.time_seconds), - context.getString(R.string.time_minutes), - context.getString(R.string.time_hours) }; - ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(context, + getString(R.string.time_seconds), + getString(R.string.time_minutes), + getString(R.string.time_hours) }; + ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, spinnerContent); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spTimeUnit.setAdapter(spinnerAdapter); @@ -96,16 +130,33 @@ public abstract class SleepTimerDialog { cbVibrate.setChecked(SleepTimerPreferences.vibrate()); chAutoEnable.setChecked(SleepTimerPreferences.autoEnable()); - chAutoEnable.setOnCheckedChangeListener((compoundButton, isChecked) -> { - SleepTimerPreferences.setAutoEnable(isChecked); - int messageString = isChecked ? R.string.sleep_timer_enabled_label : R.string.sleep_timer_disabled_label; - EventBus.getDefault().post(new MessageEvent(context.getString(messageString))); + chAutoEnable.setOnCheckedChangeListener((compoundButton, isChecked) + -> SleepTimerPreferences.setAutoEnable(isChecked)); + Button disableButton = content.findViewById(R.id.disableSleeptimerButton); + disableButton.setOnClickListener(v -> { + if (controller != null) { + controller.disableSleepTimer(); + } + }); + Button setButton = content.findViewById(R.id.setSleeptimerButton); + setButton.setOnClickListener(v -> { + if (!PlaybackService.isRunning) { + Toast.makeText(getContext(), R.string.no_media_playing_label, Toast.LENGTH_LONG).show(); + } + try { + savePreferences(); + long time = SleepTimerPreferences.timerMillis(); + if (controller != null) { + controller.setSleepTimer(time, cbShakeToReset.isChecked(), cbVibrate.isChecked()); + } + } catch (NumberFormatException e) { + e.printStackTrace(); + Toast.makeText(getContext(), R.string.time_dialog_invalid_input, Toast.LENGTH_LONG).show(); + } }); return dialog; } - public abstract void onTimerSet(long millis, boolean shakeToReset, boolean vibrate); - private void savePreferences() { SleepTimerPreferences.setLastTimer(etxtTime.getText().toString(), spTimeUnit.getSelectedItemPosition()); @@ -114,4 +165,12 @@ public abstract class SleepTimerDialog { SleepTimerPreferences.setAutoEnable(chAutoEnable.isChecked()); } + private void updateTime() { + if (controller == null) { + return; + } + timeSetup.setVisibility(controller.sleepTimerActive() ? View.GONE : View.VISIBLE); + timeDisplay.setVisibility(controller.sleepTimerActive() ? View.VISIBLE : View.GONE); + time.setText(Converter.getDurationStringLong((int) controller.getSleepTimerTimeLeft())); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index 2cfe7c1e8..343cf76ab 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -1,7 +1,11 @@ package de.danoeh.antennapod.fragment; +import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.util.Log; import androidx.fragment.app.Fragment; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -14,7 +18,7 @@ import android.widget.EditText; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; +import de.danoeh.antennapod.activity.OpmlImportActivity; import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment; /** @@ -28,6 +32,7 @@ public class AddFeedFragment extends Fragment { * Preset value for url text field. */ private static final String ARG_FEED_URL = "feedurl"; + private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; private EditText combinedFeedSearchBox; private MainActivity activity; @@ -44,8 +49,16 @@ public class AddFeedFragment extends Fragment { setupSeachBox(root); View butOpmlImport = root.findViewById(R.id.btn_opml_import); - butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(), - OpmlImportFromPathActivity.class))); + butOpmlImport.setOnClickListener(v -> { + try { + Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); + intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); + intentGetContentAction.setType("*/*"); + startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + }); root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch()); return root; } @@ -130,4 +143,18 @@ public class AddFeedFragment extends Fragment { // persist. mfietz thinks this causes the ActionBar to be invalidated setHasOptionsMenu(true); } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + Uri uri = data.getData(); + + if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { + Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index bb1f8f8e9..63160bb2c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -3,16 +3,19 @@ package de.danoeh.antennapod.fragment; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; -import androidx.preference.SwitchPreference; +import android.util.Log; import androidx.preference.ListPreference; import androidx.preference.PreferenceFragmentCompat; -import android.util.Log; +import androidx.preference.SwitchPreference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; +import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedFilter; import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; @@ -23,6 +26,8 @@ import io.reactivex.MaybeOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; + import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; @@ -32,7 +37,8 @@ import static de.danoeh.antennapod.core.feed.FeedPreferences.SPEED_USE_GLOBAL; public class FeedSettingsFragment extends PreferenceFragmentCompat { private static final CharSequence PREF_EPISODE_FILTER = "episodeFilter"; private static final String PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"; - private static final DecimalFormat decimalFormat = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); + private static final DecimalFormat SPEED_FORMAT = + new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; private static final String TAG = "FeedSettingsFragment"; @@ -73,11 +79,13 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { setupAutoDownloadPreference(); setupKeepUpdatedPreference(); setupAutoDeletePreference(); + setupVolumeReductionPreferences(); setupAuthentificationPreference(); setupEpisodeFilterPreference(); setupPlaybackSpeedPreference(); updateAutoDeleteSummary(); + updateVolumeReductionValue(); updateAutoDownloadEnabled(); updatePlaybackSpeedPreference(); }, error -> Log.d(TAG, Log.getStackTraceString(error)), () -> { }); @@ -112,7 +120,7 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { String[] speeds = UserPreferences.getPlaybackSpeedArray(); String[] values = new String[speeds.length + 1]; - values[0] = decimalFormat.format(SPEED_USE_GLOBAL); + values[0] = SPEED_FORMAT.format(SPEED_USE_GLOBAL); String[] entries = new String[speeds.length + 1]; entries[0] = getString(R.string.feed_auto_download_global); @@ -122,11 +130,12 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { feedPlaybackSpeedPreference.setEntryValues(values); feedPlaybackSpeedPreference.setEntries(entries); - feedPlaybackSpeedPreference.setOnPreferenceChangeListener((preference, newValue) -> { feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) newValue)); feed.savePreferences(); updatePlaybackSpeedPreference(); + EventBus.getDefault().post( + new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId())); return false; }); } @@ -184,7 +193,7 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); float speedValue = feedPreferences.getFeedPlaybackSpeed(); - feedPlaybackSpeedPreference.setValue(decimalFormat.format(speedValue)); + feedPlaybackSpeedPreference.setValue(SPEED_FORMAT.format(speedValue)); } private void updateAutoDeleteSummary() { @@ -206,6 +215,44 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { } } + private void setupVolumeReductionPreferences() { + ListPreference volumeReductionPreference = (ListPreference) findPreference("volumeReduction"); + volumeReductionPreference.setOnPreferenceChangeListener((preference, newValue) -> { + switch ((String) newValue) { + case "off": + feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.OFF); + break; + case "light": + feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.LIGHT_REDUCTION); + break; + case "heavy": + feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.HEAVY_REDUCTION); + break; + } + feed.savePreferences(); + updateVolumeReductionValue(); + EventBus.getDefault().post( + new VolumeAdaptionChangedEvent(feedPreferences.getVolumeAdaptionSetting(), feed.getId())); + return false; + }); + } + + private void updateVolumeReductionValue() { + ListPreference volumeReductionPreference = (ListPreference) findPreference("volumeReduction"); + + switch (feedPreferences.getVolumeAdaptionSetting()) { + case OFF: + volumeReductionPreference.setValue("off"); + break; + case LIGHT_REDUCTION: + volumeReductionPreference.setValue("light"); + break; + case HEAVY_REDUCTION: + volumeReductionPreference.setValue("heavy"); + break; + } + } + private void setupKeepUpdatedPreference() { SwitchPreference pref = (SwitchPreference) findPreference("keepUpdated"); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index 85978b761..256615199 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -1,40 +1,18 @@ package de.danoeh.antennapod.fragment; -import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ClipData; -import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.Toast; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MediaplayerInfoActivity; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.ShareUtils; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; +import de.danoeh.antennapod.view.ShownotesWebView; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -44,72 +22,29 @@ import io.reactivex.schedulers.Schedulers; * Displays the description of a Playable object in a Webview. */ public class ItemDescriptionFragment extends Fragment { - private static final String TAG = "ItemDescriptionFragment"; private static final String PREF = "ItemDescriptionFragmentPrefs"; private static final String PREF_SCROLL_Y = "prefScrollY"; private static final String PREF_PLAYABLE_ID = "prefPlayableId"; - private WebView webvDescription; + private ShownotesWebView webvDescription; private Disposable webViewLoader; private PlaybackController controller; - /** - * URL that was selected via long-press. - */ - private String selectedURL; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "Creating view"); - webvDescription = new WebView(getActivity().getApplicationContext()); - webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - - TypedArray ta = getActivity().getTheme().obtainStyledAttributes(new int[] - {android.R.attr.colorBackground}); - boolean black = UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark - || UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack; - int backgroundColor = ta.getColor(0, black ? Color.BLACK : Color.WHITE); - - ta.recycle(); - webvDescription.setBackgroundColor(backgroundColor); - if (!NetworkUtils.networkAvailable()) { - webvDescription.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); - // Use cached resources, even if they have expired - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webvDescription.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - } - - webvDescription.getSettings().setUseWideViewPort(false); - webvDescription.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); - webvDescription.getSettings().setLoadWithOverviewMode(true); - webvDescription.setOnLongClickListener(webViewLongClickListener); - webvDescription.setWebViewClient(new WebViewClient() { - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (Timeline.isTimecodeLink(url)) { - onTimecodeLinkSelected(url); - } else { - IntentUtils.openInBrowser(getContext(), url); - } - return true; + webvDescription = new ShownotesWebView(getActivity().getApplicationContext()); + webvDescription.setTimecodeSelectedListener(time -> { + if (controller != null) { + controller.seekTo(time); } - - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - Log.d(TAG, "Page finished"); - // Restoring the scroll position might not always work - view.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50); - } - }); - + webvDescription.setPageFinishedListener(() -> { + // Restoring the scroll position might not always work + webvDescription.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50); + }); registerForContextMenu(webvDescription); return webvDescription; } @@ -127,91 +62,14 @@ public class ItemDescriptionFragment extends Fragment { } } - private final View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - WebView.HitTestResult r = webvDescription.getHitTestResult(); - if (r != null - && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { - Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); - selectedURL = r.getExtra(); - webvDescription.showContextMenu(); - return true; - } - selectedURL = null; - return false; - } - }; - - @SuppressLint("NewApi") @Override public boolean onContextItemSelected(MenuItem item) { - boolean handled = selectedURL != null; - if (selectedURL != null) { - switch (item.getItemId()) { - case R.id.open_in_browser_item: - IntentUtils.openInBrowser(getContext(), selectedURL); - break; - case R.id.share_url_item: - ShareUtils.shareLink(getActivity(), selectedURL); - break; - case R.id.copy_url_item: - ClipData clipData = ClipData.newPlainText(selectedURL, - selectedURL); - android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(clipData); - Toast t = Toast.makeText(getActivity(), - R.string.copied_url_msg, Toast.LENGTH_SHORT); - t.show(); - break; - case R.id.go_to_position_item: - if (Timeline.isTimecodeLink(selectedURL)) { - onTimecodeLinkSelected(selectedURL); - } else { - Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedURL); - } - break; - default: - handled = false; - break; - - } - selectedURL = null; - } - return handled; - - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - if (selectedURL != null) { - super.onCreateContextMenu(menu, v, menuInfo); - if (Timeline.isTimecodeLink(selectedURL)) { - menu.add(Menu.NONE, R.id.go_to_position_item, Menu.NONE, - R.string.go_to_position_label); - menu.setHeaderTitle(Converter.getDurationStringLong(Timeline.getTimecodeLinkTime(selectedURL))); - } else { - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, - R.string.open_in_browser_label); - } - menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, - R.string.copy_url_label); - menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, - R.string.share_url_label); - menu.setHeaderTitle(selectedURL); - } - } + return webvDescription.onContextItemSelected(item); } private void load() { Log.d(TAG, "load()"); - if(webViewLoader != null) { + if (webViewLoader != null) { webViewLoader.dispose(); } webViewLoader = Observable.fromCallable(this::loadData) @@ -227,7 +85,7 @@ public class ItemDescriptionFragment extends Fragment { @NonNull private String loadData() { Timeline timeline = new Timeline(getActivity(), controller.getMedia()); - return timeline.processShownotes(true); + return timeline.processShownotes(); } @Override @@ -238,8 +96,7 @@ public class ItemDescriptionFragment extends Fragment { private void savePreference() { Log.d(TAG, "Saving preferences"); - SharedPreferences prefs = getActivity().getSharedPreferences(PREF, - Activity.MODE_PRIVATE); + SharedPreferences prefs = getActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); if (controller != null && controller.getMedia() != null && webvDescription != null) { Log.d(TAG, "Saving scroll position: " + webvDescription.getScrollY()); @@ -251,15 +108,14 @@ public class ItemDescriptionFragment extends Fragment { editor.putInt(PREF_SCROLL_Y, -1); editor.putString(PREF_PLAYABLE_ID, ""); } - editor.commit(); + editor.apply(); } private boolean restoreFromPreference() { Log.d(TAG, "Restoring from preferences"); Activity activity = getActivity(); if (activity != null) { - SharedPreferences prefs = activity.getSharedPreferences( - PREF, Activity.MODE_PRIVATE); + SharedPreferences prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE); String id = prefs.getString(PREF_PLAYABLE_ID, ""); int scrollY = prefs.getInt(PREF_SCROLL_Y, -1); if (controller != null && scrollY != -1 && controller.getMedia() != null @@ -274,16 +130,6 @@ public class ItemDescriptionFragment extends Fragment { return false; } - private void onTimecodeLinkSelected(String link) { - int time = Timeline.getTimecodeLinkTime(link); - if (getActivity() != null && getActivity() instanceof MediaplayerInfoActivity) { - PlaybackController pc = ((MediaplayerInfoActivity) getActivity()).getPlaybackController(); - if (pc != null) { - pc.seekTo(time); - } - } - } - @Override public void onStart() { super.onStart(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index 7a3d034f1..e1202704a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -1,9 +1,7 @@ package de.danoeh.antennapod.fragment; -import android.content.ClipData; import android.content.Context; import android.content.Intent; -import android.content.res.TypedArray; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -11,31 +9,22 @@ import android.text.Layout; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; -import android.view.ContextMenu; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.AttrRes; -import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; -import com.joanzapata.iconify.Iconify; -import com.joanzapata.iconify.widget.IconButton; +import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; @@ -47,7 +36,6 @@ import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; @@ -55,10 +43,9 @@ import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; +import de.danoeh.antennapod.view.ShownotesWebView; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -99,7 +86,7 @@ public class ItemFragment extends Fragment { private List<Downloader> downloaderList; private ViewGroup root; - private WebView webvDescription; + private ShownotesWebView webvDescription; private TextView txtvPodcast; private TextView txtvTitle; private TextView txtvDuration; @@ -111,11 +98,7 @@ public class ItemFragment extends Fragment { private Button butAction2; private Disposable disposable; - - /** - * URL that was selected via long-press. - */ - private String selectedURL; + private PlaybackController controller; @Override public void onCreate(Bundle savedInstanceState) { @@ -134,7 +117,7 @@ public class ItemFragment extends Fragment { txtvPodcast = layout.findViewById(R.id.txtvPodcast); txtvPodcast.setOnClickListener(v -> openPodcast()); txtvTitle = layout.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= 23) { txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); } txtvDuration = layout.findViewById(R.id.txtvDuration); @@ -143,31 +126,11 @@ public class ItemFragment extends Fragment { txtvTitle.setEllipsize(TextUtils.TruncateAt.END); } webvDescription = layout.findViewById(R.id.webvDescription); - if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark || - UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - webvDescription.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.black)); - } - if (!NetworkUtils.networkAvailable()) { - webvDescription.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); - // Use cached resources, even if they have expired - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webvDescription.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - } - webvDescription.getSettings().setUseWideViewPort(false); - webvDescription.getSettings().setLayoutAlgorithm( - WebSettings.LayoutAlgorithm.NARROW_COLUMNS); - webvDescription.getSettings().setLoadWithOverviewMode(true); - webvDescription.setOnLongClickListener(webViewLongClickListener); - - webvDescription.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - IntentUtils.openInBrowser(getContext(), url); - return true; + webvDescription.setTimecodeSelectedListener(time -> { + if (controller != null && item.getMedia().getIdentifier().equals(controller.getMedia().getIdentifier())) { + controller.seekTo(time); + } else { + Snackbar.make(getView(), R.string.play_this_to_seek_position, Snackbar.LENGTH_LONG).show(); } }); registerForContextMenu(webvDescription); @@ -230,6 +193,8 @@ public class ItemFragment extends Fragment { public void onStart() { super.onStart(); EventBus.getDefault().register(this); + controller = new PlaybackController(getActivity(), false); + controller.init(); } @Override @@ -245,12 +210,13 @@ public class ItemFragment extends Fragment { public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); + controller.release(); } @Override public void onDestroyView() { super.onDestroyView(); - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } if (webvDescription != null && root != null) { @@ -364,76 +330,14 @@ public class ItemFragment extends Fragment { } } - private final View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - WebView.HitTestResult r = webvDescription.getHitTestResult(); - if (r != null - && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { - Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); - selectedURL = r.getExtra(); - webvDescription.showContextMenu(); - return true; - } - selectedURL = null; - return false; - } - }; - @Override public boolean onContextItemSelected(MenuItem item) { - boolean handled = selectedURL != null; - if (selectedURL != null) { - switch (item.getItemId()) { - case R.id.open_in_browser_item: - IntentUtils.openInBrowser(getContext(), selectedURL); - break; - case R.id.share_url_item: - ShareUtils.shareLink(getActivity(), selectedURL); - break; - case R.id.copy_url_item: - ClipData clipData = ClipData.newPlainText(selectedURL, - selectedURL); - android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(clipData); - Toast t = Toast.makeText(getActivity(), - R.string.copied_url_msg, Toast.LENGTH_SHORT); - t.show(); - break; - default: - handled = false; - break; - - } - selectedURL = null; - } - return handled; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenu.ContextMenuInfo menuInfo) { - if (selectedURL != null) { - super.onCreateContextMenu(menu, v, menuInfo); - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, - R.string.open_in_browser_label); - } - menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, - R.string.copy_url_label); - menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, - R.string.share_url_label); - menu.setHeaderTitle(selectedURL); - } + return webvDescription.onContextItemSelected(item); } private void openPodcast() { Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); - ((MainActivity)getActivity()).loadChildFragment(fragment); + ((MainActivity) getActivity()).loadChildFragment(fragment); } @Subscribe(threadMode = ThreadMode.MAIN) @@ -452,11 +356,11 @@ public class ItemFragment extends Fragment { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; downloaderList = update.downloaders; - if(item == null || item.getMedia() == null) { + if (item == null || item.getMedia() == null) { return; } long mediaId = item.getMedia().getId(); - if(ArrayUtils.contains(update.mediaIds, mediaId)) { + if (ArrayUtils.contains(update.mediaIds, mediaId)) { if (itemsLoaded && getActivity() != null) { updateAppearance(); } @@ -490,7 +394,7 @@ public class ItemFragment extends Fragment { Context context = getContext(); if (feedItem != null && context != null) { Timeline t = new Timeline(context, feedItem); - webviewData = t.processShownotes(false); + webviewData = t.processShownotes(); } return feedItem; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index 83c16a9ff..f421ed005 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -550,8 +550,9 @@ public class QueueFragment extends Fragment { final boolean isRead = item.isPlayed(); DBWriter.markItemPlayed(FeedItem.PLAYED, false, item.getId()); DBWriter.removeQueueItem(getActivity(), true, item); - Snackbar snackbar = Snackbar.make - (root, getString(item.hasMedia() ? R.string.marked_as_read_label: R.string.marked_as_read_no_media_label), Snackbar.LENGTH_LONG); + Snackbar snackbar = Snackbar.make(root, getString(item.hasMedia() + ? R.string.marked_as_read_label : R.string.marked_as_read_no_media_label), + Snackbar.LENGTH_LONG); snackbar.setAction(getString(R.string.undo), v -> { DBWriter.addQueueItemAt(getActivity(), item.getId(), position, false); if(!isRead) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java index 34ea6d6e3..3059d7ad2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java @@ -16,6 +16,7 @@ import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DownloadStatisticsListAdapter; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.comparator.CompareCompat; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -23,6 +24,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.util.Collections; +import java.util.List; /** * Displays the 'download statistics' screen @@ -71,8 +73,8 @@ public class DownloadStatisticsFragment extends Fragment { disposable = Observable.fromCallable(() -> { - DBReader.StatisticsData statisticsData = DBReader.getStatistics(); - Collections.sort(statisticsData.feeds, (item1, item2) -> + List<StatisticsItem> statisticsData = DBReader.getStatistics(); + Collections.sort(statisticsData, (item1, item2) -> CompareCompat.compareLong(item1.totalDownloadSize, item2.totalDownloadSize)); return statisticsData; }) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java index c6ae8e20c..1e51380ca 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java @@ -74,16 +74,14 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { dialog.show(); return true; }); - findPreference(PREF_GPODNET_SYNC). - setOnPreferenceClickListener(preference -> { + findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> { GpodnetSyncService.sendSyncIntent(getActivity().getApplicationContext()); Toast toast = Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, Toast.LENGTH_SHORT); toast.show(); return true; }); - findPreference(PREF_GPODNET_FORCE_FULL_SYNC). - setOnPreferenceClickListener(preference -> { + findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> { GpodnetPreferences.setLastSubscriptionSyncTimestamp(0L); GpodnetPreferences.setLastEpisodeActionsSyncTimestamp(0L); GpodnetPreferences.setLastSyncAttempt(false, 0); @@ -94,17 +92,16 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { toast.show(); return true; }); - findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener( - preference -> { + findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> { GpodnetPreferences.logout(); Toast toast = Toast.makeText(activity, R.string.pref_gpodnet_logout_toast, Toast.LENGTH_SHORT); toast.show(); updateGpodnetPreferenceScreen(); return true; }); - findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener( - preference -> { - GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(dialog -> updateGpodnetPreferenceScreen()); + findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> { + GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener( + dialog -> updateGpodnetPreferenceScreen()); return true; }); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java new file mode 100644 index 000000000..8036a7506 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java @@ -0,0 +1,283 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.FileProvider; +import androidx.preference.PreferenceFragmentCompat; +import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.OpmlImportActivity; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.activity.SplashActivity; +import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; +import de.danoeh.antennapod.asynctask.ExportWorker; +import de.danoeh.antennapod.core.export.ExportWriter; +import de.danoeh.antennapod.core.export.html.HtmlWriter; +import de.danoeh.antennapod.core.export.opml.OpmlWriter; +import de.danoeh.antennapod.core.storage.DatabaseExporter; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.List; + +public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { + private static final String TAG = "ImportExPrefFragment"; + private static final String PREF_OPML_EXPORT = "prefOpmlExport"; + private static final String PREF_OPML_IMPORT = "prefOpmlImport"; + private static final String PREF_HTML_EXPORT = "prefHtmlExport"; + private static final String PREF_DATABASE_IMPORT = "prefDatabaseImport"; + private static final String PREF_DATABASE_EXPORT = "prefDatabaseExport"; + private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml"; + private static final String CONTENT_TYPE_OPML = "text/x-opml"; + private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html"; + private static final String CONTENT_TYPE_HTML = "text/html"; + private static final int REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH = 1; + private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 2; + private static final int REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH = 3; + private static final int REQUEST_CODE_RESTORE_DATABASE = 4; + private static final int REQUEST_CODE_BACKUP_DATABASE = 5; + private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup.db"; + private Disposable disposable; + private ProgressDialog progressDialog; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences_import_export); + setupStorageScreen(); + progressDialog = new ProgressDialog(getContext()); + progressDialog.setIndeterminate(true); + progressDialog.setMessage(getContext().getString(R.string.please_wait)); + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.import_export_pref); + } + + @Override + public void onStop() { + super.onStop(); + if (disposable != null) { + disposable.dispose(); + } + } + + private void setupStorageScreen() { + findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( + preference -> { + openExportPathPicker(CONTENT_TYPE_OPML, DEFAULT_OPML_OUTPUT_NAME, + REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH, new OpmlWriter()); + return true; + } + ); + findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( + preference -> { + openExportPathPicker(CONTENT_TYPE_HTML, DEFAULT_HTML_OUTPUT_NAME, + REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH, new HtmlWriter()); + return true; + }); + findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( + preference -> { + try { + Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); + intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); + intentGetContentAction.setType("*/*"); + startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + return true; + }); + findPreference(PREF_DATABASE_IMPORT).setOnPreferenceClickListener( + preference -> { + importDatabase(); + return true; + }); + findPreference(PREF_DATABASE_EXPORT).setOnPreferenceClickListener( + preference -> { + exportDatabase(); + return true; + }); + } + + private void exportWithWriter(ExportWriter exportWriter, final Uri uri) { + Context context = getActivity(); + progressDialog.show(); + if (uri == null) { + Observable<File> observable = new ExportWorker(exportWriter, getContext()).exportObservable(); + disposable = observable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> { + Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), + context.getString(R.string.provider_authority), output); + showExportSuccessDialog(output.toString(), fileUri); + }, this::showExportErrorDialog, progressDialog::dismiss); + } else { + DocumentFileExportWorker worker = new DocumentFileExportWorker(exportWriter, context, uri); + disposable = worker.exportObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> + showExportSuccessDialog(output.getUri().toString(), output.getUri()), + this::showExportErrorDialog, progressDialog::dismiss); + } + } + + private void exportDatabase() { + if (Build.VERSION.SDK_INT >= 19) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("application/x-sqlite3") + .putExtra(Intent.EXTRA_TITLE, DATABASE_EXPORT_FILENAME); + + startActivityForResult(intent, REQUEST_CODE_BACKUP_DATABASE); + } else { + File sd = Environment.getExternalStorageDirectory(); + File backupDB = new File(sd, DATABASE_EXPORT_FILENAME); + progressDialog.show(); + disposable = Completable.fromAction(() -> + DatabaseExporter.exportToStream(new FileOutputStream(backupDB), getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } + } + + private void importDatabase() { + if (Build.VERSION.SDK_INT >= 19) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + startActivityForResult(intent, REQUEST_CODE_RESTORE_DATABASE); + } else { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + startActivityForResult(Intent.createChooser(intent, + getString(R.string.import_select_file)), REQUEST_CODE_RESTORE_DATABASE); + } + } + + private void showDatabaseImportSuccessDialog() { + AlertDialog.Builder d = new AlertDialog.Builder(getContext()); + d.setMessage(R.string.import_ok); + d.setCancelable(false); + d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { + Intent intent = new Intent(getContext(), SplashActivity.class); + ComponentName cn = intent.getComponent(); + Intent mainIntent = Intent.makeRestartActivityTask(cn); + startActivity(mainIntent); + }); + d.show(); + } + + private void showExportSuccessDialog(final String path, final Uri streamUri) { + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); + alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_success_title); + alert.setMessage(getContext().getString(R.string.export_success_sum, path)); + alert.setPositiveButton(R.string.send_label, (dialog, which) -> { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label)); + sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); + sendIntent.setType("text/plain"); + sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + List<ResolveInfo> resInfoList = getContext().getPackageManager() + .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } + getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); + }); + alert.create().show(); + } + + private void showExportErrorDialog(final Throwable error) { + progressDialog.dismiss(); + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); + alert.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_error_label); + alert.setMessage(error.getMessage()); + alert.show(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + Uri uri = data.getData(); + + if (requestCode == REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH) { + exportWithWriter(new OpmlWriter(), uri); + } else if (requestCode == REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH) { + exportWithWriter(new HtmlWriter(), uri); + } else if (requestCode == REQUEST_CODE_RESTORE_DATABASE) { + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + showDatabaseImportSuccessDialog(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } else if (requestCode == REQUEST_CODE_BACKUP_DATABASE) { + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.exportToDocument(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } else if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { + Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + } + + private void openExportPathPicker(String contentType, String title, int requestCode, ExportWriter writer) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { + Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(contentType) + .putExtra(Intent.EXTRA_TITLE, title); + + // Creates an implicit intent to launch a file manager which lets + // the user choose a specific directory to export to. + try { + startActivityForResult(intentPickAction, requestCode); + return; + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + } + + // If we are using a SDK lower than API 21 or the implicit intent failed + // fallback to the legacy export process + exportWithWriter(writer, null); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 5fd38d663..da82d4f8c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -103,6 +103,9 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)); config.index(R.xml.preferences_storage) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_storage)); + config.index(R.xml.preferences_import_export) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_storage)) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_import_export)); config.index(R.xml.preferences_autodownload) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)) .addBreadcrumb(R.string.automation) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java index bed767e8e..d25dff743 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java @@ -27,6 +27,7 @@ import de.danoeh.antennapod.adapter.PlaybackStatisticsListAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.comparator.CompareCompat; import io.reactivex.Completable; import io.reactivex.Observable; @@ -35,6 +36,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.util.Collections; +import java.util.List; /** * Displays the 'playback statistics' screen @@ -180,13 +182,13 @@ public class PlaybackStatisticsFragment extends Fragment { }, error -> Log.e(TAG, Log.getStackTraceString(error))); } - private DBReader.StatisticsData fetchStatistics() { - DBReader.StatisticsData statisticsData = DBReader.getStatistics(); + private List<StatisticsItem> fetchStatistics() { + List<StatisticsItem> statisticsData = DBReader.getStatistics(); if (countAll) { - Collections.sort(statisticsData.feeds, (item1, item2) -> + Collections.sort(statisticsData, (item1, item2) -> CompareCompat.compareLong(item1.timePlayedCountAll, item2.timePlayedCountAll)); } else { - Collections.sort(statisticsData.feeds, (item1, item2) -> + Collections.sort(statisticsData, (item1, item2) -> CompareCompat.compareLong(item1.timePlayed, item2.timePlayed)); } return statisticsData; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java index 2c1590c47..8a0742b7f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java @@ -1,62 +1,32 @@ package de.danoeh.antennapod.fragment.preferences; import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; -import android.app.ProgressDialog; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import androidx.core.app.ActivityCompat; -import androidx.core.content.FileProvider; -import androidx.documentfile.provider.DocumentFile; +import android.util.Log; import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceFragmentCompat; -import android.util.Log; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.DirectoryChooserActivity; -import de.danoeh.antennapod.activity.ImportExportActivity; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; -import de.danoeh.antennapod.asynctask.ExportWorker; -import de.danoeh.antennapod.core.export.ExportWriter; -import de.danoeh.antennapod.core.export.html.HtmlWriter; -import de.danoeh.antennapod.core.export.opml.OpmlWriter; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.dialog.ChooseDataFolderDialog; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; import java.io.File; -import java.util.List; public class StoragePreferencesFragment extends PreferenceFragmentCompat { private static final String TAG = "StoragePrefFragment"; - private static final String PREF_OPML_EXPORT = "prefOpmlExport"; - private static final String PREF_OPML_IMPORT = "prefOpmlImport"; - private static final String PREF_HTML_EXPORT = "prefHtmlExport"; - private static final String IMPORT_EXPORT = "importExport"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; + private static final String PREF_IMPORT_EXPORT = "prefImportExport"; private static final String[] EXTERNAL_STORAGE_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 41; - private static final int CHOOSE_OPML_EXPORT_PATH = 1; - private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml"; - private static final String CONTENT_TYPE_OPML = "text/x-opml"; - private static final int CHOOSE_HTML_EXPORT_PATH = 2; - private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html"; - private static final String CONTENT_TYPE_HTML = "text/html"; - private Disposable disposable; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -76,51 +46,20 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { setDataFolderText(); } - @Override - public void onStop() { - super.onStop(); - if (disposable != null) { - disposable.dispose(); - } - } - private void setupStorageScreen() { final Activity activity = getActivity(); - - findPreference(IMPORT_EXPORT).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, ImportExportActivity.class)); - return true; - } - ); - findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( - preference -> { - openOpmlExportPathPicker(); - return true; - } - ); - findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( - preference -> { - openHtmlExportPathPicker(); - return true; - }); - findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, OpmlImportFromPathActivity.class)); - return true; - }); findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( preference -> { - if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT && - Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { + if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { showChooseDataFolderDialog(); } else { int readPermission = ActivityCompat.checkSelfPermission( activity, Manifest.permission.READ_EXTERNAL_STORAGE); int writePermission = ActivityCompat.checkSelfPermission( activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (readPermission == PackageManager.PERMISSION_GRANTED && - writePermission == PackageManager.PERMISSION_GRANTED) { + if (readPermission == PackageManager.PERMISSION_GRANTED + && writePermission == PackageManager.PERMISSION_GRANTED) { openDirectoryChooser(); } else { requestPermission(); @@ -129,19 +68,18 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { return true; } ); - findPreference(PREF_CHOOSE_DATA_DIR) - .setOnPreferenceClickListener( - preference -> { - if (Build.VERSION.SDK_INT >= 19) { - showChooseDataFolderDialog(); - } else { - Intent intent = new Intent(activity, DirectoryChooserActivity.class); - activity.startActivityForResult(intent, - DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); - } - return true; - } - ); + findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( + preference -> { + if (Build.VERSION.SDK_INT >= 19) { + showChooseDataFolderDialog(); + } else { + Intent intent = new Intent(activity, DirectoryChooserActivity.class); + activity.startActivityForResult(intent, + DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); + } + return true; + } + ); findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE).setOnPreferenceChangeListener( (preference, o) -> { if (o instanceof String) { @@ -158,74 +96,16 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { return false; } ); - } - - private boolean export(ExportWriter exportWriter) { - return export(exportWriter, null); - } - - private boolean export(ExportWriter exportWriter, final Uri uri) { - Context context = getActivity(); - final ProgressDialog progressDialog = new ProgressDialog(context); - progressDialog.setMessage(context.getString(R.string.exporting_label)); - progressDialog.setIndeterminate(true); - progressDialog.show(); - if (uri == null) { - Observable<File> observable = new ExportWorker(exportWriter).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), - context.getString(R.string.provider_authority), output); - showExportSuccessDialog(context.getString(R.string.export_success_sum, output.toString()), fileUri); - }, this::showExportErrorDialog, progressDialog::dismiss); - } else { - Observable<DocumentFile> observable = new DocumentFileExportWorker(exportWriter, context, uri).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - showExportSuccessDialog(context.getString(R.string.export_success_sum, output.getUri()), output.getUri()); - }, this::showExportErrorDialog, progressDialog::dismiss); - } - return true; - } - - private void showExportSuccessDialog(final String message, final Uri streamUri) { - final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) - .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.setTitle(R.string.export_success_title); - alert.setMessage(message); - alert.setPositiveButton(R.string.send_label, (dialog, which) -> { - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label)); - sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); - sendIntent.setType("text/plain"); - sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - List<ResolveInfo> resInfoList = getContext().getPackageManager() - .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + findPreference(PREF_IMPORT_EXPORT).setOnPreferenceClickListener( + preference -> { + ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_import_export); + return true; } - } - getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); - }); - alert.create().show(); - } - - private void showExportErrorDialog(final Throwable error) { - final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) - .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.setTitle(R.string.export_error_label); - alert.setMessage(error.getMessage()); - alert.show(); + ); } - @SuppressLint("NewApi") public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && - requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { + if (resultCode == Activity.RESULT_OK && requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); File path; @@ -255,23 +135,12 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { ab.show(); } } - - if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_OPML_EXPORT_PATH) { - Uri uri = data.getData(); - export(new OpmlWriter(), uri); - } - - if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_HTML_EXPORT_PATH) { - Uri uri = data.getData(); - export(new HtmlWriter(), uri); - } } private void setDataFolderText() { File f = UserPreferences.getDataFolder(null); if (f != null) { - findPreference(PREF_CHOOSE_DATA_DIR) - .setSummary(f.getAbsolutePath()); + findPreference(PREF_CHOOSE_DATA_DIR).setSummary(f.getAbsolutePath()); } } @@ -286,50 +155,6 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { activity.startActivityForResult(intent, DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); } - private void openOpmlExportPathPicker() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(CONTENT_TYPE_OPML) - .putExtra(Intent.EXTRA_TITLE, DEFAULT_OPML_OUTPUT_NAME); - - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - startActivityForResult(intentPickAction, CHOOSE_OPML_EXPORT_PATH); - return; - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - } - - // If we are using a SDK lower than API 21 or the implicit intent failed - // fallback to the legacy export process - export(new OpmlWriter()); - } - - private void openHtmlExportPathPicker() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(CONTENT_TYPE_HTML) - .putExtra(Intent.EXTRA_TITLE, DEFAULT_HTML_OUTPUT_NAME); - - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - startActivityForResult(intentPickAction, CHOOSE_HTML_EXPORT_PATH); - return; - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - } - - // If we are using a SDK lower than API 21 or the implicit intent failed - // fallback to the legacy export process - export(new HtmlWriter()); - } - private void showChooseDataFolderDialog() { ChooseDataFolderDialog.showDialog( getActivity(), new ChooseDataFolderDialog.RunnableWithString() { diff --git a/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java new file mode 100644 index 000000000..60ef820a9 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java @@ -0,0 +1,105 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; +import androidx.annotation.Nullable; +import androidx.vectordrawable.graphics.drawable.ArgbEvaluator; +import androidx.viewpager.widget.ViewPager; + +public class PagerIndicatorView extends View { + private final Paint paint = new Paint(); + private float position = 0; + private int numPages = 0; + private int disabledPage = -1; + private int circleColor = 0; + private int circleColorHighlight = -1; + + public PagerIndicatorView(Context context) { + super(context); + setup(); + } + + public PagerIndicatorView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public PagerIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + private void setup() { + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.FILL); + + int[] colorAttrs = new int[] { android.R.attr.textColorSecondary }; + TypedArray a = getContext().obtainStyledAttributes(colorAttrs); + circleColorHighlight = a.getColor(0, 0xffffffff); + circleColor = (Integer) new ArgbEvaluator().evaluate(0.8f, 0x00ffffff, circleColorHighlight); + a.recycle(); + } + + public void setViewPager(ViewPager pager) { + numPages = pager.getAdapter().getCount(); + pager.getAdapter().registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + numPages = pager.getAdapter().getCount(); + invalidate(); + } + }); + pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + PagerIndicatorView.this.position = position + positionOffset; + invalidate(); + } + }); + } + + public void setDisabledPage(int disabledPage) { + this.disabledPage = disabledPage; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + for (int i = 0; i < numPages; i++) { + if ((int) Math.floor(position) == i) { + // This is the current dot + drawCircle(canvas, i, (float) (1 - (position - Math.floor(position)))); + } else if ((int) Math.ceil(position) == i) { + // This is the next dot + drawCircle(canvas, i, (float) (position - Math.floor(position))); + } else { + drawCircle(canvas, i, 0); + } + } + } + + private void drawCircle(Canvas canvas, int position, float frac) { + float circleRadiusSmall = canvas.getHeight() * 0.26f; + float circleRadiusBig = canvas.getHeight() * 0.35f; + float circleRadiusDelta = (circleRadiusBig - circleRadiusSmall); + float start = 0.5f * (canvas.getWidth() - numPages * 1.5f * canvas.getHeight()); + paint.setStrokeWidth(canvas.getHeight() * 0.3f); + + if (position == disabledPage) { + paint.setStyle(Paint.Style.STROKE); + } else { + paint.setStyle(Paint.Style.FILL_AND_STROKE); + } + + paint.setColor((Integer) new ArgbEvaluator().evaluate(frac, circleColor, circleColorHighlight)); + canvas.drawCircle(start + (position * 1.5f + 0.75f) * canvas.getHeight(), 0.5f * canvas.getHeight(), + circleRadiusSmall + frac * circleRadiusDelta, paint); + } +}
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java index c0c74c42c..ab4920119 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java +++ b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java @@ -81,7 +81,7 @@ public class PieChartView extends AppCompatImageView { } public boolean isLargeEnoughToDisplay(int index) { - return getPercentageOfItem(index) > 0.05; + return getPercentageOfItem(index) > 0.04; } public int getColorOfItem(int index) { @@ -94,7 +94,6 @@ public class PieChartView extends AppCompatImageView { private static class PieChartDrawable extends Drawable { private static final float PADDING_DEGREES = 3f; - private static final float STROKE_SIZE = 15f; private PieChartData data; private final Paint paint; @@ -104,14 +103,16 @@ public class PieChartView extends AppCompatImageView { paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeCap(Paint.Cap.ROUND); - paint.setStrokeWidth(STROKE_SIZE); } @Override public void draw(@NonNull Canvas canvas) { - float radius = getBounds().height() - STROKE_SIZE; + final float strokeSize = getBounds().height() / 30f; + paint.setStrokeWidth(strokeSize); + + float radius = getBounds().height() - strokeSize; float center = getBounds().width() / 2.f; - RectF arcBounds = new RectF(center - radius, STROKE_SIZE, center + radius, STROKE_SIZE + radius * 2); + RectF arcBounds = new RectF(center - radius, strokeSize, center + radius, strokeSize + radius * 2); float startAngle = 180; for (int i = 0; i < data.values.length; i++) { diff --git a/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java new file mode 100644 index 000000000..3ea57eb5e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java @@ -0,0 +1,167 @@ +package de.danoeh.antennapod.view; + +import android.content.ClipData; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.core.content.ContextCompat; +import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.Consumer; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.playback.Timeline; + +public class ShownotesWebView extends WebView implements View.OnLongClickListener { + private static final String TAG = "ShownotesWebView"; + + /** + * URL that was selected via long-press. + */ + private String selectedUrl; + private Consumer<Integer> timecodeSelectedListener; + private Runnable pageFinishedListener; + + public ShownotesWebView(Context context) { + super(context); + setup(); + } + + public ShownotesWebView(Context context, AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public ShownotesWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + private void setup() { + setBackgroundColor(Color.TRANSPARENT); + if (!NetworkUtils.networkAvailable()) { + getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); + // Use cached resources, even if they have expired + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + } + getSettings().setUseWideViewPort(false); + getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); + getSettings().setLoadWithOverviewMode(true); + setOnLongClickListener(this); + + setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (Timeline.isTimecodeLink(url) && timecodeSelectedListener != null) { + timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl)); + } else { + IntentUtils.openInBrowser(getContext(), url); + } + return true; + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Log.d(TAG, "Page finished"); + if (pageFinishedListener != null) { + pageFinishedListener.run(); + } + } + }); + } + + @Override + public boolean onLongClick(View v) { + WebView.HitTestResult r = getHitTestResult(); + if (r != null && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { + Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); + selectedUrl = r.getExtra(); + showContextMenu(); + return true; + } + selectedUrl = null; + return false; + } + + public boolean onContextItemSelected(MenuItem item) { + if (selectedUrl == null) { + return false; + } + + switch (item.getItemId()) { + case R.id.open_in_browser_item: + IntentUtils.openInBrowser(getContext(), selectedUrl); + break; + case R.id.share_url_item: + ShareUtils.shareLink(getContext(), selectedUrl); + break; + case R.id.copy_url_item: + ClipData clipData = ClipData.newPlainText(selectedUrl, selectedUrl); + android.content.ClipboardManager cm = (android.content.ClipboardManager) getContext() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(clipData); + Snackbar.make(this, R.string.copied_url_msg, Snackbar.LENGTH_LONG).show(); + break; + case R.id.go_to_position_item: + if (Timeline.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) { + timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl)); + } else { + Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedUrl); + } + break; + default: + selectedUrl = null; + return false; + + } + selectedUrl = null; + return true; + } + + @Override + protected void onCreateContextMenu(ContextMenu menu) { + super.onCreateContextMenu(menu); + if (selectedUrl == null) { + return; + } + + if (Timeline.isTimecodeLink(selectedUrl)) { + menu.add(Menu.NONE, R.id.go_to_position_item, Menu.NONE, R.string.go_to_position_label); + menu.setHeaderTitle(Converter.getDurationStringLong(Timeline.getTimecodeLinkTime(selectedUrl))); + } else { + Uri uri = Uri.parse(selectedUrl); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if (IntentUtils.isCallable(getContext(), intent)) { + menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, R.string.open_in_browser_label); + } + menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, R.string.copy_url_label); + menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, R.string.share_url_label); + menu.setHeaderTitle(selectedUrl); + } + } + + public void setTimecodeSelectedListener(Consumer<Integer> timecodeSelectedListener) { + this.timecodeSelectedListener = timecodeSelectedListener; + } + + public void setPageFinishedListener(Runnable pageFinishedListener) { + this.pageFinishedListener = pageFinishedListener; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java b/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java deleted file mode 100644 index f4ee092df..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.util.Log; -import android.view.GestureDetector; -import android.view.MotionEvent; - -public class SwipeGestureDetector extends GestureDetector.SimpleOnGestureListener { - - private static final String TAG = "SwipeGestureDetector"; - - private static final int SWIPE_MIN_DISTANCE = 120; - private static final int SWIPE_MAX_OFF_PATH = 250; - private static final int SWIPE_THRESHOLD_VELOCITY = 200; - - private final OnSwipeGesture callback; - - public SwipeGestureDetector(OnSwipeGesture callback) { - this.callback = callback; - } - - @Override - public boolean onDown(MotionEvent e) { - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - try { - if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) - return false; - if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE - && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { - return callback.onSwipeRightToLeft(); - } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE - && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { - return callback.onSwipeLeftToRight(); - } - } catch (Exception e) { - Log.d(TAG, Log.getStackTraceString(e)); - } - return false; - } - -} |