diff options
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod')
69 files changed, 1480 insertions, 926 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java index aa59e4e96..f7c96a93a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java @@ -7,7 +7,6 @@ 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.util.Log; import com.google.android.material.snackbar.Snackbar; @@ -103,22 +102,21 @@ public class BugReportActivity extends AppCompatActivity { Runtime.getRuntime().exec(cmd); //share file try { - Intent i = new Intent(Intent.ACTION_SEND); - i.setType("text/*"); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/*"); String authString = getString(de.danoeh.antennapod.core.R.string.provider_authority); Uri fileUri = FileProvider.getUriForFile(this, authString, filename); - i.putExtra(Intent.EXTRA_STREAM, fileUri); - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - PackageManager pm = getPackageManager(); - List<ResolveInfo> resInfos = pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfos) { - String packageName = resolveInfo.activityInfo.packageName; - grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - } + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label); - startActivity(Intent.createChooser(i, chooserTitle)); + Intent chooser = Intent.createChooser(intent, chooserTitle); + List<ResolveInfo> resInfos = getPackageManager() + .queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfos) { + String packageName = resolveInfo.activityInfo.packageName; + grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + startActivity(chooser); } catch (Exception e) { e.printStackTrace(); int strResId = R.string.log_file_share_exception; 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 f07ad6ad5..7dc760e76 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -38,6 +38,7 @@ import com.bumptech.glide.Glide; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.playback.cast.CastEnabledActivity; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.Validate; import org.greenrobot.eventbus.EventBus; @@ -45,7 +46,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.MessageEvent; +import de.danoeh.antennapod.event.MessageEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.service.playback.PlaybackService; 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 4dca1fda7..9148a9949 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -6,7 +6,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.LightingColorFilter; -import android.os.Build; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableString; @@ -19,6 +18,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; @@ -31,8 +31,8 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; @@ -60,7 +60,6 @@ import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.discovery.PodcastSearcherRegistry; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedPreferences; -import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.model.playback.RemoteMedia; import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException; import io.reactivex.Maybe; @@ -101,6 +100,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { private Feed feed; private String selectedDownloadUrl; private Downloader downloader; + private String username = null; + private String password = null; private boolean isPaused; private boolean didPressSubscribe = false; @@ -144,12 +145,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (feedUrl.contains("subscribeonandroid.com")) { feedUrl = feedUrl.replaceFirst("((www.)?(subscribeonandroid.com/))", ""); } - if (savedInstanceState == null) { - lookupUrlAndDownload(feedUrl, null, null); - } else { - lookupUrlAndDownload(feedUrl, savedInstanceState.getString("username"), - savedInstanceState.getString("password")); + if (savedInstanceState != null) { + username = savedInstanceState.getString("username"); + password = savedInstanceState.getString("password"); } + lookupUrlAndDownload(feedUrl); } } @@ -210,10 +210,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - if (feed != null && feed.getPreferences() != null) { - outState.putString("username", feed.getPreferences().getUsername()); - outState.putString("password", feed.getPreferences().getPassword()); - } + outState.putString("username", username); + outState.putString("password", password); } private void resetIntent(String url) { @@ -242,32 +240,23 @@ public class OnlineFeedViewActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } - private void lookupUrlAndDownload(String url, String username, String password) { + private void lookupUrlAndDownload(String url) { download = PodcastSearcherRegistry.lookupUrl(url) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) - .subscribe(lookedUpUrl -> startFeedDownload(lookedUpUrl, username, password), + .subscribe(this::startFeedDownload, error -> { showNoPodcastFoundError(); Log.e(TAG, Log.getStackTraceString(error)); }); } - private void startFeedDownload(String url, String username, String password) { + private void startFeedDownload(String url) { Log.d(TAG, "Starting feed download"); url = URLChecker.prepareURL(url); feed = new Feed(url, null); - if (username != null && password != null) { - feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, - VolumeAdaptionSetting.OFF, username, password)); - } - String fileUrl; - try { - fileUrl = DownloadRequester.getInstance().getDownloadPathForFeed(feed).getAbsolutePath(); - } catch (DownloadRequestException e) { - e.printStackTrace(); - fileUrl = new File(getCacheDir(), FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); - } + String fileUrl = new File(getExternalCacheDir(), + FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); feed.setFile_url(fileUrl); final DownloadRequest request = new DownloadRequest(feed.getFile_url(), feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, @@ -293,6 +282,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { parseFeed(); } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { if (!isFinishing() && !isPaused) { + if (username != null && password != null) { + Toast.makeText(this, R.string.download_error_unauthorized, Toast.LENGTH_LONG).show(); + } dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this, R.string.authentication_notification_title, downloader.getDownloadRequest().getSource()).create(); @@ -458,8 +450,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { final int MAX_LINES_COLLAPSED = 10; description.setMaxLines(MAX_LINES_COLLAPSED); description.setOnClickListener(v -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && description.getMaxLines() > MAX_LINES_COLLAPSED) { + if (description.getMaxLines() > MAX_LINES_COLLAPSED) { description.setMaxLines(MAX_LINES_COLLAPSED); } else { description.setMaxLines(2000); @@ -642,21 +633,17 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (urls.size() == 1) { // Skip dialog and display the item directly resetIntent(urls.get(0)); - startFeedDownload(urls.get(0), null, null); + startFeedDownload(urls.get(0)); return true; } - final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); + final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, + R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); DialogInterface.OnClickListener onClickListener = (dialog, which) -> { String selectedUrl = urls.get(which); dialog.dismiss(); resetIntent(selectedUrl); - FeedPreferences prefs = feed.getPreferences(); - if(prefs != null) { - startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword()); - } else { - startFeedDownload(selectedUrl, null, null); - } + startFeedDownload(selectedUrl); }; AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this) @@ -679,7 +666,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { private final String feedUrl; FeedViewAuthenticationDialog(Context context, int titleRes, String feedUrl) { - super(context, titleRes, true, null, null); + super(context, titleRes, true, username, password); this.feedUrl = feedUrl; } @@ -691,7 +678,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { @Override protected void onConfirmed(String username, String password) { - startFeedDownload(feedUrl, username, password); + OnlineFeedViewActivity.this.username = username; + OnlineFeedViewActivity.this.password = password; + startFeedDownload(feedUrl); } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index a6810715c..3d0c9d113 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.activity; +import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -15,7 +16,9 @@ import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; -import androidx.annotation.NonNull; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -35,7 +38,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import org.apache.commons.io.ByteOrderMark; import org.apache.commons.io.input.BOMInputStream; -import org.apache.commons.lang3.ArrayUtils; import java.io.InputStream; import java.io.InputStreamReader; @@ -48,7 +50,6 @@ import java.util.List; * */ public class OpmlImportActivity extends AppCompatActivity { private static final String TAG = "OpmlImportBaseActivity"; - private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; @Nullable private Uri uri; OpmlSelectionBinding viewBinding; private ArrayAdapter<String> listAdapter; @@ -198,27 +199,23 @@ public class OpmlImportActivity extends AppCompatActivity { } private void requestPermission() { - String[] permissions = { android.Manifest.permission.READ_EXTERNAL_STORAGE }; - ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_READ_EXTERNAL_STORAGE); + requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE); } - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode != PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { - return; - } - if (grantResults.length > 0 && ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED)) { - startImport(); - } else { - new AlertDialog.Builder(this) - .setMessage(R.string.opml_import_ask_read_permission) - .setPositiveButton(android.R.string.ok, (dialog, which) -> requestPermission()) - .setNegativeButton(R.string.cancel_label, (dialog, which) -> finish()) - .show(); - } - } + private final ActivityResultLauncher<String> requestPermissionLauncher = + registerForActivityResult(new RequestPermission(), isGranted -> { + if (isGranted) { + startImport(); + } else { + new AlertDialog.Builder(this) + .setMessage(R.string.opml_import_ask_read_permission) + .setPositiveButton(android.R.string.ok, (dialog, which) -> + requestPermission()) + .setNegativeButton(R.string.cancel_label, (dialog, which) -> + finish()) + .show(); + } + }); /** Starts the import process. */ private void startImport() { 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 600204554..1fc16ab32 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.provider.Settings; import android.view.Menu; import android.view.MenuItem; + import android.view.View; import android.view.inputmethod.InputMethodManager; @@ -21,13 +22,13 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.databinding.SettingsActivityBinding; 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.MainPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.NetworkPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.NotificationPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.PlaybackPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.StoragePreferencesFragment; +import de.danoeh.antennapod.fragment.preferences.synchronization.SynchronizationPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.SwipePreferencesFragment; import de.danoeh.antennapod.fragment.preferences.UserInterfacePreferencesFragment; @@ -76,8 +77,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe prefFragment = new ImportExportPreferencesFragment(); } else if (screen == R.xml.preferences_autodownload) { prefFragment = new AutoDownloadPreferencesFragment(); - } else if (screen == R.xml.preferences_gpodder) { - prefFragment = new GpodderPreferencesFragment(); + } else if (screen == R.xml.preferences_synchronization) { + prefFragment = new SynchronizationPreferencesFragment(); } else if (screen == R.xml.preferences_playback) { prefFragment = new PlaybackPreferencesFragment(); } else if (screen == R.xml.preferences_notifications) { @@ -101,8 +102,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe return R.string.import_export_pref; } else if (preferences == R.xml.preferences_user_interface) { return R.string.user_interface_label; - } else if (preferences == R.xml.preferences_gpodder) { - return R.string.gpodnet_main_label; + } else if (preferences == R.xml.preferences_synchronization) { + return R.string.synchronization_pref; } else if (preferences == R.xml.preferences_notifications) { return R.string.notification_pref_fragment; } else if (preferences == R.xml.feed_settings) { diff --git a/app/src/main/java/de/danoeh/antennapod/activity/SelectSubscriptionActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/SelectSubscriptionActivity.java new file mode 100644 index 000000000..4ffed949e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/SelectSubscriptionActivity.java @@ -0,0 +1,162 @@ +package de.danoeh.antennapod.activity; + +import static de.danoeh.antennapod.activity.MainActivity.EXTRA_FEED_ID; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.util.Log; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; + +import java.util.ArrayList; +import java.util.List; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.NavDrawerData; +import de.danoeh.antennapod.databinding.SubscriptionSelectionActivityBinding; +import de.danoeh.antennapod.model.feed.Feed; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +public class SelectSubscriptionActivity extends AppCompatActivity { + + private static final String TAG = "SelectSubscription"; + + private Disposable disposable; + private volatile List<Feed> listItems; + + private SubscriptionSelectionActivityBinding viewBinding; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(UserPreferences.getTranslucentTheme()); + super.onCreate(savedInstanceState); + + viewBinding = SubscriptionSelectionActivityBinding.inflate(getLayoutInflater()); + setContentView(viewBinding.getRoot()); + setSupportActionBar(viewBinding.toolbar); + setTitle(R.string.shortcut_select_subscription); + + viewBinding.transparentBackground.setOnClickListener(v -> finish()); + viewBinding.card.setOnClickListener(null); + + loadSubscriptions(); + + final Integer[] checkedPosition = new Integer[1]; + viewBinding.list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + viewBinding.list.setOnItemClickListener((listView, view1, position, rowId) -> + checkedPosition[0] = position + ); + viewBinding.shortcutBtn.setOnClickListener(view -> { + if (checkedPosition[0] != null && Intent.ACTION_CREATE_SHORTCUT.equals( + getIntent().getAction())) { + getBitmapFromUrl(listItems.get(checkedPosition[0])); + } + }); + + } + + public List<Feed> getFeedItems(List<NavDrawerData.DrawerItem> items, List<Feed> result) { + for (NavDrawerData.DrawerItem item : items) { + if (item.type == NavDrawerData.DrawerItem.Type.TAG) { + getFeedItems(((NavDrawerData.TagDrawerItem) item).children, result); + } else { + Feed feed = ((NavDrawerData.FeedDrawerItem) item).feed; + if (!result.contains(feed)) { + result.add(feed); + } + } + } + return result; + } + + private void addShortcut(Feed feed, Bitmap bitmap) { + Intent intent = new Intent(this, MainActivity.class); + intent.setAction(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(EXTRA_FEED_ID, feed.getId()); + String id = "subscription-" + feed.getId(); + IconCompat icon; + + if (bitmap != null) { + icon = IconCompat.createWithAdaptiveBitmap(bitmap); + } else { + icon = IconCompat.createWithResource(this, R.drawable.ic_folder_shortcut); + } + + ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, id) + .setShortLabel(feed.getTitle()) + .setLongLabel(feed.getFeedTitle()) + .setIntent(intent) + .setIcon(icon) + .build(); + + setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut)); + finish(); + } + + private void getBitmapFromUrl(Feed feed) { + int iconSize = (int) (128 * getResources().getDisplayMetrics().density); + Glide.with(this) + .asBitmap() + .load(feed.getImageUrl()) + .apply(new RequestOptions().override(iconSize, iconSize)) + .listener(new RequestListener<Bitmap>() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, + Target<Bitmap> target, boolean isFirstResource) { + addShortcut(feed, null); + return true; + } + + @Override + public boolean onResourceReady(Bitmap resource, Object model, + Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) { + addShortcut(feed, resource); + return true; + } + }).submit(); + } + + private void loadSubscriptions() { + if (disposable != null) { + disposable.dispose(); + } + disposable = Observable.fromCallable( + () -> { + NavDrawerData data = DBReader.getNavDrawerData(); + return getFeedItems(data.items, new ArrayList<>()); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + result -> { + listItems = result; + ArrayList<String> titles = new ArrayList<>(); + for (Feed feed: result) { + titles.add(feed.getTitle()); + } + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, + R.layout.simple_list_item_multiple_choice_on_start, titles); + viewBinding.list.setAdapter(adapter); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); + } +}
\ No newline at end of file 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 d436acf0d..4ff2a5775 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -36,11 +36,13 @@ import androidx.core.view.WindowCompat; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import com.bumptech.glide.Glide; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.event.playback.BufferUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.Converter; @@ -50,7 +52,6 @@ import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; -import de.danoeh.antennapod.core.util.playback.MediaPlayerError; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.databinding.VideoplayerActivityBinding; import de.danoeh.antennapod.dialog.PlaybackControlsDialog; @@ -60,6 +61,8 @@ import de.danoeh.antennapod.dialog.SleepTimerDialog; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlayerStatus; +import de.danoeh.antennapod.playback.cast.CastEnabledActivity; import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -193,40 +196,11 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar. } @Override - public void onBufferStart() { - viewBinding.progressBar.setVisibility(View.VISIBLE); - } - - @Override - public void onBufferEnd() { - viewBinding.progressBar.setVisibility(View.INVISIBLE); - } - - @Override - public void onBufferUpdate(float progress) { - viewBinding.sbPosition.setSecondaryProgress((int) (progress * viewBinding.sbPosition.getMax())); - } - - @Override - public void handleError(int code) { - final AlertDialog.Builder errorDialog = new AlertDialog.Builder(VideoplayerActivity.this); - errorDialog.setTitle(R.string.error_label); - errorDialog.setMessage(MediaPlayerError.getErrorString(VideoplayerActivity.this, code)); - errorDialog.setNeutralButton(android.R.string.ok, (dialog, which) -> finish()); - errorDialog.show(); - } - - @Override public void onReloadNotification(int code) { VideoplayerActivity.this.onReloadNotification(code); } @Override - public void onSleepTimerUpdate() { - supportInvalidateOptionsMenu(); - } - - @Override protected void updatePlayButtonShowsPlay(boolean showPlay) { viewBinding.playButton.setIsShowPlay(showPlay); } @@ -261,6 +235,26 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar. }; } + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void bufferUpdate(BufferUpdateEvent event) { + if (event.hasStarted()) { + viewBinding.progressBar.setVisibility(View.VISIBLE); + } else if (event.hasEnded()) { + viewBinding.progressBar.setVisibility(View.INVISIBLE); + } else { + viewBinding.sbPosition.setSecondaryProgress((int) (event.getProgress() * viewBinding.sbPosition.getMax())); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void sleepTimerUpdate(SleepTimerUpdatedEvent event) { + if (event.isCancelled() || event.wasJustEnabled()) { + supportInvalidateOptionsMenu(); + } + } + protected void loadMediaInfo() { Log.d(TAG, "loadMediaInfo()"); if (controller == null || controller.getMedia() == null) { @@ -544,12 +538,21 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar. } @Subscribe(threadMode = ThreadMode.MAIN) - public void onPlaybackServiceChanged(ServiceEvent event) { - if (event.action == ServiceEvent.Action.SERVICE_SHUT_DOWN) { + public void onPlaybackServiceChanged(PlaybackServiceEvent event) { + if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) { finish(); } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onMediaPlayerError(PlayerErrorEvent event) { + final AlertDialog.Builder errorDialog = new AlertDialog.Builder(VideoplayerActivity.this); + errorDialog.setTitle(R.string.error_label); + errorDialog.setMessage(event.getMessage()); + errorDialog.setNeutralButton(android.R.string.ok, (dialog, which) -> finish()); + errorDialog.show(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java index 3020aba43..674071294 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java @@ -1,21 +1,14 @@ package de.danoeh.antennapod.activity; -import android.Manifest; -import android.app.WallpaperManager; import android.appwidget.AppWidgetManager; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.CheckBox; -import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.PlayerWidget; @@ -51,7 +44,6 @@ public class WidgetConfigActivity extends AppCompatActivity { finish(); } - displayDeviceBackground(); opacityTextView = findViewById(R.id.widget_opacity_textView); opacitySeekBar = findViewById(R.id.widget_opacity_seekBar); widgetPreview = findViewById(R.id.widgetLayout); @@ -102,16 +94,6 @@ public class WidgetConfigActivity extends AppCompatActivity { widgetPreview.findViewById(R.id.butRew).setVisibility(ckRewind.isChecked() ? View.VISIBLE : View.GONE); } - private void displayDeviceBackground() { - int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); - if (Build.VERSION.SDK_INT < 27 || permission == PackageManager.PERMISSION_GRANTED) { - final WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); - final Drawable wallpaperDrawable = wallpaperManager.getDrawable(); - ImageView background = findViewById(R.id.widget_config_background); - background.setImageDrawable(wallpaperDrawable); - } - } - private void confirmCreateWidget(View v) { int backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.getProgress()); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java index 2ab96e84d..5ddb6407c 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java @@ -95,7 +95,7 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { holder.preview.setVisibility(View.GONE); holder.description.setTag(Boolean.FALSE); } else { - holder.description.setMaxLines(2000); + holder.description.setMaxLines(30); holder.description.setTag(Boolean.TRUE); holder.preview.setVisibility(item.getMedia() != null ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java index ff0311ab6..7854f7aa9 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -196,7 +196,7 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> bindFeedView((NavDrawerData.FeedDrawerItem) item, (FeedHolder) holder); holder.itemView.setOnCreateContextMenuListener(itemAccess); } else { - bindFolderView((NavDrawerData.FolderDrawerItem) item, (FeedHolder) holder); + bindTagView((NavDrawerData.TagDrawerItem) item, (FeedHolder) holder); } } if (viewType != VIEW_TYPE_SECTION_DIVIDER) { @@ -327,16 +327,16 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> } } - private void bindFolderView(NavDrawerData.FolderDrawerItem folder, FeedHolder holder) { + private void bindTagView(NavDrawerData.TagDrawerItem tag, FeedHolder holder) { Activity context = activity.get(); if (context == null) { return; } - if (folder.isOpen) { + if (tag.isOpen) { holder.count.setVisibility(View.GONE); } Glide.with(context).clear(holder.image); - holder.image.setImageResource(R.drawable.ic_folder); + holder.image.setImageResource(R.drawable.ic_tag); holder.failure.setVisibility(View.GONE); } 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 5fec5f063..26674b2b2 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java @@ -1,13 +1,12 @@ package de.danoeh.antennapod.adapter; -import android.content.Context; -import androidx.appcompat.app.AlertDialog; - +import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateFormatter; +import de.danoeh.antennapod.fragment.FeedStatisticsDialogFragment; import de.danoeh.antennapod.view.PieChartView; import java.util.Date; @@ -18,10 +17,12 @@ import java.util.List; */ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { + private final Fragment fragment; boolean countAll = true; - public PlaybackStatisticsListAdapter(Context context) { - super(context); + public PlaybackStatisticsListAdapter(Fragment fragment) { + super(fragment.getContext()); + this.fragment = fragment; } public void setCountAll(boolean countAll) { @@ -60,16 +61,9 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { holder.value.setText(Converter.shortLocalizedDuration(context, time)); holder.itemView.setOnClickListener(v -> { - AlertDialog.Builder dialog = new AlertDialog.Builder(context); - dialog.setTitle(statsItem.feed.getTitle()); - dialog.setMessage(context.getString(R.string.statistics_details_dialog, - countAll ? statsItem.episodesStartedIncludingMarked : statsItem.episodesStarted, - statsItem.episodes, Converter.shortLocalizedDuration(context, - countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed), - Converter.shortLocalizedDuration(context, statsItem.time))); - dialog.setPositiveButton(android.R.string.ok, null); - dialog.show(); + FeedStatisticsDialogFragment yourDialogFragment = FeedStatisticsDialogFragment.newInstance( + statsItem.feed.getId(), statsItem.feed.getTitle()); + yourDialogFragment.show(fragment.getChildFragmentManager().beginTransaction(), "DialogFragment"); }); } - } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java index 73f67d016..b637eb31d 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java @@ -219,7 +219,7 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription .load(); } else { new CoverLoader(mainActivityRef.get()) - .withResource(R.drawable.ic_folder) + .withResource(R.drawable.ic_tag) .withPlaceholderView(feedTitle, true) .withCoverView(imageView) .load(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java index dedf8e5e6..a2b0e98c3 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java @@ -34,7 +34,7 @@ public class CancelDownloadActionButton extends ItemActionButton { FeedMedia media = item.getMedia(); DownloadRequester.getInstance().cancelDownload(context, media); if (UserPreferences.isEnableAutodownload()) { - item.setAutoDownload(false); + item.disableAutoDownload(); DBWriter.setFeedItem(item); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index a45eb5199..1f4f657b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -15,6 +15,5 @@ class ClientConfigurator { ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME; ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl(); ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); - ClientConfig.castCallbacks = new CastCallbackImpl(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java index 938bb5931..590b7c897 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.config; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import de.danoeh.antennapod.R; @@ -24,7 +25,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); return PendingIntent.getActivity(context, - R.id.pending_intent_download_service_notification, intent, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_download_service_notification, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override @@ -33,7 +35,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { activityIntent.setAction("request" + request.getFeedfileId()); activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request); return PendingIntent.getActivity(context.getApplicationContext(), - R.id.pending_intent_download_service_auth, activityIntent, PendingIntent.FLAG_ONE_SHOT); + R.id.pending_intent_download_service_auth, activityIntent, + PendingIntent.FLAG_ONE_SHOT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override @@ -43,15 +46,15 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { Bundle args = new Bundle(); args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); - return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, - intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override public PendingIntent getAutoDownloadReportNotificationContentIntent(Context context) { Intent intent = new Intent(context, MainActivity.class); intent.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, QueueFragment.TAG); - return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, - intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java index ee19a0339..595f37e40 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.dialog; import android.content.Context; import android.view.View; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.RadioButton; @@ -14,7 +15,6 @@ import de.danoeh.antennapod.model.feed.FeedFilter; * Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion */ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { - private final FeedFilter initialFilter; public EpisodeFilterDialog(Context context, FeedFilter filter) { @@ -26,8 +26,10 @@ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { setView(rootView); final EditText etxtEpisodeFilterText = rootView.findViewById(R.id.etxtEpisodeFilterText); + final EditText etxtEpisodeFilterDurationText = rootView.findViewById(R.id.etxtEpisodeFilterDurationText); final RadioButton radioInclude = rootView.findViewById(R.id.radio_filter_include); final RadioButton radioExclude = rootView.findViewById(R.id.radio_filter_exclude); + final CheckBox checkboxDuration = rootView.findViewById(R.id.checkbox_filter_duration); if (initialFilter.includeOnly()) { radioInclude.setChecked(true); @@ -40,18 +42,31 @@ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { radioInclude.setChecked(false); etxtEpisodeFilterText.setText(""); } + if (initialFilter.hasMinimalDurationFilter()) { + checkboxDuration.setChecked(true); + // Store minimal duration in seconds, show in minutes + etxtEpisodeFilterDurationText.setText(String.valueOf(initialFilter.getMinimalDurationFilter() / 60)); + } setNegativeButton(R.string.cancel_label, null); setPositiveButton(R.string.confirm_label, (dialog, which) -> { String includeString = ""; String excludeString = ""; + int minimalDuration = -1; if (radioInclude.isChecked()) { includeString = etxtEpisodeFilterText.getText().toString(); } else { excludeString = etxtEpisodeFilterText.getText().toString(); } - - onConfirmed(new FeedFilter(includeString, excludeString)); + if (checkboxDuration.isChecked()) { + try { + // Store minimal duration in seconds + minimalDuration = Integer.parseInt(etxtEpisodeFilterDurationText.getText().toString()) * 60; + } catch (NumberFormatException e) { + // Do not change anything on error + } + } + onConfirmed(new FeedFilter(includeString, excludeString, minimalDuration)); } ); } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java index 96d1b9b67..b89d05f88 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java @@ -10,7 +10,7 @@ import java.util.Arrays; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; public class FeedSortDialog { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index 3186cbe2e..5cc1f99c6 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -12,9 +12,13 @@ import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.view.PlaybackSpeedSeekBar; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import java.util.List; import java.util.Locale; @@ -22,6 +26,8 @@ import java.util.Locale; public class PlaybackControlsDialog extends DialogFragment { private PlaybackController controller; private AlertDialog dialog; + private PlaybackSpeedSeekBar speedSeekBar; + private TextView txtvPlaybackSpeed; public static PlaybackControlsDialog newInstance() { Bundle arguments = new Bundle(); @@ -42,10 +48,12 @@ public class PlaybackControlsDialog extends DialogFragment { public void loadMediaInfo() { setupUi(); setupAudioTracks(); + updateSpeed(new SpeedChangedEvent(getCurrentPlaybackSpeedMultiplier())); } }; controller.init(); setupUi(); + EventBus.getDefault().register(this); } @Override @@ -53,6 +61,7 @@ public class PlaybackControlsDialog extends DialogFragment { super.onStop(); controller.release(); controller = null; + EventBus.getDefault().unregister(this); } @NonNull @@ -66,12 +75,14 @@ public class PlaybackControlsDialog extends DialogFragment { } private void setupUi() { - final TextView txtvPlaybackSpeed = dialog.findViewById(R.id.txtvPlaybackSpeed); - - PlaybackSpeedSeekBar speedSeekBar = dialog.findViewById(R.id.speed_seek_bar); - speedSeekBar.setController(controller); - speedSeekBar.setProgressChangedListener(speed - -> txtvPlaybackSpeed.setText(String.format(Locale.getDefault(), "%.2fx", speed))); + txtvPlaybackSpeed = dialog.findViewById(R.id.txtvPlaybackSpeed); + speedSeekBar = dialog.findViewById(R.id.speed_seek_bar); + speedSeekBar.setProgressChangedListener(speed -> { + if (controller != null) { + controller.setPlaybackSpeed(speed); + } + }); + updateSpeed(new SpeedChangedEvent(controller.getCurrentPlaybackSpeedMultiplier())); final CheckBox stereoToMono = dialog.findViewById(R.id.stereo_to_mono); stereoToMono.setChecked(UserPreferences.stereoToMono()); @@ -100,6 +111,12 @@ public class PlaybackControlsDialog extends DialogFragment { }); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void updateSpeed(SpeedChangedEvent event) { + txtvPlaybackSpeed.setText(String.format(Locale.getDefault(), "%.2fx", event.getNewSpeed())); + speedSeekBar.updateSpeed(event.getNewSpeed()); + } + private void setupAudioTracks() { List<String> audioTracks = controller.getAudioTracks(); int selectedAudioTrack = controller.getSelectedAudioTrack(); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java index 13258b4ec..ad2ed3499 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java @@ -67,6 +67,7 @@ public class ProxyDialog { .setView(content) .setNegativeButton(R.string.cancel_label, null) .setPositiveButton(R.string.proxy_test_label, null) + .setNeutralButton(R.string.reset, null) .show(); // To prevent cancelling the dialog on button click dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener((view) -> { @@ -75,36 +76,19 @@ public class ProxyDialog { test(); return; } - String type = (String) spType.getSelectedItem(); - ProxyConfig proxy; - if (Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { - proxy = ProxyConfig.direct(); - } else { - String host = etHost.getText().toString(); - String port = etPort.getText().toString(); - String username = etUsername.getText().toString(); - if (TextUtils.isEmpty(username)) { - username = null; - } - String password = etPassword.getText().toString(); - if (TextUtils.isEmpty(password)) { - password = null; - } - int portValue = 0; - if (!TextUtils.isEmpty(port)) { - portValue = Integer.parseInt(port); - } - if (Proxy.Type.valueOf(type) == Proxy.Type.SOCKS) { - proxy = ProxyConfig.socks(host, portValue, username, password); - } else { - proxy = ProxyConfig.http(host, portValue, username, password); - } - } - UserPreferences.setProxyConfig(proxy); + setProxyConfig(); AntennapodHttpClient.reinit(); dialog.dismiss(); }); + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener((view) -> { + etHost.getText().clear(); + etPort.getText().clear(); + etUsername.getText().clear(); + etPassword.getText().clear(); + setProxyConfig(); + }); + List<String> types = new ArrayList<>(); types.add(Proxy.Type.DIRECT.name()); types.add(Proxy.Type.HTTP.name()); @@ -144,6 +128,11 @@ public class ProxyDialog { spType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (position == 0) { + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setVisibility(View.GONE); + } else { + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setVisibility(View.VISIBLE); + } enableSettings(position > 0); setTestRequired(position > 0); } @@ -158,6 +147,35 @@ public class ProxyDialog { return dialog; } + private void setProxyConfig() { + String type = (String) spType.getSelectedItem(); + ProxyConfig proxy; + if (Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { + proxy = ProxyConfig.direct(); + } else { + String host = etHost.getText().toString(); + String port = etPort.getText().toString(); + String username = etUsername.getText().toString(); + if (TextUtils.isEmpty(username)) { + username = null; + } + String password = etPassword.getText().toString(); + if (TextUtils.isEmpty(password)) { + password = null; + } + int portValue = 0; + if (!TextUtils.isEmpty(port)) { + portValue = Integer.parseInt(port); + } + if (Proxy.Type.valueOf(type) == Proxy.Type.SOCKS) { + proxy = ProxyConfig.socks(host, portValue, username, password); + } else { + proxy = ProxyConfig.http(host, portValue, username, password); + } + } + UserPreferences.setProxyConfig(proxy); + } + private final TextWatcher requireTestOnChange = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java index 9fcf8be69..23c032248 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java @@ -19,18 +19,18 @@ import io.reactivex.schedulers.Schedulers; public class RemoveFeedDialog { private static final String TAG = "RemoveFeedDialog"; - public static void show(Context context, Feed feed, Runnable onSuccess) { + public static void show(Context context, Feed feed) { List<Feed> feeds = Collections.singletonList(feed); String message = getMessageId(context, feeds); - showDialog(context, feeds, message, onSuccess); + showDialog(context, feeds, message); } - public static void show(Context context, List<Feed> feeds, Runnable onSuccess) { + public static void show(Context context, List<Feed> feeds) { String message = getMessageId(context, feeds); - showDialog(context, feeds, message, onSuccess); + showDialog(context, feeds, message); } - private static void showDialog(Context context, List<Feed> feeds, String message, Runnable onSuccess) { + private static void showDialog(Context context, List<Feed> feeds, String message) { ConfirmationDialog dialog = new ConfirmationDialog(context, R.string.remove_feed_label, message) { @Override public void onConfirmButtonPressed(DialogInterface clickedDialog) { @@ -42,20 +42,16 @@ public class RemoveFeedDialog { progressDialog.setCancelable(false); progressDialog.show(); - Completable.fromCallable(() -> { + Completable.fromAction(() -> { for (Feed feed : feeds) { DBWriter.deleteFeed(context, feed.getId()).get(); } - return null; }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( () -> { Log.d(TAG, "Feed(s) deleted"); - if (onSuccess != null) { - onSuccess.run(); - } progressDialog.dismiss(); }, error -> { Log.e(TAG, Log.getStackTraceString(error)); 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 691bd65e8..764940e06 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -18,20 +18,17 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; 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; - -import java.util.concurrent.TimeUnit; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; public class SleepTimerDialog extends DialogFragment { private PlaybackController controller; - private Disposable timeUpdater; - private EditText etxtTime; private Spinner spTimeUnit; private LinearLayout timeSetup; @@ -47,19 +44,11 @@ public class SleepTimerDialog extends DialogFragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public void onSleepTimerUpdate() { - updateTime(); - } - - @Override public void loadMediaInfo() { - updateTime(); } }; controller.init(); - timeUpdater = Observable.interval(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(tick -> updateTime()); + EventBus.getDefault().register(this); } @Override @@ -68,9 +57,7 @@ public class SleepTimerDialog extends DialogFragment { if (controller != null) { controller.release(); } - if (timeUpdater != null) { - timeUpdater.dispose(); - } + EventBus.getDefault().unregister(this); } @NonNull @@ -170,13 +157,12 @@ public class SleepTimerDialog extends DialogFragment { return builder.create(); } - 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())); + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void timerUpdated(SleepTimerUpdatedEvent event) { + timeDisplay.setVisibility(event.isOver() || event.isCancelled() ? View.GONE : View.VISIBLE); + timeSetup.setVisibility(event.isOver() || event.isCancelled() ? View.VISIBLE : View.GONE); + time.setText(Converter.getDurationStringLong((int) event.getTimeLeft())); } private void closeKeyboard(View content) { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java index 29172bb5e..9e524188f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java @@ -16,7 +16,7 @@ import java.util.HashSet; import java.util.Set; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.SubscriptionsFilter; import de.danoeh.antennapod.core.feed.SubscriptionsFilterGroup; import de.danoeh.antennapod.core.preferences.UserPreferences; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java index 8ef01590f..8f5f1b802 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java @@ -27,7 +27,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class TagSettingsDialog extends DialogFragment { public static final String TAG = "TagSettingsDialog"; @@ -36,10 +38,10 @@ public class TagSettingsDialog extends DialogFragment { private EditTagsDialogBinding viewBinding; private TagSelectionAdapter adapter; - public static TagSettingsDialog newInstance(FeedPreferences preferences) { + public static TagSettingsDialog newInstance(List<FeedPreferences> preferencesList) { TagSettingsDialog fragment = new TagSettingsDialog(); Bundle args = new Bundle(); - args.putSerializable(ARG_FEED_PREFERENCES, preferences); + args.putSerializable(ARG_FEED_PREFERENCES, new ArrayList<>(preferencesList)); fragment.setArguments(args); return fragment; } @@ -47,8 +49,14 @@ public class TagSettingsDialog extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - FeedPreferences preferences = (FeedPreferences) getArguments().getSerializable(ARG_FEED_PREFERENCES); - displayedTags = new ArrayList<>(preferences.getTags()); + ArrayList<FeedPreferences> feedPreferencesList = + (ArrayList<FeedPreferences>) getArguments().getSerializable(ARG_FEED_PREFERENCES); + Set<String> commonTags = new HashSet<>(feedPreferencesList.get(0).getTags()); + + for (FeedPreferences preference : feedPreferencesList) { + commonTags.retainAll(preference.getTags()); + } + displayedTags = new ArrayList<>(commonTags); displayedTags.remove(FeedPreferences.TAG_ROOT); viewBinding = EditTagsDialogBinding.inflate(getLayoutInflater()); @@ -57,7 +65,7 @@ public class TagSettingsDialog extends DialogFragment { adapter = new TagSelectionAdapter(); adapter.setHasStableIds(true); viewBinding.tagsRecycler.setAdapter(adapter); - viewBinding.rootFolderCheckbox.setChecked(preferences.getTags().contains(FeedPreferences.TAG_ROOT)); + viewBinding.rootFolderCheckbox.setChecked(commonTags.contains(FeedPreferences.TAG_ROOT)); viewBinding.newTagButton.setOnClickListener(v -> addTag(viewBinding.newTagEditText.getText().toString().trim())); @@ -73,17 +81,16 @@ public class TagSettingsDialog extends DialogFragment { } }); + if (feedPreferencesList.size() > 1) { + viewBinding.commonTagsInfo.setVisibility(View.VISIBLE); + } + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); dialog.setView(viewBinding.getRoot()); - dialog.setTitle(R.string.feed_folders_label); + dialog.setTitle(R.string.feed_tags_label); dialog.setPositiveButton(android.R.string.ok, (d, input) -> { addTag(viewBinding.newTagEditText.getText().toString().trim()); - preferences.getTags().clear(); - preferences.getTags().addAll(displayedTags); - if (viewBinding.rootFolderCheckbox.isChecked()) { - preferences.getTags().add(FeedPreferences.TAG_ROOT); - } - DBWriter.setFeedPreferences(preferences); + updatePreferencesTags(feedPreferencesList, commonTags); }); dialog.setNegativeButton(R.string.cancel_label, null); return dialog.create(); @@ -96,7 +103,7 @@ public class TagSettingsDialog extends DialogFragment { List<NavDrawerData.DrawerItem> items = data.items; List<String> folders = new ArrayList<String>(); for (NavDrawerData.DrawerItem item : items) { - if (item.type == NavDrawerData.DrawerItem.Type.FOLDER) { + if (item.type == NavDrawerData.DrawerItem.Type.TAG) { folders.add(item.getTitle()); } } @@ -123,6 +130,17 @@ public class TagSettingsDialog extends DialogFragment { adapter.notifyDataSetChanged(); } + private void updatePreferencesTags(List<FeedPreferences> feedPreferencesList, Set<String> commonTags) { + if (viewBinding.rootFolderCheckbox.isChecked()) { + displayedTags.add(FeedPreferences.TAG_ROOT); + } + for (FeedPreferences preferences : feedPreferencesList) { + preferences.getTags().removeAll(commonTags); + preferences.getTags().addAll(displayedTags); + DBWriter.setFeedPreferences(preferences); + } + } + public class TagSelectionAdapter extends RecyclerView.Adapter<TagSelectionAdapter.ViewHolder> { @Override diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java index def2e56a7..2bce73b79 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.dialog; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -15,10 +14,14 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.chip.Chip; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.view.ItemOffsetDecoration; import de.danoeh.antennapod.view.PlaybackSpeedSeekBar; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -47,22 +50,12 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public void onPlaybackSpeedChange() { - updateSpeed(); - } - - @Override public void loadMediaInfo() { - updateSpeed(); + updateSpeed(new SpeedChangedEvent(controller.getCurrentPlaybackSpeedMultiplier())); } }; controller.init(); - speedSeekBar.setController(controller); - } - - private void updateSpeed() { - speedSeekBar.updateSpeed(); - addCurrentSpeedChip.setText(speedFormat.format(controller.getCurrentPlaybackSpeedMultiplier())); + EventBus.getDefault().register(this); } @Override @@ -70,6 +63,13 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { super.onStop(); controller.release(); controller = null; + EventBus.getDefault().unregister(this); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void updateSpeed(SpeedChangedEvent event) { + speedSeekBar.updateSpeed(event.getNewSpeed()); + addCurrentSpeedChip.setText(speedFormat.format(event.getNewSpeed())); } @Nullable @@ -78,6 +78,11 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { @Nullable Bundle savedInstanceState) { View root = View.inflate(getContext(), R.layout.speed_select_dialog, null); speedSeekBar = root.findViewById(R.id.speed_seek_bar); + speedSeekBar.setProgressChangedListener(multiplier -> { + if (controller != null) { + controller.setPlaybackSpeed(multiplier); + } + }); RecyclerView selectedSpeedsGrid = root.findViewById(R.id.selected_speeds_grid); selectedSpeedsGrid.setLayoutManager(new GridLayoutManager(getContext(), 3)); selectedSpeedsGrid.addItemDecoration(new ItemOffsetDecoration(getContext(), 4)); @@ -112,9 +117,7 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { @NonNull public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { Chip chip = new Chip(getContext()); - if (Build.VERSION.SDK_INT >= 17) { - chip.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - } + chip.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); return new ViewHolder(chip); } diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java index f97c1c7ab..340783208 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.discovery; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException; @@ -18,8 +18,8 @@ public class GpodnetPodcastSearcher implements PodcastSearcher { return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> { try { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0); List<PodcastSearchResult> results = new ArrayList<>(); for (GpodnetPodcast podcast : gpodnetPodcasts) { diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java index 6e894176f..5f3dd5f61 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java @@ -17,9 +17,12 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ItunesPodcastSearcher implements PodcastSearcher { private static final String ITUNES_API_URL = "https://itunes.apple.com/search?media=podcast&term=%s"; + private static final String PATTERN_BY_ID = ".*/podcasts\\.apple\\.com/.*/podcast/.*/id(\\d+).*"; public ItunesPodcastSearcher() { } @@ -70,9 +73,12 @@ public class ItunesPodcastSearcher implements PodcastSearcher { @Override public Single<String> lookupUrl(String url) { + Pattern pattern = Pattern.compile(PATTERN_BY_ID); + Matcher matcher = pattern.matcher(url); + final String lookupUrl = matcher.find() ? ("https://itunes.apple.com/lookup?id=" + matcher.group(1)) : url; return Single.create(emitter -> { OkHttpClient client = AntennapodHttpClient.getHttpClient(); - Request.Builder httpReq = new Request.Builder().url(url); + Request.Builder httpReq = new Request.Builder().url(lookupUrl); try { Response response = client.newCall(httpReq.build()).execute(); if (response.isSuccessful()) { @@ -92,7 +98,7 @@ public class ItunesPodcastSearcher implements PodcastSearcher { @Override public boolean urlNeedsLookup(String url) { - return url.contains("itunes.apple.com"); + return url.contains("itunes.apple.com") || url.matches(PATTERN_BY_ID); } @Override 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 64e7f161e..8c01a4563 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ClipboardManager; import android.content.Context; @@ -12,9 +11,14 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.activity.result.contract.ActivityResultContracts.GetContent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.documentfile.provider.DocumentFile; @@ -48,14 +52,17 @@ import java.util.Collections; public class AddFeedFragment extends Fragment { public static final String TAG = "AddFeedFragment"; - private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; - private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; private static final String KEY_UP_ARROW = "up_arrow"; private AddfeedBinding viewBinding; private MainActivity activity; private boolean displayUpArrow; + private final ActivityResultLauncher<String> chooseOpmlImportPathLauncher = + registerForActivityResult(new GetContent(), this::chooseOpmlImportPathResult); + private final ActivityResultLauncher<Uri> addLocalFolderLauncher = + registerForActivityResult(new AddLocalFolder(), this::addLocalFolderResult); + @Override @Nullable public View onCreateView(@NonNull LayoutInflater inflater, @@ -91,10 +98,7 @@ public class AddFeedFragment extends Fragment { viewBinding.opmlImportButton.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); + chooseOpmlImportPathLauncher.launch("*/*"); } catch (ActivityNotFoundException e) { e.printStackTrace(); ((MainActivity) getActivity()) @@ -107,9 +111,7 @@ public class AddFeedFragment extends Fragment { return; } try { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER); + addLocalFolderLauncher.launch(null); } catch (ActivityNotFoundException e) { e.printStackTrace(); ((MainActivity) getActivity()) @@ -157,6 +159,10 @@ public class AddFeedFragment extends Fragment { } private void performSearch() { + viewBinding.combinedFeedSearchEditText.clearFocus(); + InputMethodManager in = (InputMethodManager) + getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + in.hideSoftInputFromWindow(viewBinding.combinedFeedSearchEditText.getWindowToken(), 0); String query = viewBinding.combinedFeedSearchEditText.getText().toString(); if (query.matches("http[s]?://.*")) { addUrl(query); @@ -171,22 +177,23 @@ public class AddFeedFragment extends Fragment { setRetainInstance(true); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK || data == null) { + private void chooseOpmlImportPathResult(final Uri uri) { + if (uri == 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); - } else if (requestCode == REQUEST_CODE_ADD_LOCAL_FOLDER) { - Observable.fromCallable(() -> addLocalFolder(uri)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( + final Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + + private void addLocalFolderResult(final Uri uri) { + if (uri == null) { + return; + } + Observable.fromCallable(() -> addLocalFolder(uri)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( feed -> { Fragment fragment = FeedItemlistFragment.newInstance(feed.getId()); ((MainActivity) getActivity()).loadChildFragment(fragment); @@ -195,7 +202,6 @@ public class AddFeedFragment extends Fragment { ((MainActivity) getActivity()) .showSnackbarAbovePlayer(error.getLocalizedMessage(), Snackbar.LENGTH_LONG); }); - } } private Feed addLocalFolder(Uri uri) throws DownloadRequestException { @@ -219,4 +225,14 @@ public class AddFeedFragment extends Fragment { DBTasks.forceRefreshFeed(getContext(), fromDatabase, true); return fromDatabase; } + + private static class AddLocalFolder extends ActivityResultContracts.OpenDocumentTree { + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @NonNull + @Override + public Intent createIntent(@NonNull final Context context, @Nullable final Uri input) { + return super.createIntent(context, input) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java index 168133c7a..95e2eb1aa 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java @@ -25,6 +25,14 @@ import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.event.playback.BufferUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; +import de.danoeh.antennapod.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; +import de.danoeh.antennapod.playback.cast.CastEnabledActivity; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -34,25 +42,20 @@ import java.text.NumberFormat; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.CastEnabledActivity; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.FavoritesEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.event.FavoritesEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.model.feed.Chapter; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; -import de.danoeh.antennapod.core.util.playback.MediaPlayerError; import de.danoeh.antennapod.model.playback.Playable; -import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.dialog.PlaybackControlsDialog; import de.danoeh.antennapod.dialog.SkipPreferenceDialog; import de.danoeh.antennapod.dialog.SleepTimerDialog; @@ -224,8 +227,8 @@ public class AudioPlayerFragment extends Fragment implements } @Subscribe(threadMode = ThreadMode.MAIN) - public void onPlaybackServiceChanged(ServiceEvent event) { - if (event.action == ServiceEvent.Action.SERVICE_SHUT_DOWN) { + public void onPlaybackServiceChanged(PlaybackServiceEvent event) { + if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) { ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); } } @@ -243,14 +246,11 @@ public class AudioPlayerFragment extends Fragment implements }); } - protected void updatePlaybackSpeedButton(Playable media) { - if (butPlaybackSpeed == null || controller == null) { - return; - } - float speed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(media); - String speedStr = new DecimalFormat("0.00").format(speed); + @Subscribe(threadMode = ThreadMode.MAIN) + public void updatePlaybackSpeedButton(SpeedChangedEvent event) { + String speedStr = new DecimalFormat("0.00").format(event.getNewSpeed()); txtvPlaybackSpeed.setText(speedStr); - butPlaybackSpeed.setSpeed(speed); + butPlaybackSpeed.setSpeed(event.getNewSpeed()); } private void loadMediaInfo(boolean includingChapters) { @@ -282,47 +282,6 @@ public class AudioPlayerFragment extends Fragment implements private PlaybackController newPlaybackController() { return new PlaybackController(getActivity()) { @Override - public void onBufferStart() { - progressIndicator.setVisibility(View.VISIBLE); - } - - @Override - public void onBufferEnd() { - progressIndicator.setVisibility(View.GONE); - } - - @Override - public void onBufferUpdate(float progress) { - if (isStreaming()) { - sbPosition.setSecondaryProgress((int) (progress * sbPosition.getMax())); - } else { - sbPosition.setSecondaryProgress(0); - } - } - - @Override - public void handleError(int code) { - final AlertDialog.Builder errorDialog = new AlertDialog.Builder(getContext()); - errorDialog.setTitle(R.string.error_label); - errorDialog.setMessage(MediaPlayerError.getErrorString(getContext(), code)); - errorDialog.setPositiveButton(android.R.string.ok, (dialog, which) -> - ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED)); - if (!UserPreferences.useExoplayer()) { - errorDialog.setNeutralButton(R.string.media_player_switch_to_exoplayer, (dialog, which) -> { - UserPreferences.enableExoplayer(); - ((MainActivity) getActivity()).showSnackbarAbovePlayer( - R.string.media_player_switched_to_exoplayer, Snackbar.LENGTH_LONG); - }); - } - errorDialog.create().show(); - } - - @Override - public void onSleepTimerUpdate() { - AudioPlayerFragment.this.loadMediaInfo(false); - } - - @Override protected void updatePlayButtonShowsPlay(boolean showPlay) { butPlay.setIsShowPlay(showPlay); } @@ -336,25 +295,28 @@ public class AudioPlayerFragment extends Fragment implements public void onPlaybackEnd() { ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); } - - @Override - public void onPlaybackSpeedChange() { - updatePlaybackSpeedButton(getMedia()); - } }; } private void updateUi(Playable media) { - if (controller == null) { + if (controller == null || media == null) { return; } duration = controller.getDuration(); - updatePosition(new PlaybackPositionEvent(controller.getPosition(), duration)); - updatePlaybackSpeedButton(media); + updatePosition(new PlaybackPositionEvent(media.getPosition(), media.getDuration())); + updatePlaybackSpeedButton(new SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media))); setChapterDividers(media); setupOptionsMenu(media); } + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void sleepTimerUpdate(SleepTimerUpdatedEvent event) { + if (event.isCancelled() || event.wasJustEnabled()) { + AudioPlayerFragment.this.loadMediaInfo(false); + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -385,6 +347,20 @@ public class AudioPlayerFragment extends Fragment implements } @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void bufferUpdate(BufferUpdateEvent event) { + if (event.hasStarted()) { + progressIndicator.setVisibility(View.VISIBLE); + } else if (event.hasEnded()) { + progressIndicator.setVisibility(View.GONE); + } else if (controller != null && controller.isStreaming()) { + sbPosition.setSecondaryProgress((int) (event.getProgress() * sbPosition.getMax())); + } else { + sbPosition.setSecondaryProgress(0); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) public void updatePosition(PlaybackPositionEvent event) { if (controller == null || txtvPosition == null || txtvLength == null || sbPosition == null) { return; @@ -419,6 +395,23 @@ public class AudioPlayerFragment extends Fragment implements AudioPlayerFragment.this.loadMediaInfo(false); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void mediaPlayerError(PlayerErrorEvent event) { + final AlertDialog.Builder errorDialog = new AlertDialog.Builder(getContext()); + errorDialog.setTitle(R.string.error_label); + errorDialog.setMessage(event.getMessage()); + errorDialog.setPositiveButton(android.R.string.ok, (dialog, which) -> + ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED)); + if (!UserPreferences.useExoplayer()) { + errorDialog.setNeutralButton(R.string.media_player_switch_to_exoplayer, (dialog, which) -> { + UserPreferences.enableExoplayer(); + ((MainActivity) getActivity()).showSnackbarAbovePlayer( + R.string.media_player_switched_to_exoplayer, Snackbar.LENGTH_LONG); + }); + } + errorDialog.create().show(); + } + @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (controller == null || txtvLength == null) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java index de14f220e..04ad6e2bd 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -17,14 +17,14 @@ import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import de.danoeh.antennapod.playback.base.PlayerStatus; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChaptersListAdapter; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.model.feed.Chapter; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index 6c8baef29..933147378 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -23,10 +23,10 @@ import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.adapter.actionbutton.DeleteActionButton; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloadLogEvent; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; import de.danoeh.antennapod.model.feed.FeedItem; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index 8c2203f72..2d448faa8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -45,7 +45,7 @@ import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; @@ -110,9 +110,8 @@ public class CoverFragment extends Fragment { butNextChapter.setColorFilter(colorFilter); butPrevChapter.setColorFilter(colorFilter); descriptionIcon.setColorFilter(colorFilter); - ChaptersFragment chaptersFragment = new ChaptersFragment(); chapterControl.setOnClickListener(v -> - chaptersFragment.show(getChildFragmentManager(), ChaptersFragment.TAG)); + new ChaptersFragment().show(getChildFragmentManager(), ChaptersFragment.TAG)); butPrevChapter.setOnClickListener(v -> seekToPrevChapter()); butNextChapter.setOnClickListener(v -> seekToNextChapter()); @@ -156,8 +155,13 @@ public class CoverFragment extends Fragment { + "・" + "\u00A0" + StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0")); - Intent openFeed = MainActivity.getIntentToOpenFeed(requireContext(), ((FeedMedia) media).getItem().getFeedId()); - txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed)); + if (media instanceof FeedMedia) { + Intent openFeed = MainActivity.getIntentToOpenFeed(requireContext(), + ((FeedMedia) media).getItem().getFeedId()); + txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed)); + } else { + txtvPodcastTitle.setOnClickListener(null); + } txtvPodcastTitle.setOnLongClickListener(v -> copyText(media.getFeedTitle())); txtvEpisodeTitle.setText(media.getEpisodeTitle()); txtvEpisodeTitle.setOnLongClickListener(v -> copyText(media.getEpisodeTitle())); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java index 034b111e1..230a0ce0d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java @@ -23,7 +23,7 @@ import org.greenrobot.eventbus.EventBus; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.itunes.ItunesAdapter; -import de.danoeh.antennapod.core.event.DiscoveryDefaultUpdateEvent; +import de.danoeh.antennapod.event.DiscoveryDefaultUpdateEvent; import de.danoeh.antennapod.discovery.ItunesTopListLoader; import de.danoeh.antennapod.discovery.PodcastSearchResult; import io.reactivex.disposables.Disposable; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index ddbf6c078..5602dcb78 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -113,7 +113,7 @@ public class DownloadLogFragment extends ListFragment { if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { FeedMedia media = DBReader.getFeedMedia(downloadRequest.getFeedfileId()); FeedItem feedItem = media.getItem(); - feedItem.setAutoDownload(false); + feedItem.disableAutoDownload(); DBWriter.setFeedItem(feedItem); } } else if (item instanceof DownloadStatus) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 7ea76bb8d..37d77d31f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -21,10 +21,10 @@ import android.widget.TextView; import android.widget.Toast; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.view.EpisodeItemListRecyclerView; import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; @@ -40,7 +40,7 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.storage.DBWriter; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 8e070738c..1e24d62f7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -9,21 +9,23 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; + +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.bottomsheet.BottomSheetBehavior; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.model.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.playback.base.PlayerStatus; import de.danoeh.antennapod.view.PlayButton; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -77,8 +79,8 @@ public class ExternalPlayerFragment extends Fragment { } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); butPlay.setOnClickListener(v -> { if (controller == null) { return; @@ -97,12 +99,6 @@ public class ExternalPlayerFragment extends Fragment { private PlaybackController setupPlaybackController() { return new PlaybackController(getActivity()) { - - @Override - public void onPositionObserverUpdate() { - ExternalPlayerFragment.this.onPositionObserverUpdate(); - } - @Override protected void updatePlayButtonShowsPlay(boolean showPlay) { butPlay.setIsShowPlay(showPlay); @@ -140,13 +136,20 @@ public class ExternalPlayerFragment extends Fragment { } @Subscribe(threadMode = ThreadMode.MAIN) - public void onEventMainThread(PlaybackPositionEvent event) { - onPositionObserverUpdate(); + public void onPositionObserverUpdate(PlaybackPositionEvent event) { + if (controller == null) { + return; + } else if (controller.getPosition() == PlaybackService.INVALID_TIME + || controller.getDuration() == PlaybackService.INVALID_TIME) { + return; + } + progressBar.setProgress((int) + ((double) controller.getPosition() / controller.getDuration() * 100)); } @Subscribe(threadMode = ThreadMode.MAIN) - public void onPlaybackServiceChanged(ServiceEvent event) { - if (event.action == ServiceEvent.Action.SERVICE_SHUT_DOWN) { + public void onPlaybackServiceChanged(PlaybackServiceEvent event) { + if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) { ((MainActivity) getActivity()).setPlayerVisible(false); } } @@ -193,7 +196,7 @@ public class ExternalPlayerFragment extends Fragment { ((MainActivity) getActivity()).setPlayerVisible(true); txtvTitle.setText(media.getEpisodeTitle()); feedName.setText(media.getFeedTitle()); - onPositionObserverUpdate(); + onPositionObserverUpdate(new PlaybackPositionEvent(media.getPosition(), media.getDuration())); RequestOptions options = new RequestOptions() .placeholder(R.color.light_gray) @@ -218,15 +221,4 @@ public class ExternalPlayerFragment extends Fragment { ((MainActivity) getActivity()).getBottomSheet().setLocked(false); } } - - private void onPositionObserverUpdate() { - if (controller == null) { - return; - } else if (controller.getPosition() == PlaybackService.INVALID_TIME - || controller.getDuration() == PlaybackService.INVALID_TIME) { - return; - } - progressBar.setProgress((int) - ((double) controller.getPosition() / controller.getDuration() * 100)); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java index 986c417fd..d7bfd404d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -18,7 +18,7 @@ import org.greenrobot.eventbus.Subscribe; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.FavoritesEvent; +import de.danoeh.antennapod.event.FavoritesEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java index da7e7e633..ae298cc1c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.Context; @@ -10,62 +9,56 @@ import android.graphics.LightingColorFilter; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatDrawableManager; -import androidx.appcompat.widget.Toolbar; -import androidx.documentfile.provider.DocumentFile; -import androidx.fragment.app.Fragment; import android.text.TextUtils; -import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatDrawableManager; +import androidx.appcompat.widget.Toolbar; +import androidx.documentfile.provider.DocumentFile; +import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.snackbar.Snackbar; import com.joanzapata.iconify.Iconify; - -import org.apache.commons.lang3.StringUtils; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.model.feed.Feed; -import de.danoeh.antennapod.model.feed.FeedFunding; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.StatisticsItem; -import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; import de.danoeh.antennapod.fragment.preferences.StatisticsFragment; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.model.feed.FeedFunding; import de.danoeh.antennapod.view.ToolbarIconTintManager; import io.reactivex.Completable; import io.reactivex.Maybe; import io.reactivex.MaybeOnSubscribe; -import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.HashSet; -import java.util.List; -import java.util.Locale; /** * Displays information about a feed. @@ -74,28 +67,22 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; private static final String TAG = "FeedInfoActivity"; - private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; + private final ActivityResultLauncher<Uri> addLocalFolderLauncher = + registerForActivityResult(new AddLocalFolder(), this::addLocalFolderResult); private Feed feed; private Disposable disposable; - private Disposable disposableStatistics; private ImageView imgvCover; private TextView txtvTitle; private TextView txtvDescription; - private TextView lblStatistics; - private TextView txtvPodcastTime; - private TextView txtvPodcastSpace; - private TextView txtvPodcastEpisodeCount; private TextView txtvFundingUrl; private TextView lblSupport; - private Button btnvOpenStatistics; private TextView txtvUrl; private TextView txtvAuthorHeader; private ImageView imgvBackground; private View infoContainer; private View header; private Toolbar toolbar; - private ToolbarIconTintManager iconTintManager; public static FeedInfoFragment newInstance(Feed feed) { FeedInfoFragment fragment = new FeedInfoFragment(); @@ -133,7 +120,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic AppBarLayout appBar = root.findViewById(R.id.appBar); CollapsingToolbarLayout collapsingToolbar = root.findViewById(R.id.collapsing_toolbar); - iconTintManager = new ToolbarIconTintManager(getContext(), toolbar, collapsingToolbar) { + ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager(getContext(), toolbar, collapsingToolbar) { @Override protected void doTint(Context themedContext) { toolbar.getMenu().findItem(R.id.visit_website_item) @@ -157,23 +144,20 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); txtvDescription = root.findViewById(R.id.txtvDescription); - lblStatistics = root.findViewById(R.id.lblStatistics); - txtvPodcastSpace = root.findViewById(R.id.txtvPodcastSpaceUsed); - txtvPodcastEpisodeCount = root.findViewById(R.id.txtvPodcastEpisodeCount); - txtvPodcastTime = root.findViewById(R.id.txtvPodcastTime); - btnvOpenStatistics = root.findViewById(R.id.btnvOpenStatistics); txtvUrl = root.findViewById(R.id.txtvUrl); lblSupport = root.findViewById(R.id.lblSupport); txtvFundingUrl = root.findViewById(R.id.txtvFundingUrl); txtvUrl.setOnClickListener(copyUrlToClipboard); - btnvOpenStatistics.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatisticsFragment fragment = new StatisticsFragment(); - ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE); - } + long feedId = getArguments().getLong(EXTRA_FEED_ID); + getParentFragmentManager().beginTransaction().replace(R.id.statisticsFragmentContainer, + FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment") + .commitAllowingStateLoss(); + + root.findViewById(R.id.btnvOpenStatistics).setOnClickListener(view -> { + StatisticsFragment fragment = new StatisticsFragment(); + ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE); }); return root; @@ -195,7 +179,6 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic .subscribe(result -> { feed = result; showFeed(); - loadStatistics(); }, error -> Log.d(TAG, Log.getStackTraceString(error)), () -> { }); } @@ -270,53 +253,12 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic refreshToolbarState(); } - private void loadStatistics() { - if (disposableStatistics != null) { - disposableStatistics.dispose(); - } - - disposableStatistics = - Observable.fromCallable(() -> { - List<StatisticsItem> statisticsData = DBReader.getStatistics(); - - for (StatisticsItem statisticsItem : statisticsData) { - if (statisticsItem.feed.getId() == feed.getId()) { - return statisticsItem; - } - } - - return null; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - txtvPodcastTime.setText(Converter.shortLocalizedDuration( - getContext(), result.timePlayed)); - txtvPodcastSpace.setText(Formatter.formatShortFileSize( - getContext(), result.totalDownloadSize)); - txtvPodcastEpisodeCount.setText(String.format(Locale.getDefault(), - "%d%s", result.episodesDownloadCount, - getString(R.string.episodes_suffix))); - }, error -> { - Log.d(TAG, Log.getStackTraceString(error)); - lblStatistics.setVisibility(View.GONE); - txtvPodcastSpace.setVisibility(View.GONE); - txtvPodcastTime.setVisibility(View.GONE); - txtvPodcastEpisodeCount.setVisibility(View.GONE); - btnvOpenStatistics.setVisibility(View.GONE); - }); - } - @Override public void onDestroy() { super.onDestroy(); if (disposable != null) { disposable.dispose(); } - - if (disposableStatistics != null) { - disposableStatistics.dispose(); - } } private void refreshToolbarState() { @@ -351,9 +293,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic alert.setMessage(R.string.reconnect_local_folder_warning); alert.setPositiveButton(android.R.string.ok, (dialog, which) -> { try { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER); + addLocalFolderLauncher.launch(null); } catch (ActivityNotFoundException e) { Log.e(TAG, "No activity found. Should never happen..."); } @@ -366,16 +306,11 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic return handled; } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK || data == null) { + private void addLocalFolderResult(final Uri uri) { + if (uri == null) { return; } - Uri uri = data.getData(); - - if (requestCode == REQUEST_CODE_ADD_LOCAL_FOLDER) { - reconnectLocalFolder(uri); - } + reconnectLocalFolder(uri); } private void reconnectLocalFolder(Uri uri) { @@ -401,4 +336,14 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic error -> ((MainActivity) getActivity()) .showSnackbarAbovePlayer(error.getLocalizedMessage(), Snackbar.LENGTH_LONG)); } + + private static class AddLocalFolder extends ActivityResultContracts.OpenDocumentTree { + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @NonNull + @Override + public Intent createIntent(@NonNull final Context context, @Nullable final Uri input) { + return super.createIntent(context, input) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index 0ee60866d..148cf6582 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -52,13 +52,13 @@ import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FavoritesEvent; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FavoritesEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedEvent; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; @@ -333,8 +333,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem new RenameFeedDialog(getActivity(), feed).show(); return true; } else if (itemId == R.id.remove_item) { - RemoveFeedDialog.show(getContext(), feed, () -> - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null)); + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); + RemoveFeedDialog.show(getContext(), feed); return true; } else if (itemId == R.id.action_search) { ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(feed.getId(), feed.getTitle())); 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 dbc7f2ae3..0c2103d25 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -7,16 +7,19 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.preference.ListPreference; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.SwitchPreferenceCompat; import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.settings.SkipIntroEndingChangedEvent; -import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; -import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; +import de.danoeh.antennapod.event.settings.SkipIntroEndingChangedEvent; +import de.danoeh.antennapod.event.settings.SpeedPresetChangedEvent; +import de.danoeh.antennapod.event.settings.VolumeAdaptionChangedEvent; +import de.danoeh.antennapod.databinding.PlaybackSpeedFeedSettingDialogBinding; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedFilter; import de.danoeh.antennapod.model.feed.FeedPreferences; @@ -35,12 +38,9 @@ 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.Collections; import java.util.Locale; -import static de.danoeh.antennapod.model.feed.FeedPreferences.SPEED_USE_GLOBAL; - public class FeedSettingsFragment extends Fragment { private static final String TAG = "FeedSettingsFragment"; private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; @@ -104,8 +104,6 @@ public class FeedSettingsFragment extends Fragment { private static final String PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"; private static final String PREF_AUTO_SKIP = "feedAutoSkip"; private static final String PREF_TAGS = "tags"; - private static final DecimalFormat SPEED_FORMAT = - new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); private Feed feed; private Disposable disposable; @@ -164,7 +162,6 @@ public class FeedSettingsFragment extends Fragment { updateAutoDeleteSummary(); updateVolumeReductionValue(); updateAutoDownloadEnabled(); - updatePlaybackSpeedPreference(); if (feed.isLocalFeed()) { findPreference(PREF_AUTHENTICATION).setVisible(false); @@ -205,27 +202,34 @@ public class FeedSettingsFragment extends Fragment { } private void setupPlaybackSpeedPreference() { - ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); - - final String[] speeds = getResources().getStringArray(R.array.playback_speed_values); - String[] values = new String[speeds.length + 1]; - values[0] = SPEED_FORMAT.format(SPEED_USE_GLOBAL); - - String[] entries = new String[speeds.length + 1]; - entries[0] = getString(R.string.feed_auto_download_global); - - System.arraycopy(speeds, 0, values, 1, speeds.length); - System.arraycopy(speeds, 0, entries, 1, speeds.length); - - feedPlaybackSpeedPreference.setEntryValues(values); - feedPlaybackSpeedPreference.setEntries(entries); - feedPlaybackSpeedPreference.setOnPreferenceChangeListener((preference, newValue) -> { - feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) newValue)); - DBWriter.setFeedPreferences(feedPreferences); - updatePlaybackSpeedPreference(); - EventBus.getDefault().post( - new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId())); - return false; + Preference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); + feedPlaybackSpeedPreference.setOnPreferenceClickListener(preference -> { + PlaybackSpeedFeedSettingDialogBinding viewBinding = + PlaybackSpeedFeedSettingDialogBinding.inflate(getLayoutInflater()); + viewBinding.seekBar.setProgressChangedListener(speed -> + viewBinding.currentSpeedLabel.setText(String.format(Locale.getDefault(), "%.2fx", speed))); + float speed = feedPreferences.getFeedPlaybackSpeed(); + viewBinding.useGlobalCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> { + viewBinding.seekBar.setEnabled(!isChecked); + viewBinding.seekBar.setAlpha(isChecked ? 0.4f : 1f); + viewBinding.currentSpeedLabel.setAlpha(isChecked ? 0.4f : 1f); + }); + viewBinding.useGlobalCheckbox.setChecked(speed == FeedPreferences.SPEED_USE_GLOBAL); + viewBinding.seekBar.updateSpeed(speed == FeedPreferences.SPEED_USE_GLOBAL ? 1 : speed); + new AlertDialog.Builder(getContext()) + .setTitle(R.string.playback_speed) + .setView(viewBinding.getRoot()) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + float newSpeed = viewBinding.useGlobalCheckbox.isChecked() + ? FeedPreferences.SPEED_USE_GLOBAL : viewBinding.seekBar.getCurrentSpeed(); + feedPreferences.setFeedPlaybackSpeed(newSpeed); + DBWriter.setFeedPreferences(feedPreferences); + EventBus.getDefault().post( + new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId())); + }) + .setNegativeButton(R.string.cancel_label, null) + .show(); + return true; }); } @@ -277,13 +281,6 @@ public class FeedSettingsFragment extends Fragment { }); } - private void updatePlaybackSpeedPreference() { - ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); - - float speedValue = feedPreferences.getFeedPlaybackSpeed(); - feedPlaybackSpeedPreference.setValue(SPEED_FORMAT.format(speedValue)); - } - private void updateAutoDeleteSummary() { ListPreference autoDeletePreference = findPreference(PREF_AUTO_DELETE); @@ -395,7 +392,8 @@ public class FeedSettingsFragment extends Fragment { private void setupTags() { findPreference(PREF_TAGS).setOnPreferenceClickListener(preference -> { - TagSettingsDialog.newInstance(feedPreferences).show(getChildFragmentManager(), TagSettingsDialog.TAG); + TagSettingsDialog.newInstance(Collections.singletonList(feedPreferences)) + .show(getChildFragmentManager(), TagSettingsDialog.TAG); return true; }); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsDialogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsDialogFragment.java new file mode 100644 index 000000000..33710b2c4 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsDialogFragment.java @@ -0,0 +1,42 @@ +package de.danoeh.antennapod.fragment; + +import android.app.Dialog; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import de.danoeh.antennapod.R; + +public class FeedStatisticsDialogFragment extends DialogFragment { + private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; + private static final String EXTRA_FEED_TITLE = "de.danoeh.antennapod.extra.feedTitle"; + + public static FeedStatisticsDialogFragment newInstance(long feedId, String feedTitle) { + FeedStatisticsDialogFragment fragment = new FeedStatisticsDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putLong(EXTRA_FEED_ID, feedId); + arguments.putString(EXTRA_FEED_TITLE, feedTitle); + fragment.setArguments(arguments); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.setTitle(getArguments().getString(EXTRA_FEED_TITLE)); + dialog.setView(R.layout.feed_statistics_dialog); + return dialog.create(); + } + + @Override + public void onStart() { + super.onStart(); + long feedId = getArguments().getLong(EXTRA_FEED_ID); + getChildFragmentManager().beginTransaction().replace(R.id.statisticsContainer, + FeedStatisticsFragment.newInstance(feedId, true), "feed_statistics_fragment") + .commitAllowingStateLoss(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java new file mode 100644 index 000000000..e85c2a386 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java @@ -0,0 +1,93 @@ +package de.danoeh.antennapod.fragment; + +import android.os.Bundle; +import android.text.format.Formatter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +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.databinding.FeedStatisticsBinding; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.util.List; +import java.util.Locale; + +public class FeedStatisticsFragment extends Fragment { + private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; + private static final String EXTRA_DETAILED = "de.danoeh.antennapod.extra.detailed"; + + private long feedId; + private Disposable disposable; + private FeedStatisticsBinding viewBinding; + + public static FeedStatisticsFragment newInstance(long feedId, boolean detailed) { + FeedStatisticsFragment fragment = new FeedStatisticsFragment(); + Bundle arguments = new Bundle(); + arguments.putLong(EXTRA_FEED_ID, feedId); + arguments.putBoolean(EXTRA_DETAILED, detailed); + fragment.setArguments(arguments); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + feedId = getArguments().getLong(EXTRA_FEED_ID); + viewBinding = FeedStatisticsBinding.inflate(inflater); + + if (!getArguments().getBoolean(EXTRA_DETAILED)) { + for (int i = 0; i < viewBinding.getRoot().getChildCount(); i++) { + View child = viewBinding.getRoot().getChildAt(i); + if ("detailed".equals(child.getTag())) { + child.setVisibility(View.GONE); + } + } + } + + loadStatistics(); + return viewBinding.getRoot(); + } + + private void loadStatistics() { + disposable = + Observable.fromCallable(() -> { + List<StatisticsItem> statisticsData = DBReader.getStatistics(); + for (StatisticsItem statisticsItem : statisticsData) { + if (statisticsItem.feed.getId() == feedId) { + return statisticsItem; + } + } + return null; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::showStats, Throwable::printStackTrace); + } + + private void showStats(StatisticsItem s) { + viewBinding.startedTotalLabel.setText(String.format(Locale.getDefault(), "%d / %d", + s.episodesStarted, s.episodes)); + viewBinding.timePlayedLabel.setText(Converter.shortLocalizedDuration(getContext(), s.timePlayed)); + viewBinding.durationPlayedLabel.setText(Converter.shortLocalizedDuration(getContext(), s.timePlayedCountAll)); + viewBinding.totalDurationLabel.setText(Converter.shortLocalizedDuration(getContext(), s.time)); + viewBinding.onDeviceLabel.setText(String.format(Locale.getDefault(), "%d", s.episodesDownloadCount)); + viewBinding.spaceUsedLabel.setText(Formatter.formatShortFileSize(getContext(), s.totalDownloadSize)); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposable != null) { + disposable.dispose(); + } + } +} 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 31c6da8cd..c261370e2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -14,7 +14,6 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.text.TextUtilsCompat; import androidx.core.util.ObjectsCompat; @@ -42,9 +41,9 @@ import de.danoeh.antennapod.adapter.actionbutton.StreamActionButton; import de.danoeh.antennapod.adapter.actionbutton.VisitWebsiteActionButton; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; @@ -224,17 +223,6 @@ public class ItemFragment extends Fragment { } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - load(); - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - } - - @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); @@ -245,6 +233,7 @@ public class ItemFragment extends Fragment { } }; controller.init(); + load(); } @Override @@ -398,7 +387,7 @@ public class ItemFragment extends Fragment { long mediaId = item.getMedia().getId(); if (ArrayUtils.contains(update.mediaIds, mediaId)) { if (itemsLoaded && getActivity() != null) { - updateAppearance(); + updateButtons(); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java index d42300ca7..14f6ae875 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java @@ -20,7 +20,7 @@ import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java index 826a7e0ab..18defc545 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -28,9 +28,9 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.NavListAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.dialog.TagSettingsDialog; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -50,6 +50,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -157,16 +158,16 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS }; removeAllNewFlagsConfirmationDialog.createNewDialog().show(); return true; - } else if (itemId == R.id.add_to_folder) { - TagSettingsDialog.newInstance(feed.getPreferences()).show(getChildFragmentManager(), TagSettingsDialog.TAG); + } else if (itemId == R.id.edit_tags) { + TagSettingsDialog.newInstance(Collections.singletonList(feed.getPreferences())) + .show(getChildFragmentManager(), TagSettingsDialog.TAG); return true; } else if (itemId == R.id.rename_item) { new RenameFeedDialog(getActivity(), feed).show(); return true; } else if (itemId == R.id.remove_item) { - RemoveFeedDialog.show(getContext(), feed, () -> { - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); - }); + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); + RemoveFeedDialog.show(getContext(), feed); return true; } return super.onContextItemSelected(item); @@ -318,7 +319,7 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS ((MainActivity) getActivity()).getBottomSheet() .setState(BottomSheetBehavior.STATE_COLLAPSED); } else { - NavDrawerData.FolderDrawerItem folder = ((NavDrawerData.FolderDrawerItem) clickedItem); + NavDrawerData.TagDrawerItem folder = ((NavDrawerData.TagDrawerItem) clickedItem); if (openFolders.contains(folder.name)) { openFolders.remove(folder.name); } else { @@ -388,11 +389,11 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS for (NavDrawerData.DrawerItem item : items) { item.setLayer(layer); flatItems.add(item); - if (item.type == NavDrawerData.DrawerItem.Type.FOLDER) { - NavDrawerData.FolderDrawerItem folder = ((NavDrawerData.FolderDrawerItem) item); + if (item.type == NavDrawerData.DrawerItem.Type.TAG) { + NavDrawerData.TagDrawerItem folder = ((NavDrawerData.TagDrawerItem) item); folder.isOpen = openFolders.contains(folder.name); if (folder.isOpen) { - flatItems.addAll(makeFlatDrawerData(((NavDrawerData.FolderDrawerItem) item).children, layer + 1)); + flatItems.addAll(makeFlatDrawerData(((NavDrawerData.TagDrawerItem) item).children, layer + 1)); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java index 992b6930c..f3080f655 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java @@ -1,15 +1,20 @@ package de.danoeh.antennapod.fragment; +import android.content.Context; import android.content.Intent; import android.os.Bundle; + +import android.widget.AbsListView; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.appcompat.widget.SearchView; + import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.GridView; import android.widget.ProgressBar; @@ -110,6 +115,21 @@ public class OnlineSearchFragment extends Fragment { TextView txtvPoweredBy = root.findViewById(R.id.search_powered_by); txtvPoweredBy.setText(getString(R.string.search_powered_by, searchProvider.getName())); setupToolbar(root.findViewById(R.id.toolbar)); + + gridView.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (scrollState == SCROLL_STATE_TOUCH_SCROLL) { + InputMethodManager imm = (InputMethodManager) + getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + } + }); return root; } @@ -142,6 +162,11 @@ public class OnlineSearchFragment extends Fragment { return false; } }); + sv.setOnQueryTextFocusChangeListener((view, hasFocus) -> { + if (hasFocus) { + showInputMethod(view.findFocus()); + } + }); searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { @@ -192,4 +217,11 @@ public class OnlineSearchFragment extends Fragment { txtvEmpty.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); } + + private void showInputMethod(View view) { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, 0); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index 5e3d36c03..e1fa5eeb6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -16,11 +16,11 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackHistoryEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackHistoryEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; 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 1b7d236c6..b308db0f6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -33,11 +33,11 @@ import de.danoeh.antennapod.adapter.QueueRecyclerAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; import de.danoeh.antennapod.fragment.swipeactions.SwipeActions; @@ -247,8 +247,9 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); private void refreshToolbarState() { - toolbar.getMenu().findItem(R.id.queue_lock).setChecked(UserPreferences.isQueueLocked()); boolean keepSorted = UserPreferences.isQueueKeepSorted(); + toolbar.getMenu().findItem(R.id.queue_lock).setChecked(UserPreferences.isQueueLocked()); + toolbar.getMenu().findItem(R.id.queue_lock).setVisible(!keepSorted); toolbar.getMenu().findItem(R.id.queue_sort_random).setVisible(!keepSorted); toolbar.getMenu().findItem(R.id.queue_keep_sorted).setChecked(keepSorted); isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), @@ -635,11 +636,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi } @Override - public boolean isItemViewSwipeEnabled() { - return !UserPreferences.isQueueLocked(); - } - - @Override public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); // Check if drag finished diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java index 14f355b52..8bfcfd1ed 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java @@ -25,7 +25,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.FeedDiscoverAdapter; -import de.danoeh.antennapod.core.event.DiscoveryDefaultUpdateEvent; +import de.danoeh.antennapod.event.DiscoveryDefaultUpdateEvent; import de.danoeh.antennapod.discovery.ItunesTopListLoader; import de.danoeh.antennapod.discovery.PodcastSearchResult; import io.reactivex.disposables.Disposable; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index f8326d9c1..e43b6f314 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.fragment; + +import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -9,6 +11,7 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -26,10 +29,10 @@ import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.adapter.FeedSearchResultAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.FeedSearcher; @@ -70,7 +73,6 @@ public class SearchFragment extends Fragment { private SearchView searchView; private Handler automaticSearchDebouncer; private long lastQueryChange = 0; - /** * Create a new SearchFragment that searches all feeds. */ @@ -153,6 +155,22 @@ public class SearchFragment extends Fragment { if (getArguments().getString(ARG_QUERY, null) != null) { search(); } + searchView.setOnQueryTextFocusChangeListener((view, hasFocus) -> { + if (hasFocus) { + showInputMethod(view.findFocus()); + } + }); + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + InputMethodManager imm = (InputMethodManager) + getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(recyclerView.getWindowToken(), 0); + } + } + }); return layout; } @@ -320,4 +338,11 @@ public class SearchFragment extends Fragment { List<Feed> feeds = FeedSearcher.searchFeeds(getContext(), query); return new Pair<>(items, feeds); } + + private void showInputMethod(View view) { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, 0); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index ea6c2ca0d..c4ac25455 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -33,6 +33,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.Callable; @@ -42,8 +43,8 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.SubscriptionsRecyclerAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; @@ -250,8 +251,8 @@ public class SubscriptionFragment extends Fragment } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onViewCreated(@NonNull View v, Bundle savedInstanceState) { + super.onViewCreated(v, savedInstanceState); subscriptionAdapter = new SubscriptionsRecyclerAdapter((MainActivity) getActivity()); subscriptionAdapter.setOnSelectModeListener(this); subscriptionRecycler.setAdapter(subscriptionAdapter); @@ -293,9 +294,9 @@ public class SubscriptionFragment extends Fragment NavDrawerData data = DBReader.getNavDrawerData(); List<NavDrawerData.DrawerItem> items = data.items; for (NavDrawerData.DrawerItem item : items) { - if (item.type == NavDrawerData.DrawerItem.Type.FOLDER + if (item.type == NavDrawerData.DrawerItem.Type.TAG && item.getTitle().equals(displayedFolder)) { - return ((NavDrawerData.FolderDrawerItem) item).children; + return ((NavDrawerData.TagDrawerItem) item).children; } } return items; @@ -344,14 +345,15 @@ public class SubscriptionFragment extends Fragment R.string.remove_all_new_flags_confirmation_msg, () -> DBWriter.removeFeedNewFlag(feed.getId())); return true; - } else if (itemId == R.id.add_to_folder) { - TagSettingsDialog.newInstance(feed.getPreferences()).show(getChildFragmentManager(), TagSettingsDialog.TAG); + } else if (itemId == R.id.edit_tags) { + TagSettingsDialog.newInstance(Collections.singletonList(feed.getPreferences())) + .show(getChildFragmentManager(), TagSettingsDialog.TAG); return true; } else if (itemId == R.id.rename_item) { new RenameFeedDialog(getActivity(), feed).show(); return true; } else if (itemId == R.id.remove_item) { - RemoveFeedDialog.show(getContext(), feed, null); + RemoveFeedDialog.show(getContext(), feed); return true; } else if (itemId == R.id.multi_select) { speedDialView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java b/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java index f160b2241..e3dfe8ade 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java @@ -3,19 +3,23 @@ package de.danoeh.antennapod.fragment.actions; import android.util.Log; import androidx.annotation.PluralsRes; +import androidx.appcompat.app.AlertDialog; import androidx.core.util.Consumer; import com.google.android.material.snackbar.Snackbar; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.databinding.PlaybackSpeedFeedSettingDialogBinding; import de.danoeh.antennapod.dialog.RemoveFeedDialog; +import de.danoeh.antennapod.dialog.TagSettingsDialog; import de.danoeh.antennapod.fragment.preferences.dialog.PreferenceListDialog; import de.danoeh.antennapod.fragment.preferences.dialog.PreferenceSwitchDialog; import de.danoeh.antennapod.model.feed.Feed; @@ -33,7 +37,7 @@ public class FeedMultiSelectActionHandler { public void handleAction(int id) { if (id == R.id.remove_item) { - RemoveFeedDialog.show(activity, selectedItems, null); + RemoveFeedDialog.show(activity, selectedItems); } else if (id == R.id.keep_updated) { keepUpdatedPrefHandler(); } else if (id == R.id.autodownload) { @@ -42,6 +46,8 @@ public class FeedMultiSelectActionHandler { autoDeleteEpisodesPrefHandler(); } else if (id == R.id.playback_speed) { playbackSpeedPrefHandler(); + } else if (id == R.id.edit_tags) { + editFeedPrefTags(); } else { Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + id); } @@ -64,25 +70,26 @@ public class FeedMultiSelectActionHandler { new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); private void playbackSpeedPrefHandler() { - final String[] speeds = activity.getResources().getStringArray(R.array.playback_speed_values); - String[] values = new String[speeds.length + 1]; - values[0] = SPEED_FORMAT.format(FeedPreferences.SPEED_USE_GLOBAL); - - String[] entries = new String[speeds.length + 1]; - entries[0] = activity.getString(R.string.feed_auto_download_global); - - System.arraycopy(speeds, 0, values, 1, speeds.length); - System.arraycopy(speeds, 0, entries, 1, speeds.length); - - PreferenceListDialog preferenceListDialog = new PreferenceListDialog(activity, - activity.getString(R.string.playback_speed)); - preferenceListDialog.openDialog(entries); - preferenceListDialog.setOnPreferenceChangedListener(pos -> { - saveFeedPreferences(feedPreferences -> { - feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) values[pos])); - }); - + PlaybackSpeedFeedSettingDialogBinding viewBinding = + PlaybackSpeedFeedSettingDialogBinding.inflate(activity.getLayoutInflater()); + viewBinding.seekBar.setProgressChangedListener(speed -> + viewBinding.currentSpeedLabel.setText(String.format(Locale.getDefault(), "%.2fx", speed))); + viewBinding.useGlobalCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> { + viewBinding.seekBar.setEnabled(!isChecked); + viewBinding.seekBar.setAlpha(isChecked ? 0.4f : 1f); + viewBinding.currentSpeedLabel.setAlpha(isChecked ? 0.4f : 1f); }); + viewBinding.seekBar.updateSpeed(1.0f); + new AlertDialog.Builder(activity) + .setTitle(R.string.playback_speed) + .setView(viewBinding.getRoot()) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + float newSpeed = viewBinding.useGlobalCheckbox.isChecked() + ? FeedPreferences.SPEED_USE_GLOBAL : viewBinding.seekBar.getCurrentSpeed(); + saveFeedPreferences(feedPreferences -> feedPreferences.setFeedPlaybackSpeed(newSpeed)); + }) + .setNegativeButton(R.string.cancel_label, null) + .show(); } private void autoDeleteEpisodesPrefHandler() { @@ -136,4 +143,13 @@ public class FeedMultiSelectActionHandler { } showMessage(R.plurals.updated_feeds_batch_label, selectedItems.size()); } + + private void editFeedPrefTags() { + ArrayList<FeedPreferences> preferencesList = new ArrayList<>(); + for (Feed feed : selectedItems) { + preferencesList.add(feed.getPreferences()); + } + TagSettingsDialog.newInstance(preferencesList).show(activity.getSupportFragmentManager(), + TagSettingsDialog.TAG); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index c813cbf7a..c2c5adc9a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -15,7 +15,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException; @@ -76,8 +76,8 @@ public abstract class PodcastListFragment extends Fragment { disposable = Observable.fromCallable( () -> { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); return loadPodcastData(service); }) .subscribeOn(Schedulers.io()) @@ -101,7 +101,7 @@ public abstract class PodcastListFragment extends Fragment { }, error -> { gridView.setVisibility(View.GONE); progressBar.setVisibility(View.GONE); - txtvError.setText(getString(R.string.error_msg_prefix) + error.getMessage()); + txtvError.setText(error.getMessage()); txtvError.setVisibility(View.VISIBLE); butRetry.setVisibility(View.VISIBLE); Log.e(TAG, Log.getStackTraceString(error)); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index f961e30bb..abdfab941 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import androidx.fragment.app.ListFragment; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetTag; @@ -51,8 +51,8 @@ public class TagListFragment extends ListFragment { disposable = Observable.fromCallable( () -> { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); return service.getTopTags(COUNT); }) .subscribeOn(Schedulers.io()) 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 deleted file mode 100644 index 4fb734e17..000000000 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java +++ /dev/null @@ -1,128 +0,0 @@ -package de.danoeh.antennapod.fragment.preferences; - -import android.app.Activity; -import android.os.Bundle; -import androidx.core.text.HtmlCompat; -import androidx.preference.PreferenceFragmentCompat; - -import android.text.Spanned; -import android.text.format.DateUtils; -import com.google.android.material.snackbar.Snackbar; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.event.SyncServiceEvent; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.sync.SyncService; -import de.danoeh.antennapod.dialog.AuthenticationDialog; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -public class GpodderPreferencesFragment extends PreferenceFragmentCompat { - private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate"; - private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; - private static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync"; - private static final String PREF_GPODNET_FORCE_FULL_SYNC = "pref_gpodnet_force_full_sync"; - private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences_gpodder); - setupGpodderScreen(); - } - - @Override - public void onStart() { - super.onStart(); - ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.gpodnet_main_label); - updateGpodnetPreferenceScreen(); - EventBus.getDefault().register(this); - } - - @Override - public void onStop() { - super.onStop(); - EventBus.getDefault().unregister(this); - ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(""); - } - - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) - public void syncStatusChanged(SyncServiceEvent event) { - updateGpodnetPreferenceScreen(); - if (!GpodnetPreferences.loggedIn()) { - return; - } - if (event.getMessageResId() == R.string.sync_status_error - || event.getMessageResId() == R.string.sync_status_success) { - updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()), - SyncService.getLastSyncAttempt(getContext())); - } else { - ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(event.getMessageResId()); - } - } - - private void setupGpodderScreen() { - final Activity activity = getActivity(); - - findPreference(PREF_GPODNET_LOGIN).setOnPreferenceClickListener(preference -> { - new GpodderAuthenticationFragment().show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG); - return true; - }); - findPreference(PREF_GPODNET_SETLOGIN_INFORMATION) - .setOnPreferenceClickListener(preference -> { - AuthenticationDialog dialog = new AuthenticationDialog(activity, - R.string.pref_gpodnet_setlogin_information_title, false, GpodnetPreferences.getUsername(), - null) { - - @Override - protected void onConfirmed(String username, String password) { - GpodnetPreferences.setPassword(password); - } - }; - dialog.show(); - return true; - }); - findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> { - SyncService.syncImmediately(getActivity().getApplicationContext()); - return true; - }); - findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> { - SyncService.fullSync(getContext()); - return true; - }); - findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> { - GpodnetPreferences.logout(); - Snackbar.make(getView(), R.string.pref_gpodnet_logout_toast, Snackbar.LENGTH_LONG).show(); - updateGpodnetPreferenceScreen(); - return true; - }); - } - - private void updateGpodnetPreferenceScreen() { - final boolean loggedIn = GpodnetPreferences.loggedIn(); - findPreference(PREF_GPODNET_LOGIN).setEnabled(!loggedIn); - findPreference(PREF_GPODNET_SETLOGIN_INFORMATION).setEnabled(loggedIn); - findPreference(PREF_GPODNET_SYNC).setEnabled(loggedIn); - findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setEnabled(loggedIn); - findPreference(PREF_GPODNET_LOGOUT).setEnabled(loggedIn); - if (loggedIn) { - String format = getActivity().getString(R.string.pref_gpodnet_login_status); - String summary = String.format(format, GpodnetPreferences.getUsername(), - GpodnetPreferences.getDeviceID()); - Spanned formattedSummary = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_LEGACY); - findPreference(PREF_GPODNET_LOGOUT).setSummary(formattedSummary); - updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()), - SyncService.getLastSyncAttempt(getContext())); - } else { - findPreference(PREF_GPODNET_LOGOUT).setSummary(null); - } - } - - private void updateLastGpodnetSyncReport(boolean successful, long lastTime) { - String status = String.format("%1$s (%2$s)", getString(successful - ? R.string.gpodnetsync_pref_report_successful : R.string.gpodnetsync_pref_report_failed), - DateUtils.getRelativeDateTimeString(getContext(), - lastTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME)); - ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(status); - } -} 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 index f6aa45e93..b72d1eb32 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java @@ -12,6 +12,13 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.util.Log; + +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.activity.result.contract.ActivityResultContracts.GetContent; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.FileProvider; import androidx.preference.PreferenceFragmentCompat; @@ -54,13 +61,19 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds-%s.html"; private static final String CONTENT_TYPE_HTML = "text/html"; private static final String DEFAULT_FAVORITES_OUTPUT_NAME = "antennapod-favorites-%s.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 int REQUEST_CODE_CHOOSE_FAVORITES_EXPORT_PATH = 6; private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup-%s.db"; + private final ActivityResultLauncher<Intent> chooseOpmlExportPathLauncher = + registerForActivityResult(new StartActivityForResult(), this::chooseOpmlExportPathResult); + private final ActivityResultLauncher<Intent> chooseHtmlExportPathLauncher = + registerForActivityResult(new StartActivityForResult(), this::chooseHtmlExportPathResult); + private final ActivityResultLauncher<Intent> chooseFavoritesExportPathLauncher = + registerForActivityResult(new StartActivityForResult(), this::chooseFavoritesExportPathResult); + private final ActivityResultLauncher<Intent> restoreDatabaseLauncher = + registerForActivityResult(new StartActivityForResult(), this::restoreDatabaseResult); + private final ActivityResultLauncher<String> backupDatabaseLauncher = + registerForActivityResult(new BackupDatabase(), this::backupDatabaseResult); + private final ActivityResultLauncher<String> chooseOpmlImportPathLauncher = + registerForActivityResult(new GetContent(), this::chooseOpmlImportPathResult); private Disposable disposable; private ProgressDialog progressDialog; @@ -95,23 +108,20 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( preference -> { openExportPathPicker(CONTENT_TYPE_OPML, dateStampFilename(DEFAULT_OPML_OUTPUT_NAME), - REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH, new OpmlWriter()); + chooseOpmlExportPathLauncher, new OpmlWriter()); return true; } ); findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( preference -> { openExportPathPicker(CONTENT_TYPE_HTML, dateStampFilename(DEFAULT_HTML_OUTPUT_NAME), - REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH, new HtmlWriter()); + chooseHtmlExportPathLauncher, 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); + chooseOpmlImportPathLauncher.launch("*/*"); } catch (ActivityNotFoundException e) { Log.e(TAG, "No activity found. Should never happen..."); } @@ -130,7 +140,7 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { findPreference(PREF_FAVORITE_EXPORT).setOnPreferenceClickListener( preference -> { openExportPathPicker(CONTENT_TYPE_HTML, dateStampFilename(DEFAULT_FAVORITES_OUTPUT_NAME), - REQUEST_CODE_CHOOSE_FAVORITES_EXPORT_PATH, new FavoritesWriter()); + chooseFavoritesExportPathLauncher, new FavoritesWriter()); return true; }); } @@ -160,12 +170,7 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { 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, dateStampFilename(DATABASE_EXPORT_FILENAME)); - - startActivityForResult(intent, REQUEST_CODE_BACKUP_DATABASE); + backupDatabaseLauncher.launch(dateStampFilename(DATABASE_EXPORT_FILENAME)); } else { File sd = Environment.getExternalStorageDirectory(); File backupDB = new File(sd, dateStampFilename(DATABASE_EXPORT_FILENAME)); @@ -190,18 +195,10 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { // add a button builder.setNegativeButton(R.string.no, null); builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - 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); - } - } - ); + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + restoreDatabaseLauncher.launch(intent); + }); // create and show the alert dialog builder.show(); @@ -227,15 +224,14 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { 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); - } + Intent chooserIntent = Intent.createChooser(sendIntent, getString(R.string.send_label)); + 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))); + getContext().startActivity(chooserIntent); }); alert.create().show(); } @@ -249,64 +245,97 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { alert.show(); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK || data == null) { + private void chooseOpmlExportPathResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { return; } - Uri uri = data.getData(); + final Uri uri = result.getData().getData(); + exportWithWriter(new OpmlWriter(), uri); + } - 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_CHOOSE_FAVORITES_EXPORT_PATH) { - exportWithWriter(new FavoritesWriter(), 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(); - UserPreferences.unsetUsageCountingDate(); - 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 chooseHtmlExportPathResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; } + final Uri uri = result.getData().getData(); + exportWithWriter(new HtmlWriter(), uri); } - 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); + private void chooseFavoritesExportPathResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; + } + final Uri uri = result.getData().getData(); + exportWithWriter(new FavoritesWriter(), uri); + } - // 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..."); - } + private void restoreDatabaseResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; + } + final Uri uri = result.getData().getData(); + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + showDatabaseImportSuccessDialog(); + UserPreferences.unsetUsageCountingDate(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } + + private void backupDatabaseResult(final Uri uri) { + if (uri == null) { + return; + } + 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); + } + + private void chooseOpmlImportPathResult(final Uri uri) { + if (uri == null) { + return; + } + final Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + + private void openExportPathPicker(String contentType, String title, + final ActivityResultLauncher<Intent> result, ExportWriter writer) { + 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 { + result.launch(intentPickAction); + 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); } + + private static class BackupDatabase extends ActivityResultContracts.CreateDocument { + @NonNull + @Override + public Intent createIntent(@NonNull final Context context, @NonNull final String input) { + return super.createIntent(context, input) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("application/x-sqlite3"); + } + } } 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 cc09acbca..891d3737b 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 @@ -1,6 +1,8 @@ package de.danoeh.antennapod.fragment.preferences; import android.content.Intent; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; @@ -17,12 +19,11 @@ import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.fragment.preferences.about.AboutFragment; public class MainPreferencesFragment extends PreferenceFragmentCompat { - private static final String TAG = "MainPreferencesFragment"; private static final String PREF_SCREEN_USER_INTERFACE = "prefScreenInterface"; private static final String PREF_SCREEN_PLAYBACK = "prefScreenPlayback"; private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork"; - private static final String PREF_SCREEN_GPODDER = "prefScreenGpodder"; + private static final String PREF_SCREEN_SYNCHRONIZATION = "prefScreenSynchronization"; private static final String PREF_SCREEN_STORAGE = "prefScreenStorage"; private static final String PREF_DOCUMENTATION = "prefDocumentation"; private static final String PREF_VIEW_FORUM = "prefViewForum"; @@ -43,15 +44,26 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { // and afterwards remove the following lines. Please keep in mind that AntennaPod is licensed under the GPL. // This means that your application needs to be open-source under the GPL, too. // It must also include a prominent copyright notice. - String packageName = getContext().getPackageName(); - if (!"de.danoeh.antennapod".equals(packageName) && !"de.danoeh.antennapod.debug".equals(packageName)) { + int packageHash = getContext().getPackageName().hashCode(); + if (packageHash != 1790437538 && packageHash != -1190467065) { findPreference(PREF_CATEGORY_PROJECT).setVisible(false); Preference copyrightNotice = new Preference(getContext()); + copyrightNotice.setIcon(R.drawable.ic_info_white); + copyrightNotice.getIcon().mutate() + .setColorFilter(new PorterDuffColorFilter(0xffcc0000, PorterDuff.Mode.MULTIPLY)); copyrightNotice.setSummary("This application is based on AntennaPod." + " The AntennaPod team does NOT provide support for this unofficial version." + " If you can read this message, the developers of this modification" + " violate the GNU General Public License (GPL)."); findPreference(PREF_CATEGORY_PROJECT).getParent().addPreference(copyrightNotice); + } else if (packageHash == -1190467065) { + Preference debugNotice = new Preference(getContext()); + debugNotice.setIcon(R.drawable.ic_info_white); + debugNotice.getIcon().mutate() + .setColorFilter(new PorterDuffColorFilter(0xffcc0000, PorterDuff.Mode.MULTIPLY)); + debugNotice.setOrder(-1); + debugNotice.setSummary("This is a development version of AntennaPod and not meant for daily use"); + findPreference(PREF_CATEGORY_PROJECT).getParent().addPreference(debugNotice); } } @@ -74,8 +86,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_network); return true; }); - findPreference(PREF_SCREEN_GPODDER).setOnPreferenceClickListener(preference -> { - ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_gpodder); + findPreference(PREF_SCREEN_SYNCHRONIZATION).setOnPreferenceClickListener(preference -> { + ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_synchronization); return true; }); findPreference(PREF_SCREEN_STORAGE).setOnPreferenceClickListener(preference -> { @@ -142,8 +154,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)) .addBreadcrumb(R.string.automation) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_autodownload)); - config.index(R.xml.preferences_gpodder) - .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_gpodder)); + config.index(R.xml.preferences_synchronization) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_synchronization)); config.index(R.xml.preferences_notifications) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_notifications)); config.index(R.xml.feed_settings) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java index 94e151f7a..ba17cedb2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java @@ -4,11 +4,10 @@ import android.os.Bundle; import androidx.preference.PreferenceFragmentCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; public class NotificationPreferencesFragment extends PreferenceFragmentCompat { - private static final String TAG = "NotificationPrefFragment"; private static final String PREF_GPODNET_NOTIFICATIONS = "pref_gpodnet_notifications"; @Override @@ -24,7 +23,6 @@ public class NotificationPreferencesFragment extends PreferenceFragmentCompat { } private void setUpScreen() { - final boolean loggedIn = GpodnetPreferences.loggedIn(); - findPreference(PREF_GPODNET_NOTIFICATIONS).setEnabled(loggedIn); + findPreference(PREF_GPODNET_NOTIFICATIONS).setEnabled(SynchronizationSettings.isProviderConnected()); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java index 1fa1fed58..7fa2ed4d1 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java @@ -10,13 +10,12 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UsageStatistics; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; import de.danoeh.antennapod.dialog.SkipPreferenceDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; -import de.danoeh.antennapod.preferences.PreferenceControllerFlavorHelper; import java.util.Map; import org.greenrobot.eventbus.EventBus; @@ -31,7 +30,6 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { addPreferencesFromResource(R.xml.preferences_playback); setupPlaybackScreen(); - PreferenceControllerFlavorHelper.setupFlavoredUI(this); buildSmartMarkAsPlayedPreference(); } 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 208ede8cc..04324f709 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 @@ -68,7 +68,7 @@ public class PlaybackStatisticsFragment extends Fragment { View root = inflater.inflate(R.layout.statistics_activity, container, false); feedStatisticsList = root.findViewById(R.id.statistics_list); progressBar = root.findViewById(R.id.progressBar); - listAdapter = new PlaybackStatisticsListAdapter(getContext()); + listAdapter = new PlaybackStatisticsListAdapter(this); listAdapter.setCountAll(countAll); feedStatisticsList.setLayoutManager(new LinearLayoutManager(getContext())); feedStatisticsList.setAdapter(listAdapter); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java index 04b9677e2..ff974179e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java @@ -10,8 +10,8 @@ import androidx.preference.PreferenceFragmentCompat; import android.widget.ListView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.FeedSortDialog; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/GpodderAuthenticationFragment.java index c0bf3e0ea..9dfe6840c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/GpodderAuthenticationFragment.java @@ -1,4 +1,4 @@ -package de.danoeh.antennapod.fragment.preferences; +package de.danoeh.antennapod.fragment.preferences.synchronization; import android.app.Dialog; import android.content.Context; @@ -15,30 +15,35 @@ import android.widget.ProgressBar; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.ViewFlipper; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; + import com.google.android.material.button.MaterialButton; import com.google.android.material.textfield.TextInputLayout; + +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.sync.SyncService; -import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; -import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice; +import de.danoeh.antennapod.core.sync.SynchronizationProviderViewData; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; import de.danoeh.antennapod.core.util.FileNameGenerator; import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice; import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * Guides the user through the authentication process. */ @@ -83,23 +88,24 @@ public class GpodderAuthenticationFragment extends DialogFragment { final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup); final EditText serverUrlText = view.findViewById(R.id.serverUrlText); - if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHosturl())) { - serverUrlText.setText(GpodnetPreferences.getHosturl()); + if (!GpodnetService.DEFAULT_BASE_HOST.equals(SynchronizationCredentials.getHosturl())) { + serverUrlText.setText(SynchronizationCredentials.getHosturl()); } final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput); serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE); }); selectHost.setOnClickListener(v -> { + SynchronizationCredentials.clear(getContext()); if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) { - GpodnetPreferences.setHosturl(serverUrlText.getText().toString()); + SynchronizationCredentials.setHosturl(serverUrlText.getText().toString()); } else { - GpodnetPreferences.setHosturl(GpodnetService.DEFAULT_BASE_HOST); + SynchronizationCredentials.setHosturl(GpodnetService.DEFAULT_BASE_HOST); } service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); - getDialog().setTitle(GpodnetPreferences.getHosturl()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); + getDialog().setTitle(SynchronizationCredentials.getHosturl()); advance(); }); } @@ -116,7 +122,7 @@ public class GpodderAuthenticationFragment extends DialogFragment { createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/")); - if (GpodnetPreferences.getHosturl().startsWith("http://")) { + if (SynchronizationCredentials.getHosturl().startsWith("http://")) { createAccountWarning.setVisibility(View.VISIBLE); } password.setOnEditorActionListener((v, actionID, event) -> @@ -265,15 +271,8 @@ public class GpodderAuthenticationFragment extends DialogFragment { }); } - private void writeLoginCredentials() { - GpodnetPreferences.setUsername(username); - GpodnetPreferences.setPassword(password); - GpodnetPreferences.setDeviceID(selectedDevice.getId()); - } - private void advance() { if (currentStep < STEP_FINISH) { - View view = viewFlipper.getChildAt(currentStep + 1); if (currentStep == STEP_DEFAULT) { setupHostView(view); @@ -289,7 +288,10 @@ public class GpodderAuthenticationFragment extends DialogFragment { if (selectedDevice == null) { throw new IllegalStateException("Device must not be null here"); } else { - writeLoginCredentials(); + SynchronizationSettings.setSelectedSyncProvider(SynchronizationProviderViewData.GPODDER_NET); + SynchronizationCredentials.setUsername(username); + SynchronizationCredentials.setPassword(password); + SynchronizationCredentials.setDeviceID(selectedDevice.getId()); setupFinishView(view); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/NextcloudAuthenticationFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/NextcloudAuthenticationFragment.java new file mode 100644 index 000000000..2e9260c1d --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/NextcloudAuthenticationFragment.java @@ -0,0 +1,92 @@ +package de.danoeh.antennapod.fragment.preferences.synchronization; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; +import de.danoeh.antennapod.core.sync.SynchronizationProviderViewData; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; +import de.danoeh.antennapod.databinding.NextcloudAuthDialogBinding; +import de.danoeh.antennapod.net.sync.nextcloud.NextcloudLoginFlow; + +/** + * Guides the user through the authentication process. + */ +public class NextcloudAuthenticationFragment extends DialogFragment + implements NextcloudLoginFlow.AuthenticationCallback { + public static final String TAG = "NextcloudAuthenticationFragment"; + private NextcloudAuthDialogBinding viewBinding; + private NextcloudLoginFlow nextcloudLoginFlow; + private boolean shouldDismiss = false; + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + dialog.setTitle(R.string.gpodnetauth_login_butLabel); + dialog.setNegativeButton(R.string.cancel_label, null); + dialog.setCancelable(false); + this.setCancelable(false); + + viewBinding = NextcloudAuthDialogBinding.inflate(getLayoutInflater()); + dialog.setView(viewBinding.getRoot()); + + viewBinding.loginButton.setOnClickListener(v -> { + viewBinding.errorText.setVisibility(View.GONE); + viewBinding.loginButton.setVisibility(View.GONE); + viewBinding.loginProgressContainer.setVisibility(View.VISIBLE); + nextcloudLoginFlow = new NextcloudLoginFlow(AntennapodHttpClient.getHttpClient(), + viewBinding.serverUrlText.getText().toString(), getContext(), this); + nextcloudLoginFlow.start(); + }); + + return dialog.create(); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + super.onDismiss(dialog); + if (nextcloudLoginFlow != null) { + nextcloudLoginFlow.cancel(); + } + } + + @Override + public void onResume() { + super.onResume(); + if (shouldDismiss) { + dismiss(); + } + } + + @Override + public void onNextcloudAuthenticated(String server, String username, String password) { + SynchronizationSettings.setSelectedSyncProvider(SynchronizationProviderViewData.NEXTCLOUD_GPODDER); + SynchronizationCredentials.clear(getContext()); + SynchronizationCredentials.setPassword(password); + SynchronizationCredentials.setHosturl(server); + SynchronizationCredentials.setUsername(username); + SyncService.fullSync(getContext()); + if (isVisible()) { + dismiss(); + } else { + shouldDismiss = true; + } + } + + @Override + public void onNextcloudAuthError(String errorMessage) { + viewBinding.loginProgressContainer.setVisibility(View.GONE); + viewBinding.errorText.setVisibility(View.VISIBLE); + viewBinding.errorText.setText(errorMessage); + viewBinding.loginButton.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/SynchronizationPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/SynchronizationPreferencesFragment.java new file mode 100644 index 000000000..8cb7f45db --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/SynchronizationPreferencesFragment.java @@ -0,0 +1,222 @@ +package de.danoeh.antennapod.fragment.preferences.synchronization; + +import android.app.Activity; +import android.os.Bundle; +import android.text.Spanned; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.text.HtmlCompat; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.google.android.material.snackbar.Snackbar; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.event.SyncServiceEvent; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.SynchronizationProviderViewData; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; +import de.danoeh.antennapod.dialog.AuthenticationDialog; + +public class SynchronizationPreferencesFragment extends PreferenceFragmentCompat { + private static final String PREFERENCE_SYNCHRONIZATION_DESCRIPTION = "preference_synchronization_description"; + private static final String PREFERENCE_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; + private static final String PREFERENCE_SYNC = "pref_synchronization_sync"; + private static final String PREFERENCE_FORCE_FULL_SYNC = "pref_synchronization_force_full_sync"; + private static final String PREFERENCE_LOGOUT = "pref_synchronization_logout"; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences_synchronization); + setupScreen(); + updateScreen(); + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.synchronization_pref); + updateScreen(); + EventBus.getDefault().register(this); + } + + @Override + public void onStop() { + super.onStop(); + EventBus.getDefault().unregister(this); + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(""); + } + + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + public void syncStatusChanged(SyncServiceEvent event) { + if (!SynchronizationSettings.isProviderConnected()) { + return; + } + updateScreen(); + if (event.getMessageResId() == R.string.sync_status_error + || event.getMessageResId() == R.string.sync_status_success) { + updateLastSyncReport(SynchronizationSettings.isLastSyncSuccessful(), + SynchronizationSettings.getLastSyncAttempt()); + } else { + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(event.getMessageResId()); + } + } + + private void setupScreen() { + final Activity activity = getActivity(); + findPreference(PREFERENCE_GPODNET_SETLOGIN_INFORMATION) + .setOnPreferenceClickListener(preference -> { + AuthenticationDialog dialog = new AuthenticationDialog(activity, + R.string.pref_gpodnet_setlogin_information_title, + false, SynchronizationCredentials.getUsername(), null) { + @Override + protected void onConfirmed(String username, String password) { + SynchronizationCredentials.setPassword(password); + } + }; + dialog.show(); + return true; + }); + findPreference(PREFERENCE_SYNC).setOnPreferenceClickListener(preference -> { + SyncService.syncImmediately(getActivity().getApplicationContext()); + return true; + }); + findPreference(PREFERENCE_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> { + SyncService.fullSync(getContext()); + return true; + }); + findPreference(PREFERENCE_LOGOUT).setOnPreferenceClickListener(preference -> { + SynchronizationCredentials.clear(getContext()); + Snackbar.make(getView(), R.string.pref_synchronization_logout_toast, Snackbar.LENGTH_LONG).show(); + SynchronizationSettings.setSelectedSyncProvider(null); + updateScreen(); + return true; + }); + } + + private void updateScreen() { + final boolean loggedIn = SynchronizationSettings.isProviderConnected(); + Preference preferenceHeader = findPreference(PREFERENCE_SYNCHRONIZATION_DESCRIPTION); + if (loggedIn) { + SynchronizationProviderViewData selectedProvider = + SynchronizationProviderViewData.fromIdentifier(getSelectedSyncProviderKey()); + preferenceHeader.setTitle(""); + preferenceHeader.setSummary(selectedProvider.getSummaryResource()); + preferenceHeader.setIcon(selectedProvider.getIconResource()); + preferenceHeader.setOnPreferenceClickListener(null); + } else { + preferenceHeader.setTitle(R.string.synchronization_choose_title); + preferenceHeader.setSummary(R.string.synchronization_summary_unchoosen); + preferenceHeader.setIcon(R.drawable.ic_cloud); + preferenceHeader.setOnPreferenceClickListener((preference) -> { + chooseProviderAndLogin(); + return true; + }); + } + + Preference gpodnetSetLoginPreference = findPreference(PREFERENCE_GPODNET_SETLOGIN_INFORMATION); + gpodnetSetLoginPreference.setVisible(isProviderSelected(SynchronizationProviderViewData.GPODDER_NET)); + gpodnetSetLoginPreference.setEnabled(loggedIn); + findPreference(PREFERENCE_SYNC).setEnabled(loggedIn); + findPreference(PREFERENCE_FORCE_FULL_SYNC).setEnabled(loggedIn); + findPreference(PREFERENCE_LOGOUT).setEnabled(loggedIn); + if (loggedIn) { + String summary = getString(R.string.synchronization_login_status, + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getHosturl()); + Spanned formattedSummary = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_LEGACY); + findPreference(PREFERENCE_LOGOUT).setSummary(formattedSummary); + updateLastSyncReport(SynchronizationSettings.isLastSyncSuccessful(), + SynchronizationSettings.getLastSyncAttempt()); + } else { + findPreference(PREFERENCE_LOGOUT).setSummary(null); + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(null); + } + } + + private void chooseProviderAndLogin() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(R.string.dialog_choose_sync_service_title); + + SynchronizationProviderViewData[] providers = SynchronizationProviderViewData.values(); + ListAdapter adapter = new ArrayAdapter<SynchronizationProviderViewData>( + getContext(), R.layout.alertdialog_sync_provider_chooser, providers) { + + ViewHolder holder; + + class ViewHolder { + ImageView icon; + TextView title; + } + + public View getView(int position, View convertView, ViewGroup parent) { + final LayoutInflater inflater = LayoutInflater.from(getContext()); + if (convertView == null) { + convertView = inflater.inflate( + R.layout.alertdialog_sync_provider_chooser, null); + + holder = new ViewHolder(); + holder.icon = (ImageView) convertView.findViewById(R.id.icon); + holder.title = (TextView) convertView.findViewById(R.id.title); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + SynchronizationProviderViewData synchronizationProviderViewData = getItem(position); + holder.title.setText(synchronizationProviderViewData.getSummaryResource()); + holder.icon.setImageResource(synchronizationProviderViewData.getIconResource()); + return convertView; + } + }; + + builder.setAdapter(adapter, (dialog, which) -> { + switch (providers[which]) { + case GPODDER_NET: + new GpodderAuthenticationFragment() + .show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG); + break; + case NEXTCLOUD_GPODDER: + new NextcloudAuthenticationFragment() + .show(getChildFragmentManager(), NextcloudAuthenticationFragment.TAG); + break; + default: + break; + } + updateScreen(); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private boolean isProviderSelected(@NonNull SynchronizationProviderViewData provider) { + String selectedSyncProviderKey = getSelectedSyncProviderKey(); + return provider.getIdentifier().equals(selectedSyncProviderKey); + } + + private String getSelectedSyncProviderKey() { + return SynchronizationSettings.getSelectedSyncProviderKey(); + } + + private void updateLastSyncReport(boolean successful, long lastTime) { + String status = String.format("%1$s (%2$s)", getString(successful + ? R.string.gpodnetsync_pref_report_successful : R.string.gpodnetsync_pref_report_failed), + DateUtils.getRelativeDateTimeString(getContext(), + lastTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME)); + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(status); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java index 50c7c1ae5..adf133856 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java @@ -201,12 +201,12 @@ public class SwipeActions extends ItemTouchHelper.SimpleCallback implements Life @Override public float getSwipeEscapeVelocity(float defaultValue) { - return swipeOutEnabled ? defaultValue : Float.MAX_VALUE; + return swipeOutEnabled ? defaultValue * 1.5f : Float.MAX_VALUE; } @Override public float getSwipeVelocityThreshold(float defaultValue) { - return swipeOutEnabled ? defaultValue : 0; + return swipeOutEnabled ? defaultValue * 0.6f : 0; } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index c272af7d5..23fdb86de 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -13,12 +13,12 @@ import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; +import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ShareUtils; @@ -151,7 +151,7 @@ public class FeedItemMenuHandler { } else if (menuItemId == R.id.mark_read_item) { selectedItem.setPlayed(true); DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true); - if (GpodnetPreferences.loggedIn()) { + if (SynchronizationSettings.isProviderConnected()) { FeedMedia media = selectedItem.getMedia(); // not all items have media, Gpodder only cares about those that do if (media != null) { @@ -161,17 +161,17 @@ public class FeedItemMenuHandler { .position(media.getDuration() / 1000) .total(media.getDuration() / 1000) .build(); - SyncService.enqueueEpisodeAction(context, actionPlay); + SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionPlay); } } } else if (menuItemId == R.id.mark_unread_item) { selectedItem.setPlayed(false); DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false); - if (GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) { + if (selectedItem.getMedia() != null) { EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW) .currentTimestamp() .build(); - SyncService.enqueueEpisodeAction(context, actionNew); + SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionNew); } } else if (menuItemId == R.id.add_to_queue_item) { DBWriter.addQueueItem(context, selectedItem); diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index 84c738632..af35bbac9 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -108,9 +108,12 @@ public class PreferenceUpgrader { } } if (oldVersion < 2040000) { - SharedPreferences prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE); - prefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG, + SharedPreferences swipePrefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE); + swipePrefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG, SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE).apply(); } + if (oldVersion < 2050000) { + prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply(); + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java index 1075117dd..2ea15005a 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java @@ -22,7 +22,7 @@ public class ConnectivityActionReceiver extends BroadcastReceiver { Log.d(TAG, "Received intent"); ClientConfig.initialize(context); - if (NetworkUtils.autodownloadNetworkAvailable()) { + if (NetworkUtils.isAutoDownloadAllowed()) { Log.d(TAG, "auto-dl network available, starting auto-download"); DBTasks.autodownloadUndownloadedItems(context); } else { // if new network is Wi-Fi, finish ongoing downloads, diff --git a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java index c75164a74..33f0d47b8 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java +++ b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java @@ -9,11 +9,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Consumer; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.util.playback.PlaybackController; public class PlaybackSpeedSeekBar extends FrameLayout { private SeekBar seekBar; - private PlaybackController controller; private Consumer<Float> progressChangedListener; public PlaybackSpeedSeekBar(@NonNull Context context) { @@ -40,15 +38,9 @@ public class PlaybackSpeedSeekBar extends FrameLayout { seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (controller != null) { - float playbackSpeed = (progress + 10) / 20.0f; - controller.setPlaybackSpeed(playbackSpeed); - - if (progressChangedListener != null) { - progressChangedListener.accept(playbackSpeed); - } - } else if (fromUser) { - seekBar.post(() -> updateSpeed()); + float playbackSpeed = (progress + 10) / 20.0f; + if (progressChangedListener != null) { + progressChangedListener.accept(playbackSpeed); } } @@ -62,21 +54,23 @@ public class PlaybackSpeedSeekBar extends FrameLayout { }); } - public void updateSpeed() { - if (controller != null) { - seekBar.setProgress(Math.round((20 * controller.getCurrentPlaybackSpeedMultiplier()) - 10)); - } - } - - public void setController(PlaybackController controller) { - this.controller = controller; - updateSpeed(); - if (progressChangedListener != null && controller != null) { - progressChangedListener.accept(controller.getCurrentPlaybackSpeedMultiplier()); - } + public void updateSpeed(float speedMultiplier) { + seekBar.setProgress(Math.round((20 * speedMultiplier) - 10)); } public void setProgressChangedListener(Consumer<Float> progressChangedListener) { this.progressChangedListener = progressChangedListener; } + + public float getCurrentSpeed() { + return (seekBar.getProgress() + 10) / 20.0f; + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + seekBar.setEnabled(enabled); + findViewById(R.id.butDecSpeed).setEnabled(enabled); + findViewById(R.id.butIncSpeed).setEnabled(enabled); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java index cd3af5003..8d1810ecb 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java @@ -21,7 +21,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.CoverLoader; import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.core.util.DateFormatter; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; |