diff options
101 files changed, 1430 insertions, 1348 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 50576fdc1..263d5450f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -11,7 +11,9 @@ body: attributes: label: Checklist options: - - label: I have used the search function for [open](https://github.com/AntennaPod/AntennaPod/issues) **and** [closed](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if someone else has already submitted the same bug report. + - label: I have used the search function for [**OPEN**](https://github.com/AntennaPod/AntennaPod/issues) issues to see if someone else has already submitted the same bug report. + required: true + - label: I have **also** used the search function for [**CLOSED**](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if the problem is already solved and just waiting to be released. required: true - label: I will describe the problem with as much detail as possible. required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index fbac2857a..8853e82e2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -6,7 +6,9 @@ body: attributes: label: Checklist options: - - label: I have used the search function for [open](https://github.com/AntennaPod/AntennaPod/issues) **and** [closed](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if someone else has already submitted the same feature request. + - label: I have used the search function for [**OPEN**](https://github.com/AntennaPod/AntennaPod/issues) issues to see if someone else has already submitted the same feature request. + required: true + - label: I have **also** used the search function for [**CLOSED**](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if the feature was already implemented and is just waiting to be released, or if the feature was rejected. required: true - label: I will describe the problem with as much detail as possible. required: true diff --git a/.github/workflows/close-if-no-reply.yml b/.github/workflows/close-if-no-reply.yml index ba4903a7f..1c02efce0 100644 --- a/.github/workflows/close-if-no-reply.yml +++ b/.github/workflows/close-if-no-reply.yml @@ -13,15 +13,15 @@ jobs: steps: - uses: actions/stale@v5 with: - days-before-stale: 15 - days-before-close: 15 + days-before-stale: 7 + days-before-close: 7 only-labels: 'Awaiting reply' stale-issue-label: 'Still awaiting reply' stale-pr-label: 'Still awaiting reply' - stale-issue-message: "This issue will be closed when we don't get a reply within 15 days." - stale-pr-message: "This PR will be closed when we don't get a reply within 15 days." + stale-issue-message: "This issue will be closed when we don't get a reply within 7 days." + stale-pr-message: "This PR will be closed when we don't get a reply within 7 days." labels-to-remove-when-unstale: 'Awaiting reply' close-issue-label: "Close reason: no reply" close-pr-label: "Close reason: no reply" - close-issue-message: "This issue was closed because we didn't get a reply for 30 days." - close-pr-message: "This PR was closed because we didn't get a reply for 30 days." + close-issue-message: "This issue was closed because we didn't get a reply for 14 days." + close-pr-message: "This PR was closed because we didn't get a reply for 14 days." diff --git a/app/build.gradle b/app/build.gradle index 1a180e855..997984650 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,7 +123,7 @@ dependencies { implementation "com.joanzapata.iconify:android-iconify-fontawesome:$iconifyVersion" implementation "com.joanzapata.iconify:android-iconify-material:$iconifyVersion" implementation 'com.leinardi.android:speed-dial:3.2.0' - implementation 'com.github.ByteHamster:SearchPreference:v2.0.0' + implementation 'com.github.ByteHamster:SearchPreference:v2.5.0' implementation 'com.github.skydoves:balloon:1.5.3' implementation 'com.github.xabaras:RecyclerViewSwipeDecorator:1.3' implementation "com.annimon:stream:$annimonStreamVersion" diff --git a/app/src/androidTest/java/de/danoeh/antennapod/core/service/download/StubDownloader.java b/app/src/androidTest/java/de/danoeh/antennapod/core/service/download/StubDownloader.java deleted file mode 100644 index 835af1f95..000000000 --- a/app/src/androidTest/java/de/danoeh/antennapod/core/service/download/StubDownloader.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.danoeh.antennapod.core.service.download; - -import androidx.annotation.NonNull; -import androidx.core.util.Consumer; -import de.danoeh.antennapod.model.download.DownloadResult; -import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest; - -public class StubDownloader extends Downloader { - - private final long downloadTime; - - @NonNull - private final Consumer<DownloadResult> onDownloadComplete; - - public StubDownloader(@NonNull DownloadRequest request, long downloadTime, - @NonNull Consumer<DownloadResult> onDownloadComplete) { - super(request); - this.downloadTime = downloadTime; - this.onDownloadComplete = onDownloadComplete; - } - - @Override - protected void download() { - try { - Thread.sleep(downloadTime); - } catch (Throwable t) { - t.printStackTrace(); - } - onDownloadComplete.accept(result); - } - - @NonNull - @Override - public DownloadRequest getDownloadRequest() { - return super.getDownloadRequest(); - } - - @NonNull - @Override - public DownloadResult getResult() { - return super.getResult(); - } - - @Override - public boolean isFinished() { - return super.isFinished(); - } - - @Override - public void cancel() { - super.cancel(); - result.setCancelled(); - } -} diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index 24c20242a..9ba4276be 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -3,6 +3,7 @@ package de.test.antennapod.ui; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; + import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; import androidx.test.filters.LargeTest; @@ -30,9 +31,7 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.action.ViewActions.swipeDown; import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isChecked; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withId; @@ -40,7 +39,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText; import static de.test.antennapod.EspressoTestUtils.clickPreference; import static de.test.antennapod.EspressoTestUtils.waitForView; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -80,38 +78,22 @@ public class PreferencesTest { } @Test - public void testSetLockscreenButtons() { + public void testSetNotificationButtons() { clickPreference(R.string.user_interface_label); - String[] buttons = res.getStringArray(R.array.compact_notification_buttons_options); - clickPreference(R.string.pref_compact_notification_buttons_title); + String[] buttons = res.getStringArray(R.array.full_notification_buttons_options); + clickPreference(R.string.pref_full_notification_buttons_title); // First uncheck checkboxes - onView(withText(buttons[0])).perform(click()); - onView(withText(buttons[1])).perform(click()); - - // Now try to check all checkboxes - onView(withText(buttons[0])).perform(click()); onView(withText(buttons[1])).perform(click()); onView(withText(buttons[2])).perform(click()); - // Make sure that the third checkbox is unchecked - onView(withText(buttons[2])).check(matches(not(isChecked()))); - - String snackBarText = String.format(res.getString( - R.string.pref_compact_notification_buttons_dialog_error), 2); - Awaitility.await().ignoreExceptions().atMost(4000, MILLISECONDS) - .until(() -> { - onView(withText(snackBarText)).check(doesNotExist()); - return true; - }); - onView(withText(R.string.confirm_label)).perform(click()); Awaitility.await().atMost(1000, MILLISECONDS) - .until(UserPreferences::showRewindOnCompactNotification); + .until(() -> UserPreferences.showSkipOnFullNotification()); Awaitility.await().atMost(1000, MILLISECONDS) - .until(UserPreferences::showFastForwardOnCompactNotification); + .until(() -> UserPreferences.showNextChapterOnFullNotification()); Awaitility.await().atMost(1000, MILLISECONDS) - .until(() -> !UserPreferences.showSkipOnCompactNotification()); + .until(() -> !UserPreferences.showPlaybackSpeedOnFullNotification()); } @Test diff --git a/app/src/main/assets/licenses.xml b/app/src/main/assets/licenses.xml index f458da2aa..de06d42a1 100644 --- a/app/src/main/assets/licenses.xml +++ b/app/src/main/assets/licenses.xml @@ -7,12 +7,6 @@ license="GPL-3.0" licenseText="LICENSE.txt" /> <library - name="AntennaPod-AudioPlayer" - author="The AntennaPod team" - website="https://github.com/AntennaPod/AntennaPod-AudioPlayer" - license="Apache 2.0" - licenseText="LICENSE_APACHE-2.0.txt" /> - <library name="Android Jetpack" author="Google" website="https://developer.android.com/jetpack" 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 c5d656aa0..031117170 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -36,6 +36,7 @@ import de.danoeh.antennapod.core.feed.FeedUrlNotFoundException; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.service.playback.PlaybackServiceInterface; import de.danoeh.antennapod.core.util.DownloadErrorLabel; +import de.danoeh.antennapod.databinding.EditTextDialogBinding; import de.danoeh.antennapod.databinding.OnlinefeedviewHeaderBinding; import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.FeedListUpdateEvent; @@ -95,7 +96,7 @@ import java.util.Map; public class OnlineFeedViewActivity extends AppCompatActivity { public static final String ARG_FEEDURL = "arg.feedurl"; - // Optional argument: specify a title for the actionbar. + public static final String ARG_WAS_MANUAL_URL = "manual_url"; private static final int RESULT_ERROR = 2; private static final String TAG = "OnlineFeedViewActivity"; private static final String PREFS = "OnlineFeedViewActivityPreferences"; @@ -598,7 +599,10 @@ public class OnlineFeedViewActivity extends AppCompatActivity { builder.setMessage(R.string.download_error_error_unknown); } builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.cancel()); - builder.setOnDismissListener(dialog -> { + if (getIntent().getBooleanExtra(ARG_WAS_MANUAL_URL, false)) { + builder.setNeutralButton(R.string.edit_url_menu, (dialog, which) -> editUrl()); + } + builder.setOnCancelListener(dialog -> { setResult(RESULT_ERROR); finish(); }); @@ -609,6 +613,26 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } } + private void editUrl() { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(R.string.edit_url_menu); + final EditTextDialogBinding dialogBinding = EditTextDialogBinding.inflate(getLayoutInflater()); + if (downloader != null) { + dialogBinding.urlEditText.setText(downloader.getDownloadRequest().getSource()); + } + builder.setView(dialogBinding.getRoot()); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + setLoadingLayout(); + lookupUrlAndDownload(dialogBinding.urlEditText.getText().toString()); + }); + builder.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel()); + builder.setOnCancelListener(dialog1 -> { + setResult(RESULT_ERROR); + finish(); + }); + builder.show(); + } + @Subscribe(threadMode = ThreadMode.MAIN) public void playbackStateChanged(PlayerStatusEvent event) { boolean isPlayingPreview = 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 10a41057c..3f1c17cdc 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -6,7 +6,9 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Environment; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; import android.util.Log; import android.util.SparseBooleanArray; import android.view.Menu; @@ -45,6 +47,7 @@ import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; /** * Activity for Opml Import. @@ -140,15 +143,6 @@ public class OpmlImportActivity extends AppCompatActivity { return; } this.uri = uri; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { - int permission = ActivityCompat.checkSelfPermission(this, - android.Manifest.permission.READ_EXTERNAL_STORAGE); - if (permission != PackageManager.PERMISSION_GRANTED) { - requestPermission(); - return; - } - } startImport(); } @@ -244,12 +238,29 @@ public class OpmlImportActivity extends AppCompatActivity { getTitleList()); viewBinding.feedlist.setAdapter(listAdapter); }, e -> { + Log.d(TAG, Log.getStackTraceString(e)); + String message = e.getMessage() == null ? "" : e.getMessage(); + if (message.toLowerCase(Locale.ROOT).contains("permission") + && Build.VERSION.SDK_INT >= 23) { + int permission = ActivityCompat.checkSelfPermission(this, + android.Manifest.permission.READ_EXTERNAL_STORAGE); + if (permission != PackageManager.PERMISSION_GRANTED) { + requestPermission(); + return; + } + } viewBinding.progressBar.setVisibility(View.GONE); MaterialAlertDialogBuilder alert = new MaterialAlertDialogBuilder(this); alert.setTitle(R.string.error_label); - alert.setMessage(getString(R.string.opml_reader_error) + e.getMessage()); - alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.create().show(); + String userReadable = getString(R.string.opml_reader_error); + String details = e.getMessage(); + String total = userReadable + "\n\n" + details; + SpannableString errorMessage = new SpannableString(total); + errorMessage.setSpan(new ForegroundColorSpan(0x88888888), + userReadable.length(), total.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + alert.setMessage(errorMessage); + alert.setPositiveButton(android.R.string.ok, (dialog, which) -> finish()); + alert.show(); }); } } 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 e3473937e..fc371090e 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -567,7 +567,8 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar. menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive()); menu.findItem(R.id.player_switch_to_audio_only).setVisible(true); - menu.findItem(R.id.audio_controls).setIcon(R.drawable.ic_sliders); + + menu.findItem(R.id.audio_controls).setVisible(controller.getAudioTracks().size() >= 2); menu.findItem(R.id.playback_speed).setVisible(true); menu.findItem(R.id.player_show_chapters).setVisible(true); return true; 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 8d220c90d..a304ead3c 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -261,7 +261,7 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> int spaceUsed = itemAccess.getNumberOfDownloadedItems() - itemAccess.getReclaimableItems(); if (epCacheSize > 0 && spaceUsed >= epCacheSize) { - holder.count.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_disc_full, 0); + holder.count.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_disc_alert, 0); holder.count.setVisibility(View.VISIBLE); holder.count.setOnClickListener(v -> new MaterialAlertDialogBuilder(context) diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/IntraFeedSortDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/IntraFeedSortDialog.java deleted file mode 100644 index dc8cf65ba..000000000 --- a/app/src/main/java/de/danoeh/antennapod/dialog/IntraFeedSortDialog.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.danoeh.antennapod.dialog; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.model.feed.SortOrder; - -public abstract class IntraFeedSortDialog { - - @Nullable - protected SortOrder currentSortOrder; - @NonNull - protected Context context; - - private final String[] sortItems; - private final SortOrder[] sortValues; - - public IntraFeedSortDialog(@NonNull Context context, @Nullable SortOrder sortOrder, @NonNull boolean isLocalFeed) { - this.context = context; - this.currentSortOrder = sortOrder; - - if (isLocalFeed) { - sortItems = context.getResources().getStringArray(R.array.local_feed_episodes_sort_options); - final String[] localSortStringValues = - context.getResources().getStringArray(R.array.local_feed_episodes_sort_values); - sortValues = SortOrder.valuesOf(localSortStringValues); - } else { - sortItems = context.getResources().getStringArray(R.array.feed_episodes_sort_options); - final String[] commonSortStringValues = - context.getResources().getStringArray(R.array.feed_episodes_sort_values); - sortValues = SortOrder.valuesOf(commonSortStringValues); - } - } - - public void openDialog() { - int idxCurrentSort = getCurrentSortOrderIndex(); - - MaterialAlertDialogBuilder builder = - new MaterialAlertDialogBuilder(context) - .setTitle(R.string.sort) - .setSingleChoiceItems(sortItems, idxCurrentSort, (dialog, idxNewSort) -> { - updateSort(sortValues[idxNewSort]); - dialog.dismiss(); - }) - .setNegativeButton(R.string.cancel_label, null); - builder.create().show(); - } - - /** - * Retrieves index of currentSortOrder index in values array. - * @return if currentSortOrder is found in array - returns index of that element, - * otherwise returns 0, the default sort option; - */ - private int getCurrentSortOrderIndex() { - for (int i = 0; i < sortValues.length; i++) { - if (currentSortOrder == sortValues[i]) { - return i; - } - } - return 0; - } - - protected abstract void updateSort(@NonNull SortOrder sortOrder); -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ItemFilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ItemFilterDialog.java index 92c143992..359c513af 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/ItemFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ItemFilterDialog.java @@ -18,11 +18,13 @@ import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.button.MaterialButtonToggleGroup; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItemFilterGroup; +import de.danoeh.antennapod.databinding.FilterDialogBinding; import de.danoeh.antennapod.databinding.FilterDialogRowBinding; import de.danoeh.antennapod.model.feed.FeedItemFilter; @@ -36,24 +38,36 @@ public abstract class ItemFilterDialog extends BottomSheetDialogFragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View layout = inflater.inflate(R.layout.filter_dialog, null, false); - rows = layout.findViewById(R.id.filter_rows); + FilterDialogBinding binding = FilterDialogBinding.bind(layout); + rows = binding.filterRows; FeedItemFilter filter = (FeedItemFilter) getArguments().getSerializable(ARGUMENT_FILTER); + //add filter rows for (FeedItemFilterGroup item : FeedItemFilterGroup.values()) { - FilterDialogRowBinding binding = FilterDialogRowBinding.inflate(inflater); - binding.getRoot().addOnButtonCheckedListener( + FilterDialogRowBinding rowBinding = FilterDialogRowBinding.inflate(inflater); + rowBinding.getRoot().addOnButtonCheckedListener( (group, checkedId, isChecked) -> onFilterChanged(getNewFilterValues())); - binding.filterButton1.setText(item.values[0].displayName); - binding.filterButton1.setTag(item.values[0].filterId); - binding.filterButton2.setText(item.values[1].displayName); - binding.filterButton2.setTag(item.values[1].filterId); - binding.filterButton1.setMaxLines(3); - binding.filterButton1.setSingleLine(false); - binding.filterButton2.setMaxLines(3); - binding.filterButton2.setSingleLine(false); - rows.addView(binding.getRoot()); + rowBinding.filterButton1.setText(item.values[0].displayName); + rowBinding.filterButton1.setTag(item.values[0].filterId); + rowBinding.filterButton2.setText(item.values[1].displayName); + rowBinding.filterButton2.setTag(item.values[1].filterId); + rowBinding.filterButton1.setMaxLines(3); + rowBinding.filterButton1.setSingleLine(false); + rowBinding.filterButton2.setMaxLines(3); + rowBinding.filterButton2.setSingleLine(false); + rows.addView(rowBinding.getRoot(), rows.getChildCount() - 1); } + binding.confirmFiltermenu.setOnClickListener(view1 -> dismiss()); + binding.resetFiltermenu.setOnClickListener(view1 -> { + onFilterChanged(Collections.emptySet()); + for (int i = 0; i < rows.getChildCount(); i++) { + if (rows.getChildAt(i) instanceof MaterialButtonToggleGroup) { + ((MaterialButtonToggleGroup) rows.getChildAt(i)).clearChecked(); + } + } + }); + for (String filterId : filter.getValues()) { if (!TextUtils.isEmpty(filterId)) { Button button = layout.findViewWithTag(filterId); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ItemSortDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ItemSortDialog.java new file mode 100644 index 000000000..cd6cc4b0a --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ItemSortDialog.java @@ -0,0 +1,104 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.databinding.SortDialogBinding; +import de.danoeh.antennapod.databinding.SortDialogItemActiveBinding; +import de.danoeh.antennapod.databinding.SortDialogItemBinding; +import de.danoeh.antennapod.model.feed.SortOrder; + +public class ItemSortDialog extends BottomSheetDialogFragment { + protected SortOrder sortOrder; + protected SortDialogBinding viewBinding; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + viewBinding = SortDialogBinding.inflate(inflater); + populateList(); + viewBinding.keepSortedCheckbox.setOnCheckedChangeListener( + (buttonView, isChecked) -> ItemSortDialog.this.onSelectionChanged()); + return viewBinding.getRoot(); + } + + private void populateList() { + viewBinding.gridLayout.removeAllViews(); + onAddItem(R.string.episode_title, SortOrder.EPISODE_TITLE_A_Z, SortOrder.EPISODE_TITLE_Z_A, true); + onAddItem(R.string.feed_title, SortOrder.FEED_TITLE_A_Z, SortOrder.FEED_TITLE_Z_A, true); + onAddItem(R.string.duration, SortOrder.DURATION_SHORT_LONG, SortOrder.DURATION_LONG_SHORT, true); + onAddItem(R.string.date, SortOrder.DATE_OLD_NEW, SortOrder.DATE_NEW_OLD, false); + onAddItem(R.string.size, SortOrder.SIZE_SMALL_LARGE, SortOrder.SIZE_LARGE_SMALL, false); + onAddItem(R.string.filename, SortOrder.EPISODE_FILENAME_A_Z, SortOrder.EPISODE_FILENAME_Z_A, true); + onAddItem(R.string.random, SortOrder.RANDOM, SortOrder.RANDOM, true); + onAddItem(R.string.smart_shuffle, SortOrder.SMART_SHUFFLE_OLD_NEW, SortOrder.SMART_SHUFFLE_NEW_OLD, false); + } + + protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) { + if (sortOrder == ascending || sortOrder == descending) { + SortDialogItemActiveBinding item = SortDialogItemActiveBinding.inflate( + getLayoutInflater(), viewBinding.gridLayout, false); + SortOrder other; + if (ascending == descending) { + item.button.setText(title); + other = ascending; + } else if (sortOrder == ascending) { + item.button.setText(getString(title) + "\u00A0▲"); + other = descending; + } else { + item.button.setText(getString(title) + "\u00A0▼"); + other = ascending; + } + item.button.setOnClickListener(v -> { + sortOrder = other; + populateList(); + onSelectionChanged(); + }); + viewBinding.gridLayout.addView(item.getRoot()); + } else { + SortDialogItemBinding item = SortDialogItemBinding.inflate( + getLayoutInflater(), viewBinding.gridLayout, false); + item.button.setText(title); + item.button.setOnClickListener(v -> { + sortOrder = ascendingIsDefault ? ascending : descending; + populateList(); + onSelectionChanged(); + }); + viewBinding.gridLayout.addView(item.getRoot()); + } + } + + protected void onSelectionChanged() { + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(dialogInterface -> { + BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface; + setupFullHeight(bottomSheetDialog); + }); + return dialog; + } + + private void setupFullHeight(BottomSheetDialog bottomSheetDialog) { + FrameLayout bottomSheet = bottomSheetDialog.findViewById(R.id.design_bottom_sheet); + if (bottomSheet != null) { + BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet); + ViewGroup.LayoutParams layoutParams = bottomSheet.getLayoutParams(); + bottomSheet.setLayoutParams(layoutParams); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + } +} 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 a87dccdf5..009f33fe2 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -10,9 +10,7 @@ import androidx.appcompat.app.AlertDialog; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.fragment.app.DialogFragment; import android.widget.Button; -import android.widget.CheckBox; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.core.util.playback.PlaybackController; import java.util.List; @@ -37,12 +35,10 @@ public class PlaybackControlsDialog extends DialogFragment { controller = new PlaybackController(getActivity()) { @Override public void loadMediaInfo() { - setupUi(); setupAudioTracks(); } }; controller.init(); - setupUi(); } @Override @@ -62,15 +58,6 @@ public class PlaybackControlsDialog extends DialogFragment { return dialog; } - private void setupUi() { - final CheckBox skipSilence = dialog.findViewById(R.id.skipSilence); - skipSilence.setChecked(UserPreferences.isSkipSilence()); - skipSilence.setOnCheckedChangeListener((buttonView, isChecked) -> { - UserPreferences.setSkipSilence(isChecked); - controller.setSkipSilence(isChecked); - }); - } - private void setupAudioTracks() { List<String> audioTracks = controller.getAudioTracks(); int selectedAudioTrack = controller.getSelectedAudioTrack(); 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 cf77c2838..929e6b1ad 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java @@ -1,42 +1,49 @@ package de.danoeh.antennapod.dialog; -import android.content.Context; +import android.app.Dialog; +import android.os.Bundle; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.LinearLayout; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.button.MaterialButtonToggleGroup; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.greenrobot.eventbus.EventBus; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.SubscriptionsFilterGroup; +import de.danoeh.antennapod.databinding.FilterDialogBinding; import de.danoeh.antennapod.databinding.FilterDialogRowBinding; import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.SubscriptionsFilter; import de.danoeh.antennapod.storage.preferences.UserPreferences; +import org.greenrobot.eventbus.EventBus; -public class SubscriptionsFilterDialog { - public static void showDialog(Context context) { - SubscriptionsFilter subscriptionsFilter = UserPreferences.getSubscriptionsFilter(); - final Set<String> filterValues = new HashSet<>(Arrays.asList(subscriptionsFilter.getValues())); - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context); - builder.setTitle(context.getString(R.string.pref_filter_feed_title)); +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; - LayoutInflater inflater = LayoutInflater.from(context); - View layout = inflater.inflate(R.layout.filter_dialog, null, false); - LinearLayout rows = layout.findViewById(R.id.filter_rows); - builder.setView(layout); +public class SubscriptionsFilterDialog extends BottomSheetDialogFragment { + private LinearLayout rows; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + SubscriptionsFilter subscriptionsFilter = UserPreferences.getSubscriptionsFilter(); + FilterDialogBinding dialogBinding = FilterDialogBinding.inflate(inflater); + rows = dialogBinding.filterRows; for (SubscriptionsFilterGroup item : SubscriptionsFilterGroup.values()) { FilterDialogRowBinding binding = FilterDialogRowBinding.inflate(inflater); + binding.getRoot().addOnButtonCheckedListener( + (group, checkedId, isChecked) -> updateFilter(getFilterValues())); binding.buttonGroup.setWeightSum(item.values.length); binding.filterButton1.setText(item.values[0].displayName); binding.filterButton1.setTag(item.values[0].filterId); @@ -50,38 +57,72 @@ public class SubscriptionsFilterDialog { binding.filterButton1.setSingleLine(false); binding.filterButton2.setMaxLines(3); binding.filterButton2.setSingleLine(false); - rows.addView(binding.getRoot()); + rows.addView(binding.getRoot(), rows.getChildCount() - 1); } + final Set<String> filterValues = new HashSet<>(Arrays.asList(subscriptionsFilter.getValues())); for (String filterId : filterValues) { if (!TextUtils.isEmpty(filterId)) { - Button button = layout.findViewWithTag(filterId); + Button button = dialogBinding.getRoot().findViewWithTag(filterId); if (button != null) { ((MaterialButtonToggleGroup) button.getParent()).check(button.getId()); } } } - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - filterValues.clear(); + dialogBinding.confirmFiltermenu.setOnClickListener(view -> { + updateFilter(getFilterValues()); + dismiss(); + }); + dialogBinding.resetFiltermenu.setOnClickListener(view -> { + updateFilter(Collections.emptySet()); for (int i = 0; i < rows.getChildCount(); i++) { - if (!(rows.getChildAt(i) instanceof MaterialButtonToggleGroup)) { - continue; - } - MaterialButtonToggleGroup group = (MaterialButtonToggleGroup) rows.getChildAt(i); - if (group.getCheckedButtonId() == View.NO_ID) { - continue; - } - String tag = (String) group.findViewById(group.getCheckedButtonId()).getTag(); - if (tag == null) { // Clear buttons use no tag - continue; + if (rows.getChildAt(i) instanceof MaterialButtonToggleGroup) { + ((MaterialButtonToggleGroup) rows.getChildAt(i)).clearChecked(); } - filterValues.add(tag); } - updateFilter(filterValues); }); - builder.setNegativeButton(R.string.cancel_label, null); - builder.show(); + return dialogBinding.getRoot(); + } + + private Set<String> getFilterValues() { + Set<String> filterValues = new HashSet<>(); + for (int i = 0; i < rows.getChildCount(); i++) { + if (!(rows.getChildAt(i) instanceof MaterialButtonToggleGroup)) { + continue; + } + MaterialButtonToggleGroup group = (MaterialButtonToggleGroup) rows.getChildAt(i); + if (group.getCheckedButtonId() == View.NO_ID) { + continue; + } + String tag = (String) group.findViewById(group.getCheckedButtonId()).getTag(); + if (tag == null) { // Clear buttons use no tag + continue; + } + filterValues.add(tag); + } + return filterValues; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(dialogInterface -> { + BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface; + setupFullHeight(bottomSheetDialog); + }); + return dialog; + } + + private void setupFullHeight(BottomSheetDialog bottomSheetDialog) { + FrameLayout bottomSheet = bottomSheetDialog.findViewById(R.id.design_bottom_sheet); + if (bottomSheet != null) { + BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet); + ViewGroup.LayoutParams layoutParams = bottomSheet.getLayoutParams(); + bottomSheet.setLayoutParams(layoutParams); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } } private static void updateFilter(Set<String> filterValues) { 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 0367e717b..8de7dce04 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -6,6 +6,8 @@ import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.CheckBox; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; @@ -94,6 +96,13 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { addCurrentSpeedChip.setOnCloseIconClickListener(v -> addCurrentSpeed()); addCurrentSpeedChip.setCloseIconContentDescription(getString(R.string.add_preset)); addCurrentSpeedChip.setOnClickListener(v -> addCurrentSpeed()); + + final CheckBox skipSilence = root.findViewById(R.id.skipSilence); + skipSilence.setChecked(UserPreferences.isSkipSilence()); + skipSilence.setOnCheckedChangeListener((buttonView, isChecked) -> { + UserPreferences.setSkipSilence(isChecked); + controller.setSkipSilence(isChecked); + }); return root; } 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 29be41727..20cdd2718 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -147,6 +147,7 @@ public class AddFeedFragment extends Fragment { private void addUrl(String url) { Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, url); + intent.putExtra(OnlineFeedViewActivity.ARG_WAS_MANUAL_URL, true); startActivity(intent); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index 7061a69f3..ab5039ec2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -1,20 +1,23 @@ package de.danoeh.antennapod.fragment; -import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.dialog.AllEpisodesFilterDialog; +import de.danoeh.antennapod.dialog.ItemSortDialog; +import de.danoeh.antennapod.event.FeedListUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItemFilter; import de.danoeh.antennapod.model.feed.SortOrder; +import de.danoeh.antennapod.storage.preferences.UserPreferences; import org.apache.commons.lang3.StringUtils; +import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import java.util.ArrayList; @@ -26,48 +29,33 @@ import java.util.List; */ public class AllEpisodesFragment extends EpisodesListFragment { public static final String TAG = "EpisodesFragment"; - private static final String PREF_NAME = "PrefAllEpisodesFragment"; - private static final String PREF_FILTER = "filter"; - public static final String PREF_SORT = "prefEpisodesSort"; - private SharedPreferences prefs; + public static final String PREF_NAME = "PrefAllEpisodesFragment"; @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View root = super.onCreateView(inflater, container, savedInstanceState); toolbar.inflateMenu(R.menu.episodes); - inflateSortMenu(); toolbar.setTitle(R.string.episodes_label); updateToolbar(); updateFilterUi(); - prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); txtvInformation.setOnClickListener( v -> AllEpisodesFilterDialog.newInstance(getFilter()).show(getChildFragmentManager(), null)); return root; } - private void inflateSortMenu() { - MenuItem sortItem = toolbar.getMenu().findItem(R.id.episodes_sort); - getActivity().getMenuInflater().inflate(R.menu.sort_menu, sortItem.getSubMenu()); - - // Remove the sorting options that are not needed in this fragment - toolbar.getMenu().findItem(R.id.sort_episode_title).setVisible(false); - toolbar.getMenu().findItem(R.id.sort_feed_title).setVisible(false); - toolbar.getMenu().findItem(R.id.sort_random).setVisible(false); - toolbar.getMenu().findItem(R.id.sort_smart_shuffle).setVisible(false); - toolbar.getMenu().findItem(R.id.keep_sorted).setVisible(false); - } - @NonNull @Override protected List<FeedItem> loadData() { - return DBReader.getEpisodes(0, page * EPISODES_PER_PAGE, getFilter(), getSortOrder()); + return DBReader.getEpisodes(0, page * EPISODES_PER_PAGE, getFilter(), + UserPreferences.getAllEpisodesSortOrder()); } @NonNull @Override protected List<FeedItem> loadMoreData(int page) { - return DBReader.getEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, getFilter(), getSortOrder()); + return DBReader.getEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, getFilter(), + UserPreferences.getAllEpisodesSortOrder()); } @Override @@ -77,8 +65,7 @@ public class AllEpisodesFragment extends EpisodesListFragment { @Override protected FeedItemFilter getFilter() { - SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - return new FeedItemFilter(prefs.getString(PREF_FILTER, "")); + return new FeedItemFilter(UserPreferences.getPrefFilterAllEpisodes()); } @Override @@ -108,24 +95,16 @@ public class AllEpisodesFragment extends EpisodesListFragment { } onFilterChanged(new AllEpisodesFilterDialog.AllEpisodesFilterChangedEvent(new HashSet<>(filter))); return true; - } else { - SortOrder sortOrder = MenuItemToSortOrderConverter.convert(item); - if (sortOrder != null) { - saveSortOrderAndRefresh(sortOrder); - return true; - } + } else if (item.getItemId() == R.id.episodes_sort) { + new AllEpisodesSortDialog().show(getChildFragmentManager().beginTransaction(), "SortDialog"); + return true; } return false; } - private void saveSortOrderAndRefresh(SortOrder type) { - prefs.edit().putString(PREF_SORT, "" + type.code).apply(); - loadItems(); - } - @Subscribe public void onFilterChanged(AllEpisodesFilterDialog.AllEpisodesFilterChangedEvent event) { - prefs.edit().putString(PREF_FILTER, StringUtils.join(event.filterValues, ",")).apply(); + UserPreferences.setPrefFilterAllEpisodes(StringUtils.join(event.filterValues, ",")); updateFilterUi(); page = 1; loadItems(); @@ -144,7 +123,25 @@ public class AllEpisodesFragment extends EpisodesListFragment { getFilter().showIsFavorite ? R.drawable.ic_star : R.drawable.ic_star_border); } - private SortOrder getSortOrder() { - return SortOrder.fromCodeString(prefs.getString(PREF_SORT, "" + SortOrder.DATE_NEW_OLD.code)); + public static class AllEpisodesSortDialog extends ItemSortDialog { + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + sortOrder = UserPreferences.getAllEpisodesSortOrder(); + } + + @Override + protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) { + if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG) { + super.onAddItem(title, ascending, descending, ascendingIsDefault); + } + } + + @Override + protected void onSelectionChanged() { + super.onSelectionChanged(); + UserPreferences.setAllEpisodesSortOrder(sortOrder); + EventBus.getDefault().post(new FeedListUpdateEvent(0)); + } } } 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 f2a53ab7e..8b25c0e6a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java @@ -15,25 +15,16 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.material.appbar.MaterialToolbar; import androidx.cardview.widget.CardView; import androidx.fragment.app.Fragment; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.widget.ViewPager2; +import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.elevation.SurfaceColors; -import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; -import de.danoeh.antennapod.core.util.playback.PlaybackController; -import de.danoeh.antennapod.dialog.MediaPlayerErrorDialog; -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; @@ -44,23 +35,31 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.event.FavoritesEvent; -import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; -import de.danoeh.antennapod.model.feed.Chapter; -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.storage.preferences.UserPreferences; +import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.TimeSpeedConverter; -import de.danoeh.antennapod.model.playback.Playable; -import de.danoeh.antennapod.dialog.PlaybackControlsDialog; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.dialog.MediaPlayerErrorDialog; import de.danoeh.antennapod.dialog.SkipPreferenceDialog; import de.danoeh.antennapod.dialog.SleepTimerDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; +import de.danoeh.antennapod.event.FavoritesEvent; +import de.danoeh.antennapod.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.playback.BufferUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.model.feed.Chapter; +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.cast.CastEnabledActivity; +import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView; import de.danoeh.antennapod.view.ChapterSeekBar; import de.danoeh.antennapod.view.PlayButton; @@ -503,10 +502,6 @@ public class AudioPlayerFragment extends Fragment implements if (itemId == R.id.disable_sleeptimer_item || itemId == R.id.set_sleeptimer_item) { new SleepTimerDialog().show(getChildFragmentManager(), "SleepTimerDialog"); return true; - } else if (itemId == R.id.audio_controls) { - PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(); - dialog.show(getChildFragmentManager(), "playback_controls"); - return true; } else if (itemId == R.id.open_feed_item) { if (feedItem != null) { Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feedItem.getFeedId()); 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 730a39189..ec7a35466 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -4,16 +4,14 @@ import android.os.Bundle; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.material.appbar.MaterialToolbar; import androidx.fragment.app.Fragment; +import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.SpeedDialView; import de.danoeh.antennapod.R; @@ -25,6 +23,7 @@ import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; +import de.danoeh.antennapod.dialog.ItemSortDialog; import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.event.PlayerStatusEvent; @@ -84,8 +83,6 @@ public class CompletedDownloadsFragment extends Fragment toolbar = root.findViewById(R.id.toolbar); toolbar.setTitle(R.string.downloads_label); toolbar.inflateMenu(R.menu.downloads_completed); - inflateSortMenu(toolbar); - toolbar.setOnMenuItemClickListener(this); toolbar.setOnLongClickListener(v -> { recyclerView.scrollToPosition(5); @@ -148,20 +145,6 @@ public class CompletedDownloadsFragment extends Fragment return root; } - private void inflateSortMenu(MaterialToolbar toolbar) { - Menu menu = toolbar.getMenu(); - MenuItem downloadsItem = menu.findItem(R.id.downloads_sort); - MenuInflater menuInflater = getActivity().getMenuInflater(); - menuInflater.inflate(R.menu.sort_menu, downloadsItem.getSubMenu()); - - // Remove the sorting options that are not needed in this fragment - menu.findItem(R.id.sort_feed_title).setVisible(false); - menu.findItem(R.id.sort_random).setVisible(false); - menu.findItem(R.id.sort_smart_shuffle).setVisible(false); - menu.findItem(R.id.keep_sorted).setVisible(false); - menu.findItem(R.id.sort_size).setVisible(true); - } - @Override public void onSaveInstanceState(@NonNull Bundle outState) { outState.putBoolean(KEY_UP_ARROW, displayUpArrow); @@ -204,21 +187,13 @@ public class CompletedDownloadsFragment extends Fragment } else if (item.getItemId() == R.id.action_search) { ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance()); return true; - } else { - SortOrder sortOrder = MenuItemToSortOrderConverter.convert(item); - if (sortOrder != null) { - setSortOrder(sortOrder); - return true; - } + } else if (item.getItemId() == R.id.downloads_sort) { + new DownloadsSortDialog().show(getChildFragmentManager(), "SortDialog"); + return true; } return false; } - private void setSortOrder(SortOrder sortOrder) { - UserPreferences.setDownloadsSortedOrder(sortOrder); - loadItems(); - } - @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEventMainThread(EpisodeDownloadEvent event) { Set<String> newRunningDownloads = new HashSet<>(); @@ -391,4 +366,27 @@ public class CompletedDownloadsFragment extends Fragment MenuItemUtils.setOnClickListeners(menu, CompletedDownloadsFragment.this::onContextItemSelected); } } + + public static class DownloadsSortDialog extends ItemSortDialog { + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + sortOrder = UserPreferences.getDownloadsSortedOrder(); + } + + @Override + protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) { + if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG + || ascending == SortOrder.EPISODE_TITLE_A_Z || ascending == SortOrder.SIZE_SMALL_LARGE) { + super.onAddItem(title, ascending, descending, ascendingIsDefault); + } + } + + @Override + protected void onSelectionChanged() { + super.onSelectionChanged(); + UserPreferences.setDownloadsSortedOrder(sortOrder); + EventBus.getDefault().post(DownloadLogEvent.listUpdated()); + } + } } 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 00d671d36..0cbc23a56 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -2,8 +2,6 @@ package de.danoeh.antennapod.fragment; import android.content.DialogInterface; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.util.Log; import android.view.ContextMenu; import android.view.KeyEvent; @@ -13,6 +11,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import androidx.core.util.Pair; @@ -20,9 +19,19 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.SimpleItemAnimator; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.SpeedDialView; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; @@ -51,13 +60,6 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; /** * Shows unread or recently published episodes @@ -77,6 +79,7 @@ public abstract class EpisodesListFragment extends Fragment EmptyViewHandler emptyView; SpeedDialView speedDialView; MaterialToolbar toolbar; + SwipeRefreshLayout swipeRefreshLayout; SwipeActions swipeActions; private ProgressBar progressBar; @@ -180,13 +183,9 @@ public abstract class EpisodesListFragment extends Fragment ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); } - SwipeRefreshLayout swipeRefreshLayout = root.findViewById(R.id.swipeRefresh); + swipeRefreshLayout = root.findViewById(R.id.swipeRefresh); swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance)); - swipeRefreshLayout.setOnRefreshListener(() -> { - FeedUpdateManager.runOnceOrAsk(requireContext()); - new Handler(Looper.getMainLooper()).postDelayed(() -> swipeRefreshLayout.setRefreshing(false), - getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms)); - }); + swipeRefreshLayout.setOnRefreshListener(() -> FeedUpdateManager.runOnceOrAsk(requireContext())); listAdapter = new EpisodeItemListAdapter((MainActivity) getActivity()) { @Override @@ -456,9 +455,7 @@ public abstract class EpisodesListFragment extends Fragment @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedUpdateRunningEvent event) { - if (toolbar.getMenu().findItem(R.id.refresh_item) != null) { - MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item, event.isFeedUpdateRunning); - } + swipeRefreshLayout.setRefreshing(event.isFeedUpdateRunning); } @Override 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 b4fa77c75..f62bdaf84 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -22,25 +22,24 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.content.res.AppCompatResources; -import com.google.android.material.appbar.MaterialToolbar; 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.appbar.MaterialToolbar; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; -import com.joanzapata.iconify.Iconify; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; import de.danoeh.antennapod.dialog.EditUrlSettingsDialog; -import de.danoeh.antennapod.menuhandler.FeedMenuHandler; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedFunding; import de.danoeh.antennapod.ui.glide.FastBlurTransformation; @@ -228,7 +227,8 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu txtvAuthorHeader.setText(feed.getAuthor()); } - txtvUrl.setText(feed.getDownload_url() + " {fa-paperclip}"); + txtvUrl.setText(feed.getDownload_url()); + txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0); if (feed.getPaymentLinks() == null || feed.getPaymentLinks().size() == 0) { lblSupport.setVisibility(View.GONE); @@ -263,7 +263,6 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu txtvFundingUrl.setText(str.toString()); } - Iconify.addIcons(txtvUrl); refreshToolbarState(); } @@ -290,9 +289,11 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu R.string.please_wait_for_data, Toast.LENGTH_LONG); return false; } - boolean handled = FeedMenuHandler.onOptionsItemClicked(getContext(), item, feed); - - if (item.getItemId() == R.id.reconnect_local_folder) { + if (item.getItemId() == R.id.visit_website_item) { + IntentUtils.openInBrowser(getContext(), feed.getLink()); + } else if (item.getItemId() == R.id.share_item) { + ShareUtils.shareFeedLink(getContext(), feed); + } else if (item.getItemId() == R.id.reconnect_local_folder) { MaterialAlertDialogBuilder alert = new MaterialAlertDialogBuilder(getContext()); alert.setMessage(R.string.reconnect_local_folder_warning); alert.setPositiveButton(android.R.string.ok, (dialog, which) -> { @@ -304,23 +305,19 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu }); alert.setNegativeButton(android.R.string.cancel, null); alert.show(); - return true; - } - - if (item.getItemId() == R.id.edit_feed_url_item) { + } else if (item.getItemId() == R.id.edit_feed_url_item) { new EditUrlSettingsDialog(getActivity(), feed) { @Override protected void setUrl(String url) { feed.setDownload_url(url); - txtvUrl.setText(feed.getDownload_url() + " {fa-paperclip}"); - Iconify.addIcons(txtvUrl); + txtvUrl.setText(feed.getDownload_url()); + txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0); } }.show(); - - return true; + } else { + return false; } - - return handled; + return true; } private void addLocalFolderResult(final Uri uri) { 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 1c949218a..8020235b9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -4,8 +4,6 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.LightingColorFilter; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.util.Log; import android.view.ContextMenu; import android.view.KeyEvent; @@ -15,31 +13,48 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; -import com.google.android.material.appbar.MaterialToolbar; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; + import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.snackbar.Snackbar; import com.joanzapata.iconify.Iconify; import com.leinardi.android.speeddial.SpeedDialView; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.feed.FeedEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.FeedItemPermutors; import de.danoeh.antennapod.core.util.FeedItemUtil; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil; import de.danoeh.antennapod.databinding.FeedItemListFragmentBinding; import de.danoeh.antennapod.databinding.MultiSelectSpeedDialBinding; import de.danoeh.antennapod.dialog.DownloadLogDetailsDialog; import de.danoeh.antennapod.dialog.FeedItemFilterDialog; +import de.danoeh.antennapod.dialog.ItemSortDialog; import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.RenameItemDialog; import de.danoeh.antennapod.event.EpisodeDownloadEvent; @@ -54,11 +69,11 @@ import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; import de.danoeh.antennapod.fragment.swipeactions.SwipeActions; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; -import de.danoeh.antennapod.menuhandler.FeedMenuHandler; import de.danoeh.antennapod.model.download.DownloadResult; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItemFilter; +import de.danoeh.antennapod.model.feed.SortOrder; import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.ui.glide.FastBlurTransformation; import de.danoeh.antennapod.view.ToolbarIconTintManager; @@ -68,13 +83,6 @@ 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.Validate; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.Collections; -import java.util.List; /** * Displays a list of FeedItems. @@ -183,11 +191,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem EventBus.getDefault().register(this); viewBinding.swipeRefresh.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance)); - viewBinding.swipeRefresh.setOnRefreshListener(() -> { - FeedUpdateManager.runOnceOrAsk(requireContext(), feed); - new Handler(Looper.getMainLooper()).postDelayed(() -> viewBinding.swipeRefresh.setRefreshing(false), - getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms)); - }); + viewBinding.swipeRefresh.setOnRefreshListener(() -> FeedUpdateManager.runOnceOrAsk(requireContext(), feed)); loadItems(); @@ -240,8 +244,13 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem return; } viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed.getLink() != null); - - FeedMenuHandler.onPrepareOptionsMenu(viewBinding.toolbar.getMenu(), feed); + viewBinding.toolbar.getMenu().findItem(R.id.refresh_complete_item).setVisible(feed.isPaged()); + if (StringUtils.isBlank(feed.getLink())) { + viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(false); + } + if (feed.isLocalFeed()) { + viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(false); + } } @Override @@ -260,26 +269,39 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem R.string.please_wait_for_data, Toast.LENGTH_LONG); return true; } - boolean feedMenuHandled = FeedMenuHandler.onOptionsItemClicked(getActivity(), item, feed); - if (feedMenuHandled) { - return true; - } - final int itemId = item.getItemId(); - if (itemId == R.id.rename_item) { + if (item.getItemId() == R.id.visit_website_item) { + IntentUtils.openInBrowser(getContext(), feed.getLink()); + } else if (item.getItemId() == R.id.share_item) { + ShareUtils.shareFeedLink(getContext(), feed); + } else if (item.getItemId() == R.id.refresh_item) { + FeedUpdateManager.runOnceOrAsk(getContext(), feed); + } else if (item.getItemId() == R.id.refresh_complete_item) { + new Thread(() -> { + feed.setNextPageLink(feed.getDownload_url()); + feed.setPageNr(0); + try { + DBWriter.resetPagedFeedPage(feed).get(); + FeedUpdateManager.runOnce(getContext(), feed); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + }).start(); + } else if (item.getItemId() == R.id.sort_items) { + SingleFeedSortDialog.newInstance(feed).show(getChildFragmentManager(), "SortDialog"); + } else if (item.getItemId() == R.id.rename_item) { new RenameItemDialog(getActivity(), feed).show(); - return true; - } else if (itemId == R.id.remove_feed) { + } else if (item.getItemId() == R.id.remove_feed) { RemoveFeedDialog.show(getContext(), feed, () -> { ((MainActivity) getActivity()).loadFragment(UserPreferences.getDefaultPage(), null); // Make sure fragment is hidden before actually starting to delete getActivity().getSupportFragmentManager().executePendingTransactions(); }); - return true; - } else if (itemId == R.id.action_search) { + } else if (item.getItemId() == R.id.action_search) { ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(feed.getId(), feed.getTitle())); - return true; + } else { + return false; } - return false; + return true; } @Override @@ -407,8 +429,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem if (!event.isFeedUpdateRunning) { nextPageLoader.getRoot().setVisibility(View.GONE); } - MenuItemUtils.updateRefreshMenuItem(viewBinding.toolbar.getMenu(), - R.id.refresh_item, event.isFeedUpdateRunning); + viewBinding.swipeRefresh.setRefreshing(event.isFeedUpdateRunning); } private void refreshHeaderView() { @@ -592,4 +613,45 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem MenuItemUtils.setOnClickListeners(menu, FeedItemlistFragment.this::onContextItemSelected); } } + + public static class SingleFeedSortDialog extends ItemSortDialog { + private static final String ARG_FEED_ID = "feedId"; + private static final String ARG_FEED_IS_LOCAL = "isLocal"; + private static final String ARG_SORT_ORDER = "sortOrder"; + + private static SingleFeedSortDialog newInstance(Feed feed) { + Bundle bundle = new Bundle(); + bundle.putLong(ARG_FEED_ID, feed.getId()); + bundle.putBoolean(ARG_FEED_IS_LOCAL, feed.isLocalFeed()); + if (feed.getSortOrder() == null) { + bundle.putString(ARG_SORT_ORDER, String.valueOf(SortOrder.DATE_NEW_OLD.code)); + } else { + bundle.putString(ARG_SORT_ORDER, String.valueOf(feed.getSortOrder().code)); + } + SingleFeedSortDialog dialog = new SingleFeedSortDialog(); + dialog.setArguments(bundle); + return dialog; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + sortOrder = SortOrder.fromCodeString(getArguments().getString(ARG_SORT_ORDER)); + } + + @Override + protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) { + if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG + || ascending == SortOrder.EPISODE_TITLE_A_Z + || (getArguments().getBoolean(ARG_FEED_IS_LOCAL) && ascending == SortOrder.EPISODE_FILENAME_A_Z)) { + super.onAddItem(title, ascending, descending, ascendingIsDefault); + } + } + + @Override + protected void onSelectionChanged() { + super.onSelectionChanged(); + DBWriter.setFeedItemSortOrder(getArguments().getLong(ARG_FEED_ID), sortOrder); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java index f8bcbb532..497409e70 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/InboxFragment.java @@ -4,26 +4,25 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.Toast; - import androidx.annotation.NonNull; - +import androidx.annotation.Nullable; import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.dialog.ItemSortDialog; +import de.danoeh.antennapod.event.FeedListUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItemFilter; import de.danoeh.antennapod.model.feed.SortOrder; import de.danoeh.antennapod.storage.preferences.UserPreferences; +import org.greenrobot.eventbus.EventBus; import java.util.List; @@ -42,8 +41,6 @@ public class InboxFragment extends EpisodesListFragment { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View root = super.onCreateView(inflater, container, savedInstanceState); toolbar.inflateMenu(R.menu.inbox); - inflateSortMenu(); - toolbar.setTitle(R.string.inbox_label); prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); updateToolbar(); @@ -83,13 +80,9 @@ public class InboxFragment extends EpisodesListFragment { showRemoveAllDialog(); } return true; - } else { - SortOrder sortOrder = MenuItemToSortOrderConverter.convert(item); - if (sortOrder != null) { - UserPreferences.setInboxSortedOrder(sortOrder); - loadItems(); - return true; - } + } else if (item.getItemId() == R.id.inbox_sort) { + new InboxSortDialog().show(getChildFragmentManager(), "SortDialog"); + return true; } return false; } @@ -118,20 +111,6 @@ public class InboxFragment extends EpisodesListFragment { ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.removed_all_inbox_msg, Toast.LENGTH_SHORT); } - private void inflateSortMenu() { - Menu menu = toolbar.getMenu(); - MenuItem downloadsItem = menu.findItem(R.id.inbox_sort); - MenuInflater menuInflater = getActivity().getMenuInflater(); - menuInflater.inflate(R.menu.sort_menu, downloadsItem.getSubMenu()); - - // Remove the sorting options that are not needed in this fragment - toolbar.getMenu().findItem(R.id.sort_episode_title).setVisible(false); - toolbar.getMenu().findItem(R.id.sort_feed_title).setVisible(false); - toolbar.getMenu().findItem(R.id.sort_random).setVisible(false); - toolbar.getMenu().findItem(R.id.sort_smart_shuffle).setVisible(false); - toolbar.getMenu().findItem(R.id.keep_sorted).setVisible(false); - } - private void showRemoveAllDialog() { MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext()); builder.setTitle(R.string.remove_all_inbox_label); @@ -149,4 +128,26 @@ public class InboxFragment extends EpisodesListFragment { builder.setNegativeButton(R.string.cancel_label, null); builder.show(); } + + public static class InboxSortDialog extends ItemSortDialog { + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + sortOrder = UserPreferences.getInboxSortedOrder(); + } + + @Override + protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) { + if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG) { + super.onAddItem(title, ascending, descending, ascendingIsDefault); + } + } + + @Override + protected void onSelectionChanged() { + super.onSelectionChanged(); + UserPreferences.setInboxSortedOrder(sortOrder); + EventBus.getDefault().post(new FeedListUpdateEvent(0)); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/MenuItemToSortOrderConverter.java b/app/src/main/java/de/danoeh/antennapod/fragment/MenuItemToSortOrderConverter.java deleted file mode 100644 index d4150fbdb..000000000 --- a/app/src/main/java/de/danoeh/antennapod/fragment/MenuItemToSortOrderConverter.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.danoeh.antennapod.fragment; - -import android.view.MenuItem; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.model.feed.SortOrder; - -public class MenuItemToSortOrderConverter { - - public static SortOrder convert(MenuItem item) { - final int itemId = item.getItemId(); - - if (itemId == R.id.sort_episode_title_asc) { - return SortOrder.EPISODE_TITLE_A_Z; - } else if (itemId == R.id.sort_episode_title_desc) { - return SortOrder.EPISODE_TITLE_Z_A; - } else if (itemId == R.id.sort_date_asc) { - return SortOrder.DATE_OLD_NEW; - } else if (itemId == R.id.sort_date_desc) { - return SortOrder.DATE_NEW_OLD; - } else if (itemId == R.id.sort_duration_asc) { - return SortOrder.DURATION_SHORT_LONG; - } else if (itemId == R.id.sort_duration_desc) { - return SortOrder.DURATION_LONG_SHORT; - } else if (itemId == R.id.sort_feed_title_asc) { - return SortOrder.FEED_TITLE_A_Z; - } else if (itemId == R.id.sort_feed_title_desc) { - return SortOrder.FEED_TITLE_Z_A; - } else if (itemId == R.id.sort_random) { - return SortOrder.RANDOM; - } else if (itemId == R.id.sort_smart_shuffle_asc) { - return SortOrder.SMART_SHUFFLE_OLD_NEW; - } else if (itemId == R.id.sort_smart_shuffle_desc) { - return SortOrder.SMART_SHUFFLE_NEW_OLD; - } else if (itemId == R.id.sort_size_small_large) { - return SortOrder.SIZE_SMALL_LARGE; - } else if (itemId == R.id.sort_size_large_small) { - return SortOrder.SIZE_LARGE_SMALL; - } - - return null; - } - -} 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 eeca181cf..636c0245b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -382,7 +382,7 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS } } else if (UserPreferences.getSubscriptionsFilter().isEnabled() && navAdapter.showSubscriptionList) { - SubscriptionsFilterDialog.showDialog(requireContext()); + new SubscriptionsFilterDialog().show(getChildFragmentManager(), "filter"); } } 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 80933023e..003ee23db 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -4,29 +4,36 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.util.Log; import android.view.ContextMenu; import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ProgressBar; import android.widget.TextView; + import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.SimpleItemAnimator; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.SpeedDialView; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.List; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; @@ -39,6 +46,7 @@ import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; +import de.danoeh.antennapod.dialog.ItemSortDialog; import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.event.FeedUpdateRunningEvent; @@ -61,12 +69,6 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.List; -import java.util.Locale; /** * Shows all items in the queue. @@ -81,6 +83,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte private QueueRecyclerAdapter recyclerAdapter; private EmptyViewHandler emptyView; private MaterialToolbar toolbar; + private SwipeRefreshLayout swipeRefreshLayout; private boolean displayUpArrow; private List<FeedItem> queue; @@ -159,6 +162,8 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte case MOVED: return; } + recyclerAdapter.updateDragDropEnabled(); + refreshToolbarState(); recyclerView.saveScrollPosition(QueueFragment.TAG); refreshInfoBar(); } @@ -258,13 +263,11 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte 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.sort_random).setVisible(!keepSorted); - toolbar.getMenu().findItem(R.id.keep_sorted).setChecked(keepSorted); } @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedUpdateRunningEvent event) { - MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item, event.isFeedUpdateRunning); + swipeRefreshLayout.setRefreshing(event.isFeedUpdateRunning); } @Override @@ -273,6 +276,9 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte if (itemId == R.id.queue_lock) { toggleQueueLock(); return true; + } else if (itemId == R.id.queue_sort) { + new QueueSortDialog().show(getChildFragmentManager().beginTransaction(), "SortDialog"); + return true; } else if (itemId == R.id.refresh_item) { FeedUpdateManager.runOnceOrAsk(requireContext()); return true; @@ -291,28 +297,9 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte }; conDialog.createNewDialog().show(); return true; - } else if (itemId == R.id.keep_sorted) { - boolean keepSortedOld = UserPreferences.isQueueKeepSorted(); - boolean keepSortedNew = !keepSortedOld; - UserPreferences.setQueueKeepSorted(keepSortedNew); - if (keepSortedNew) { - SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder(); - DBWriter.reorderQueue(sortOrder, true); - } - if (recyclerAdapter != null) { - recyclerAdapter.updateDragDropEnabled(); - } - refreshToolbarState(); - return true; } else if (itemId == R.id.action_search) { ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance()); return true; - } else { - SortOrder sortOrder = MenuItemToSortOrderConverter.convert(item); - if (sortOrder != null) { - setSortOrder(sortOrder); - return true; - } } return false; } @@ -359,16 +346,6 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte } } - /** - * This method is called if the user clicks on a sort order menu item. - * - * @param sortOrder New sort order. - */ - private void setSortOrder(SortOrder sortOrder) { - UserPreferences.setQueueKeepSortedOrder(sortOrder); - DBWriter.reorderQueue(sortOrder, true); - } - @Override public boolean onContextItemSelected(MenuItem item) { Log.d(TAG, "onContextItemSelected() called with: " + "item = [" + item + "]"); @@ -422,10 +399,6 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte } ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); toolbar.inflateMenu(R.menu.queue); - - MenuItem queueItem = toolbar.getMenu().findItem(R.id.queue_sort); - MenuInflater menuInflater = getActivity().getMenuInflater(); - menuInflater.inflate(R.menu.sort_menu, queueItem.getSubMenu()); refreshToolbarState(); progressBar = root.findViewById(R.id.progressBar); progressBar.setVisibility(View.VISIBLE); @@ -454,13 +427,9 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte recyclerAdapter.setOnSelectModeListener(this); recyclerView.setAdapter(recyclerAdapter); - SwipeRefreshLayout swipeRefreshLayout = root.findViewById(R.id.swipeRefresh); + swipeRefreshLayout = root.findViewById(R.id.swipeRefresh); swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance)); - swipeRefreshLayout.setOnRefreshListener(() -> { - FeedUpdateManager.runOnceOrAsk(requireContext()); - new Handler(Looper.getMainLooper()).postDelayed(() -> swipeRefreshLayout.setRefreshing(false), - getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms)); - }); + swipeRefreshLayout.setOnRefreshListener(() -> FeedUpdateManager.runOnceOrAsk(requireContext())); emptyView = new EmptyViewHandler(getContext()); emptyView.attachToRecyclerView(recyclerView); @@ -507,9 +476,8 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte } private void refreshInfoBar() { - String info = String.format(Locale.getDefault(), "%d%s", - queue.size(), getString(R.string.episodes_suffix)); - if (queue.size() > 0) { + String info = getResources().getQuantityString(R.plurals.num_episodes, queue.size(), queue.size()); + if (!queue.isEmpty()) { long timeLeft = 0; for (FeedItem item : queue) { float playbackSpeed = 1; @@ -567,6 +535,42 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte swipeActions.attachTo(recyclerView); } + public static class QueueSortDialog extends ItemSortDialog { + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (UserPreferences.isQueueKeepSorted()) { + sortOrder = UserPreferences.getQueueKeepSortedOrder(); + } + final View view = super.onCreateView(inflater, container, savedInstanceState); + viewBinding.keepSortedCheckbox.setVisibility(View.VISIBLE); + viewBinding.keepSortedCheckbox.setChecked(UserPreferences.isQueueKeepSorted()); + // Disable until something gets selected + viewBinding.keepSortedCheckbox.setEnabled(UserPreferences.isQueueKeepSorted()); + return view; + } + + @Override + protected void onAddItem(int title, SortOrder ascending, SortOrder descending, boolean ascendingIsDefault) { + if (ascending != SortOrder.EPISODE_FILENAME_A_Z && ascending != SortOrder.SIZE_SMALL_LARGE) { + super.onAddItem(title, ascending, descending, ascendingIsDefault); + } + } + + @Override + protected void onSelectionChanged() { + super.onSelectionChanged(); + viewBinding.keepSortedCheckbox.setEnabled(sortOrder != SortOrder.RANDOM); + if (sortOrder == SortOrder.RANDOM) { + viewBinding.keepSortedCheckbox.setChecked(false); + } + UserPreferences.setQueueKeepSorted(viewBinding.keepSortedCheckbox.isChecked()); + UserPreferences.setQueueKeepSortedOrder(sortOrder); + DBWriter.reorderQueue(sortOrder, true); + } + } + private class QueueSwipeActions extends SwipeActions { // Position tracking whilst dragging 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 93ed4c2c9..e86cd58b9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -3,8 +3,6 @@ package de.danoeh.antennapod.fragment; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -81,6 +79,7 @@ public class SubscriptionFragment extends Fragment private EmptyViewHandler emptyView; private LinearLayout feedsFilteredMsg; private MaterialToolbar toolbar; + private SwipeRefreshLayout swipeRefreshLayout; private ProgressBar progressBar; private String displayedFolder = null; private boolean displayUpArrow; @@ -166,15 +165,12 @@ public class SubscriptionFragment extends Fragment }); feedsFilteredMsg = root.findViewById(R.id.feeds_filtered_message); - feedsFilteredMsg.setOnClickListener((l) -> SubscriptionsFilterDialog.showDialog(requireContext())); + feedsFilteredMsg.setOnClickListener((l) -> + new SubscriptionsFilterDialog().show(getChildFragmentManager(), "filter")); - SwipeRefreshLayout swipeRefreshLayout = root.findViewById(R.id.swipeRefresh); + swipeRefreshLayout = root.findViewById(R.id.swipeRefresh); swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance)); - swipeRefreshLayout.setOnRefreshListener(() -> { - FeedUpdateManager.runOnceOrAsk(requireContext()); - new Handler(Looper.getMainLooper()).postDelayed(() -> swipeRefreshLayout.setRefreshing(false), - getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms)); - }); + swipeRefreshLayout.setOnRefreshListener(() -> FeedUpdateManager.runOnceOrAsk(requireContext())); speedDialView = root.findViewById(R.id.fabSD); speedDialView.setOverlayLayout(root.findViewById(R.id.fabSDOverlay)); @@ -211,7 +207,7 @@ public class SubscriptionFragment extends Fragment @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedUpdateRunningEvent event) { - MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item, event.isFeedUpdateRunning); + swipeRefreshLayout.setRefreshing(event.isFeedUpdateRunning); } @Override @@ -221,7 +217,7 @@ public class SubscriptionFragment extends Fragment FeedUpdateManager.runOnceOrAsk(requireContext()); return true; } else if (itemId == R.id.subscriptions_filter) { - SubscriptionsFilterDialog.showDialog(requireContext()); + new SubscriptionsFilterDialog().show(getChildFragmentManager(), "filter"); return true; } else if (itemId == R.id.subscriptions_sort) { FeedSortDialog.showDialog(requireContext()); 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 66f592af2..0f3320e98 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 @@ -1,8 +1,10 @@ package de.danoeh.antennapod.fragment.preferences; import android.content.Context; +import android.content.DialogInterface; import android.os.Build; import android.os.Bundle; +import android.widget.Button; import android.widget.ListView; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; @@ -64,14 +66,14 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat { return true; }); - findPreference(UserPreferences.PREF_COMPACT_NOTIFICATION_BUTTONS) + findPreference(UserPreferences.PREF_FULL_NOTIFICATION_BUTTONS) .setOnPreferenceClickListener(preference -> { - showNotificationButtonsDialog(); + showFullNotificationButtonsDialog(); return true; }); findPreference(UserPreferences.PREF_FILTER_FEED) .setOnPreferenceClickListener((preference -> { - SubscriptionsFilterDialog.showDialog(requireContext()); + new SubscriptionsFilterDialog().show(getChildFragmentManager(), "filter"); return true; })); @@ -91,48 +93,85 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat { } } - private void showNotificationButtonsDialog() { + + private void showFullNotificationButtonsDialog() { final Context context = getActivity(); - final List<Integer> preferredButtons = UserPreferences.getCompactNotificationButtons(); + + final List<Integer> preferredButtons = UserPreferences.getFullNotificationButtons(); final String[] allButtonNames = context.getResources().getStringArray( - R.array.compact_notification_buttons_options); + R.array.full_notification_buttons_options); + final int[] buttonIDs = {2, 3, 4}; + final int exactItems = 2; + final DialogInterface.OnClickListener completeListener = (dialog, which) -> + UserPreferences.setFullNotificationButtons(preferredButtons); + final String title = context.getResources().getString( + R.string.pref_full_notification_buttons_title); + + showNotificationButtonsDialog(preferredButtons, allButtonNames, buttonIDs, title, + exactItems, completeListener + ); + } + + private void showNotificationButtonsDialog(List<Integer> preferredButtons, + String[] allButtonNames, int[] buttonIds, String title, + int exactItems, DialogInterface.OnClickListener completeListener) { boolean[] checked = new boolean[allButtonNames.length]; // booleans default to false in java + final Context context = getActivity(); + + // Clear buttons that are not part of the setting anymore + for (int i = preferredButtons.size() - 1; i >= 0; i--) { + boolean isValid = false; + for (int j = 0; j < checked.length; j++) { + if (buttonIds[j] == preferredButtons.get(i)) { + isValid = true; + } + } + + if (!isValid) { + preferredButtons.remove(i); + } + } + for(int i=0; i < checked.length; i++) { - if(preferredButtons.contains(i)) { + if (preferredButtons.contains(buttonIds[i])) { checked[i] = true; } } MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context); - builder.setTitle(String.format(context.getResources().getString( - R.string.pref_compact_notification_buttons_dialog_title), 2)); + builder.setTitle(title); builder.setMultiChoiceItems(allButtonNames, checked, (dialog, which, isChecked) -> { checked[which] = isChecked; if (isChecked) { - if (preferredButtons.size() < 2) { - preferredButtons.add(which); - } else { - // Only allow a maximum of two selections. This is because the notification - // on the lock screen can only display 3 buttons, and the play/pause button - // is always included. - checked[which] = false; - ListView selectionView = ((AlertDialog) dialog).getListView(); - selectionView.setItemChecked(which, false); - Snackbar.make( - selectionView, - String.format(context.getResources().getString( - R.string.pref_compact_notification_buttons_dialog_error), 2), - Snackbar.LENGTH_SHORT).show(); - } + preferredButtons.add(buttonIds[which]); } else { - preferredButtons.remove((Integer) which); + preferredButtons.remove((Integer) buttonIds[which]); } }); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> - UserPreferences.setCompactNotificationButtons(preferredButtons)); + builder.setPositiveButton(R.string.confirm_label, null); builder.setNegativeButton(R.string.cancel_label, null); - builder.create().show(); + final AlertDialog dialog = builder.create(); + + dialog.show(); + + Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + + positiveButton.setOnClickListener(v -> { + if (preferredButtons.size() != exactItems) { + ListView selectionView = dialog.getListView(); + Snackbar.make( + selectionView, + String.format(context.getResources().getString( + R.string.pref_compact_notification_buttons_dialog_error_exact), exactItems), + Snackbar.LENGTH_SHORT).show(); + + } else { + completeListener.onClick(dialog, AlertDialog.BUTTON_POSITIVE); + dialog.cancel(); + } + } + ); } } diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java index 7a9eb1bb5..eac1e9304 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -1,103 +1,32 @@ package de.danoeh.antennapod.menuhandler; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.DialogInterface; import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; import androidx.annotation.NonNull; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.ShareUtils; -import de.danoeh.antennapod.core.util.download.FeedUpdateManager; -import de.danoeh.antennapod.dialog.IntraFeedSortDialog; -import de.danoeh.antennapod.model.feed.Feed; -import de.danoeh.antennapod.model.feed.SortOrder; -import org.apache.commons.lang3.StringUtils; -import android.content.DialogInterface; -import android.annotation.SuppressLint; import androidx.fragment.app.Fragment; +import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.RenameItemDialog; import de.danoeh.antennapod.dialog.TagSettingsDialog; +import de.danoeh.antennapod.model.feed.Feed; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; + import java.util.Collections; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** * Handles interactions with the FeedItemMenu. */ -public class FeedMenuHandler { - - private FeedMenuHandler(){ } - +public abstract class FeedMenuHandler { private static final String TAG = "FeedMenuHandler"; - public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) { - if (selectedFeed == null) { - return true; - } - - Log.d(TAG, "Preparing options menu"); - - menu.findItem(R.id.refresh_complete_item).setVisible(selectedFeed.isPaged()); - if (StringUtils.isBlank(selectedFeed.getLink())) { - menu.findItem(R.id.visit_website_item).setVisible(false); - } - if (selectedFeed.isLocalFeed()) { - // hide complete submenu "Share..." as both sub menu items are not visible - menu.findItem(R.id.share_item).setVisible(false); - } - - return true; - } - - /** - * NOTE: This method does not handle clicks on the 'remove feed' - item. - */ - public static boolean onOptionsItemClicked(final Context context, final MenuItem item, final Feed selectedFeed) { - final int itemId = item.getItemId(); - if (itemId == R.id.refresh_item) { - FeedUpdateManager.runOnceOrAsk(context, selectedFeed); - } else if (itemId == R.id.refresh_complete_item) { - new Thread(() -> { - selectedFeed.setNextPageLink(selectedFeed.getDownload_url()); - selectedFeed.setPageNr(0); - try { - DBWriter.resetPagedFeedPage(selectedFeed).get(); - FeedUpdateManager.runOnce(context, selectedFeed); - } catch (ExecutionException | InterruptedException e) { - throw new RuntimeException(e); - } - }).start(); - } else if (itemId == R.id.sort_items) { - showSortDialog(context, selectedFeed); - } else if (itemId == R.id.visit_website_item) { - IntentUtils.openInBrowser(context, selectedFeed.getLink()); - } else if (itemId == R.id.share_item) { - ShareUtils.shareFeedLink(context, selectedFeed); - } else { - return false; - } - return true; - } - - private static void showSortDialog(Context context, Feed selectedFeed) { - IntraFeedSortDialog sortDialog = new IntraFeedSortDialog(context, selectedFeed.getSortOrder(), selectedFeed.isLocalFeed()) { - @Override - protected void updateSort(@NonNull SortOrder sortOrder) { - selectedFeed.setSortOrder(sortOrder); - DBWriter.setFeedItemSortOrder(selectedFeed.getId(), sortOrder); - } - }; - sortDialog.openDialog(); - } - public static boolean onMenuItemClicked(@NonNull Fragment fragment, int menuItemId, @NonNull Feed selectedFeed, Runnable callback) { @NonNull Context context = fragment.requireContext(); @@ -131,5 +60,4 @@ public class FeedMenuHandler { } return true; } - } 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 cc298b38d..3a3063599 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -6,12 +6,15 @@ import android.view.KeyEvent; import androidx.core.app.NotificationManagerCompat; import androidx.preference.PreferenceManager; +import org.apache.commons.lang3.StringUtils; + import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.error.CrashReportWriter; +import de.danoeh.antennapod.fragment.AllEpisodesFragment; import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation; import de.danoeh.antennapod.fragment.QueueFragment; @@ -149,5 +152,19 @@ public class PreferenceUpgrader { if (oldVersion < 3020000) { NotificationManagerCompat.from(context).deleteNotificationChannel("auto_download"); } + + if (oldVersion < 3030000) { + SharedPreferences allEpisodesPreferences = + context.getSharedPreferences(AllEpisodesFragment.PREF_NAME, Context.MODE_PRIVATE); + String oldEpisodeSort = allEpisodesPreferences.getString(UserPreferences.PREF_SORT_ALL_EPISODES, ""); + if (!StringUtils.isAllEmpty(oldEpisodeSort)) { + prefs.edit().putString(UserPreferences.PREF_SORT_ALL_EPISODES, oldEpisodeSort).apply(); + } + + String oldEpisodeFilter = allEpisodesPreferences.getString("filter", ""); + if (!StringUtils.isAllEmpty(oldEpisodeFilter)) { + prefs.edit().putString(UserPreferences.PREF_FILTER_ALL_EPISODES, oldEpisodeFilter).apply(); + } + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java index fc925aa03..6f43a8ff4 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java @@ -6,8 +6,6 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -21,7 +19,6 @@ import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentContainerView; -import de.danoeh.antennapod.ui.home.sections.EchoSection; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -33,7 +30,6 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; import de.danoeh.antennapod.databinding.HomeFragmentBinding; @@ -41,8 +37,10 @@ import de.danoeh.antennapod.event.FeedListUpdateEvent; import de.danoeh.antennapod.event.FeedUpdateRunningEvent; import de.danoeh.antennapod.fragment.SearchFragment; import de.danoeh.antennapod.storage.preferences.UserPreferences; +import de.danoeh.antennapod.ui.echo.EchoActivity; import de.danoeh.antennapod.ui.home.sections.AllowNotificationsSection; import de.danoeh.antennapod.ui.home.sections.DownloadsSection; +import de.danoeh.antennapod.ui.home.sections.EchoSection; import de.danoeh.antennapod.ui.home.sections.EpisodesSurpriseSection; import de.danoeh.antennapod.ui.home.sections.InboxSection; import de.danoeh.antennapod.ui.home.sections.QueueSection; @@ -85,11 +83,7 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis updateWelcomeScreenVisibility(); viewBinding.swipeRefresh.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance)); - viewBinding.swipeRefresh.setOnRefreshListener(() -> { - FeedUpdateManager.runOnceOrAsk(requireContext()); - new Handler(Looper.getMainLooper()).postDelayed(() -> viewBinding.swipeRefresh.setRefreshing(false), - getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms)); - }); + viewBinding.swipeRefresh.setOnRefreshListener(() -> FeedUpdateManager.runOnceOrAsk(requireContext())); return viewBinding.getRoot(); } @@ -104,10 +98,10 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis addSection(new AllowNotificationsSection()); } } - if (Calendar.getInstance().get(Calendar.MONTH) == Calendar.DECEMBER - && Calendar.getInstance().get(Calendar.YEAR) == 2023 + if (Calendar.getInstance().get(Calendar.YEAR) == EchoActivity.RELEASE_YEAR + && Calendar.getInstance().get(Calendar.MONTH) == Calendar.DECEMBER && Calendar.getInstance().get(Calendar.DAY_OF_MONTH) >= 10 - && prefs.getInt(PREF_HIDE_ECHO, 0) != 2023) { + && prefs.getInt(PREF_HIDE_ECHO, 0) != EchoActivity.RELEASE_YEAR) { addSection(new EchoSection()); } @@ -153,8 +147,7 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedUpdateRunningEvent event) { - MenuItemUtils.updateRefreshMenuItem(viewBinding.toolbar.getMenu(), - R.id.refresh_item, event.isFeedUpdateRunning); + viewBinding.swipeRefresh.setRefreshing(event.isFeedUpdateRunning); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/ui/home/sections/EchoSection.java b/app/src/main/java/de/danoeh/antennapod/ui/home/sections/EchoSection.java index 7261c6be4..05b716abb 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/home/sections/EchoSection.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/home/sections/EchoSection.java @@ -32,13 +32,9 @@ public class EchoSection extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { viewBinding = HomeSectionEchoBinding.inflate(inflater); - viewBinding.titleLabel.setText(getString(R.string.antennapod_echo_year, 2023)); + viewBinding.titleLabel.setText(getString(R.string.antennapod_echo_year, EchoActivity.RELEASE_YEAR)); viewBinding.echoButton.setOnClickListener(v -> startActivity(new Intent(getContext(), EchoActivity.class))); - viewBinding.closeButton.setOnClickListener(v -> { - getContext().getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE) - .edit().putInt(HomeFragment.PREF_HIDE_ECHO, 2023).apply(); - ((MainActivity) getActivity()).loadFragment(HomeFragment.TAG, null); - }); + viewBinding.closeButton.setOnClickListener(v -> hideThisYear()); updateVisibility(); return viewBinding.getRoot(); } @@ -51,7 +47,7 @@ public class EchoSection extends Fragment { date.set(Calendar.MILLISECOND, 0); date.set(Calendar.DAY_OF_MONTH, 1); date.set(Calendar.MONTH, 0); - date.set(Calendar.YEAR, 2023); + date.set(Calendar.YEAR, EchoActivity.RELEASE_YEAR); return date.getTimeInMillis(); } @@ -70,8 +66,18 @@ public class EchoSection extends Fragment { }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(totalTime -> viewBinding.getRoot() - .setVisibility((totalTime >= 3600 * 10) ? View.VISIBLE : View.GONE), - Throwable::printStackTrace); + .subscribe(totalTime -> { + boolean shouldShow = (totalTime >= 3600 * 10); + viewBinding.getRoot().setVisibility(shouldShow ? View.VISIBLE : View.GONE); + if (!shouldShow) { + hideThisYear(); + } + }, Throwable::printStackTrace); + } + + void hideThisYear() { + getContext().getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE) + .edit().putInt(HomeFragment.PREF_HIDE_ECHO, EchoActivity.RELEASE_YEAR).apply(); + ((MainActivity) getActivity()).loadFragment(HomeFragment.TAG, null); } } 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 b4f61a19a..01d4a10f0 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 @@ -100,15 +100,13 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { this.item = item; placeholder.setText(item.getFeed().getTitle()); title.setText(item.getTitle()); - leftPadding.setContentDescription(item.getTitle()); - pubDate.setText(DateFormatter.formatAbbrev(activity, item.getPubDate())); - pubDate.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate())); if (item.isPlayed()) { - cover.setContentDescription(activity.getString(R.string.is_played)); - cover.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + leftPadding.setContentDescription(item.getTitle() + ". " + activity.getString(R.string.is_played)); } else { - cover.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + leftPadding.setContentDescription(item.getTitle()); } + pubDate.setText(DateFormatter.formatAbbrev(activity, item.getPubDate())); + pubDate.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate())); isInbox.setVisibility(item.isNew() ? View.VISIBLE : View.GONE); isFavorite.setVisibility(item.isTagged(FeedItem.TAG_FAVORITE) ? View.VISIBLE : View.GONE); isInQueue.setVisibility(item.isTagged(FeedItem.TAG_QUEUE) ? View.VISIBLE : View.GONE); diff --git a/app/src/main/res/layout/audio_controls.xml b/app/src/main/res/layout/audio_controls.xml index 3abb70961..25f38a63f 100644 --- a/app/src/main/res/layout/audio_controls.xml +++ b/app/src/main/res/layout/audio_controls.xml @@ -19,19 +19,6 @@ android:visibility="gone" android:layout_marginBottom="8dp" /> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="20dp" - android:text="@string/audio_effects" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" /> - - <CheckBox - android:id="@+id/skipSilence" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/pref_skip_silence_title" /> - </LinearLayout> </ScrollView> diff --git a/app/src/main/res/layout/feedinfo.xml b/app/src/main/res/layout/feedinfo.xml index 974823668..c3ca87a5a 100644 --- a/app/src/main/res/layout/feedinfo.xml +++ b/app/src/main/res/layout/feedinfo.xml @@ -81,12 +81,13 @@ <TextView android:id="@+id/txtvUrl" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" android:maxLines="4" android:paddingTop="4dp" android:paddingBottom="4dp" + android:drawablePadding="4dp" tools:background="@android:color/holo_green_dark" tools:text="http://www.example.com/feed" /> diff --git a/app/src/main/res/layout/filter_dialog.xml b/app/src/main/res/layout/filter_dialog.xml index d700f0365..e22ea4ffe 100644 --- a/app/src/main/res/layout/filter_dialog.xml +++ b/app/src/main/res/layout/filter_dialog.xml @@ -1,15 +1,42 @@ <?xml version="1.0" encoding="utf-8"?> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <LinearLayout - android:id="@+id/filter_rows" + android:id="@+id/filter_rows" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="24dp" + android:paddingTop="24dp" + android:paddingRight="24dp" + android:paddingBottom="8dp"> + + <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingLeft="24dp" - android:paddingTop="24dp" - android:paddingRight="24dp" - android:paddingBottom="8dp"> + android:layout_height="match_parent" + android:orientation="horizontal"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/resetFiltermenu" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/reset" + style="@style/Widget.MaterialComponents.Button.TextButton" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/confirmFiltermenu" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/confirm_label" + style="@style/Widget.MaterialComponents.Button.TextButton" /> + + </LinearLayout> + </LinearLayout> + </ScrollView> diff --git a/app/src/main/res/layout/home_section.xml b/app/src/main/res/layout/home_section.xml index 82f5c0b5c..c1bac5e91 100644 --- a/app/src/main/res/layout/home_section.xml +++ b/app/src/main/res/layout/home_section.xml @@ -56,6 +56,7 @@ android:layout_marginStart="8dp" android:background="@drawable/bg_pill" android:paddingHorizontal="8dp" + android:paddingBottom="1sp" android:textAlignment="center" android:textColor="?attr/colorPrimary" android:textSize="16sp" diff --git a/app/src/main/res/layout/nextcloud_auth_dialog.xml b/app/src/main/res/layout/nextcloud_auth_dialog.xml index c08c5b969..0f25a271f 100644 --- a/app/src/main/res/layout/nextcloud_auth_dialog.xml +++ b/app/src/main/res/layout/nextcloud_auth_dialog.xml @@ -1,69 +1,75 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:padding="16dp" - android:orientation="vertical" - android:clipToPadding="false"> + android:layout_height="wrap_content"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="16dp" - android:text="@string/synchronization_host_explanation" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/serverUrlTextInput" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" - style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> + android:padding="16dp" + android:orientation="vertical" + android:clipToPadding="false"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:text="@string/synchronization_host_explanation" /> - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/serverUrlText" + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/serverUrlTextInput" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/synchronization_host_label" - android:inputType="textNoSuggestions" - android:lines="1" - android:imeOptions="actionNext|flagNoFullscreen" /> + android:layout_marginBottom="16dp" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> - </com.google.android.material.textfield.TextInputLayout> + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/serverUrlText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/synchronization_host_label" + android:inputType="textNoSuggestions" + android:lines="1" + android:imeOptions="actionNext|flagNoFullscreen" /> - <LinearLayout - android:id="@+id/loginProgressContainer" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" - android:orientation="horizontal" - android:layout_gravity="center_vertical"> + </com.google.android.material.textfield.TextInputLayout> - <ProgressBar - android:layout_width="wrap_content" + <LinearLayout + android:id="@+id/loginProgressContainer" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginEnd="8dp" - android:layout_marginRight="8dp" /> + android:visibility="gone" + android:orientation="horizontal" + android:layout_gravity="center_vertical"> + + <ProgressBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/synchronization_nextcloud_authenticate_browser" /> + + </LinearLayout> <TextView + android:id="@+id/errorText" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/synchronization_nextcloud_authenticate_browser" /> + android:visibility="gone" + android:textColor="?attr/icon_red" + android:layout_marginBottom="16dp" /> - </LinearLayout> - - <TextView - android:id="@+id/errorText" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:textColor="?attr/icon_red" - android:layout_marginBottom="16dp" /> + <Button + android:id="@+id/chooseHostButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/proceed_to_login_butLabel" /> - <Button - android:id="@+id/chooseHostButton" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/proceed_to_login_butLabel" /> + </LinearLayout> -</LinearLayout> +</ScrollView> diff --git a/app/src/main/res/layout/sort_dialog.xml b/app/src/main/res/layout/sort_dialog.xml new file mode 100644 index 000000000..88eb61df5 --- /dev/null +++ b/app/src/main/res/layout/sort_dialog.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <androidx.gridlayout.widget.GridLayout + android:id="@+id/gridLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:columnCount="2" + app:rowOrderPreserved="false" + app:useDefaultMargins="true" + app:alignmentMode="alignBounds" /> + + <CheckBox + android:id="@+id/keepSortedCheckbox" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:text="@string/keep_sorted" + tools:visibility="visible" /> + +</LinearLayout> diff --git a/app/src/main/res/layout/sort_dialog_item.xml b/app/src/main/res/layout/sort_dialog_item.xml new file mode 100644 index 000000000..570a66d26 --- /dev/null +++ b/app/src/main/res/layout/sort_dialog_item.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<Button + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/button" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_gravity="fill_horizontal|center_vertical" + app:layout_columnWeight="1" + style="@style/Widget.Material3.Button.OutlinedButton" /> diff --git a/app/src/main/res/layout/sort_dialog_item_active.xml b/app/src/main/res/layout/sort_dialog_item_active.xml new file mode 100644 index 000000000..829ccdee2 --- /dev/null +++ b/app/src/main/res/layout/sort_dialog_item_active.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<Button + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/button" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_gravity="fill_horizontal|center_vertical" + app:layout_columnWeight="1" + style="@style/Widget.Material3.Button.TonalButton" /> diff --git a/app/src/main/res/layout/speed_select_dialog.xml b/app/src/main/res/layout/speed_select_dialog.xml index e4d78c3fa..0b9e422a8 100644 --- a/app/src/main/res/layout/speed_select_dialog.xml +++ b/app/src/main/res/layout/speed_select_dialog.xml @@ -1,45 +1,51 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:padding="16dp" - android:orientation="vertical"> + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp"> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_width="match_parent" + android:layout_height="wrap_content"> <TextView - android:text="@string/playback_speed" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle"/> + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/playback_speed" + style="@style/AntennaPod.TextView.ListItemPrimaryTitle" /> <com.google.android.material.chip.Chip - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/add_current_speed_chip"/> + android:id="@+id/add_current_speed_chip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </LinearLayout> <de.danoeh.antennapod.view.PlaybackSpeedSeekBar - android:id="@+id/speed_seek_bar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp"> - </de.danoeh.antennapod.view.PlaybackSpeedSeekBar> + android:id="@+id/speed_seek_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" /> <TextView - android:text="@string/speed_presets" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" - android:layout_marginBottom="8dp"/> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:text="@string/speed_presets" + style="@style/AntennaPod.TextView.ListItemPrimaryTitle" /> <androidx.recyclerview.widget.RecyclerView - android:id="@+id/selected_speeds_grid" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:id="@+id/selected_speeds_grid" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <CheckBox + android:id="@+id/skipSilence" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/pref_skip_silence_title" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/app/src/main/res/layout/time_dialog.xml b/app/src/main/res/layout/time_dialog.xml index 263ac0c9c..2b5ae0985 100644 --- a/app/src/main/res/layout/time_dialog.xml +++ b/app/src/main/res/layout/time_dialog.xml @@ -1,164 +1,170 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="center" - android:padding="16dp"> + android:layout_height="wrap_content"> <LinearLayout - android:id="@+id/timeSetup" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical"> + android:orientation="vertical" + android:gravity="center" + android:padding="16dp"> <LinearLayout + android:id="@+id/timeSetup" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> + android:orientation="vertical"> - <EditText - android:id="@+id/etxtTime" - android:layout_width="0dp" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:selectAllOnFocus="true" - android:layout_margin="8dp" - android:ems="2" - android:inputType="number" - android:maxLength="3" /> + android:orientation="horizontal"> + + <EditText + android:id="@+id/etxtTime" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:selectAllOnFocus="true" + android:layout_margin="8dp" + android:ems="2" + android:inputType="number" + android:maxLength="3" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/time_minutes" + android:layout_marginBottom="8dp" + android:layout_marginTop="8dp" /> + + </LinearLayout> - <TextView - android:layout_width="wrap_content" + <Button + android:id="@+id/setSleeptimerButton" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/time_minutes" - android:layout_marginBottom="8dp" - android:layout_marginTop="8dp" /> + android:text="@string/set_sleeptimer_label" /> </LinearLayout> - <Button - android:id="@+id/setSleeptimerButton" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/set_sleeptimer_label" /> - - </LinearLayout> - - <LinearLayout - android:id="@+id/timeDisplay" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:visibility="visible"> - - <TextView - android:id="@+id/time" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="00:00:00" - android:layout_gravity="center" - android:gravity="center" - android:textSize="32sp" - android:textColor="?android:attr/textColorPrimary" /> - - <Button - android:id="@+id/disableSleeptimerButton" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/disable_sleeptimer_label" /> - <LinearLayout + android:id="@+id/timeDisplay" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal" - android:gravity="center_vertical"> + android:orientation="vertical" + android:visibility="visible"> - <Button - android:id="@+id/extendSleepFiveMinutesButton" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginEnd="4dp" - android:layout_marginRight="4dp" - android:paddingHorizontal="2dp" - android:paddingVertical="4dp" - android:layout_weight="1" - style="?attr/materialButtonOutlinedStyle" - tools:text="+5 min" /> + <TextView + android:id="@+id/time" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="00:00:00" + android:layout_gravity="center" + android:gravity="center" + android:textSize="32sp" + android:textColor="?android:attr/textColorPrimary" /> <Button - android:id="@+id/extendSleepTenMinutesButton" - android:layout_width="0dp" + android:id="@+id/disableSleeptimerButton" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="4dp" - android:layout_marginEnd="4dp" - android:paddingHorizontal="2dp" - android:paddingVertical="4dp" - android:layout_weight="1" - style="?attr/materialButtonOutlinedStyle" - tools:text="+10 min" /> + android:text="@string/disable_sleeptimer_label" /> - <Button - android:id="@+id/extendSleepTwentyMinutesButton" - android:layout_width="0dp" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="4dp" - android:layout_marginRight="4dp" - android:layout_marginLeft="4dp" - android:paddingHorizontal="2dp" - android:paddingVertical="4dp" - android:layout_weight="1" - style="?attr/materialButtonOutlinedStyle" - tools:text="+20 min" /> + android:orientation="horizontal" + android:gravity="center_vertical"> + + <Button + android:id="@+id/extendSleepFiveMinutesButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:paddingHorizontal="2dp" + android:paddingVertical="4dp" + android:layout_weight="1" + style="?attr/materialButtonOutlinedStyle" + tools:text="+5 min" /> + + <Button + android:id="@+id/extendSleepTenMinutesButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" + android:paddingHorizontal="2dp" + android:paddingVertical="4dp" + android:layout_weight="1" + style="?attr/materialButtonOutlinedStyle" + tools:text="+10 min" /> + + <Button + android:id="@+id/extendSleepTwentyMinutesButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginRight="4dp" + android:layout_marginLeft="4dp" + android:paddingHorizontal="2dp" + android:paddingVertical="4dp" + android:layout_weight="1" + style="?attr/materialButtonOutlinedStyle" + tools:text="+20 min" /> + + </LinearLayout> </LinearLayout> - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_marginTop="8dp"> - - <CheckBox - android:id="@+id/cbShakeToReset" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/shake_to_reset_label" /> - - <CheckBox - android:id="@+id/cbVibrate" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/timer_vibration_label" /> - <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal" - android:weightSum="1"> + android:orientation="vertical" + android:layout_marginTop="8dp"> <CheckBox - android:id="@+id/chAutoEnable" - android:layout_width="0dp" + android:id="@+id/cbShakeToReset" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:text="@string/auto_enable_label" /> + android:text="@string/shake_to_reset_label" /> - <ImageView - android:id="@+id/changeTimesButton" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:contentDescription="@string/auto_enable_change_times" - android:background="?attr/selectableItemBackgroundBorderless" - app:srcCompat="@drawable/ic_settings" /> + <CheckBox + android:id="@+id/cbVibrate" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/timer_vibration_label" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:weightSum="1"> + + <CheckBox + android:id="@+id/chAutoEnable" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/auto_enable_label" /> + + <ImageView + android:id="@+id/changeTimesButton" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:contentDescription="@string/auto_enable_change_times" + android:background="?attr/selectableItemBackgroundBorderless" + app:srcCompat="@drawable/ic_settings" /> + + </LinearLayout> </LinearLayout> </LinearLayout> -</LinearLayout> +</ScrollView> diff --git a/app/src/main/res/menu/downloads_completed.xml b/app/src/main/res/menu/downloads_completed.xml index 08179de58..a7753d080 100644 --- a/app/src/main/res/menu/downloads_completed.xml +++ b/app/src/main/res/menu/downloads_completed.xml @@ -20,12 +20,8 @@ android:id="@+id/refresh_item" android:title="@string/refresh_label" android:menuCategory="container" - android:icon="@drawable/ic_refresh" - app:showAsAction="always" /> + app:showAsAction="never" /> <item android:id="@+id/downloads_sort" - android:title="@string/sort"> - <menu></menu> - </item> - + android:title="@string/sort" /> </menu> diff --git a/app/src/main/res/menu/episodes.xml b/app/src/main/res/menu/episodes.xml index 358573c93..4c8e0dbb3 100644 --- a/app/src/main/res/menu/episodes.xml +++ b/app/src/main/res/menu/episodes.xml @@ -13,8 +13,7 @@ android:id="@+id/refresh_item" android:title="@string/refresh_label" android:menuCategory="container" - custom:showAsAction="always" - android:icon="@drawable/ic_refresh"/> + custom:showAsAction="never" /> <item android:id="@+id/filter_items" @@ -33,8 +32,6 @@ <item android:id="@+id/episodes_sort" android:title="@string/sort" - custom:showAsAction="never"> - <menu /> - </item> + custom:showAsAction="never" /> </menu> diff --git a/app/src/main/res/menu/feedlist.xml b/app/src/main/res/menu/feedlist.xml index 2086ff547..0baf434f1 100644 --- a/app/src/main/res/menu/feedlist.xml +++ b/app/src/main/res/menu/feedlist.xml @@ -10,10 +10,9 @@ </item> <item android:id="@+id/refresh_item" - android:icon="@drawable/ic_refresh" android:menuCategory="container" android:title="@string/refresh_label" - custom:showAsAction="always"> + custom:showAsAction="never"> </item> <item android:id="@+id/refresh_complete_item" diff --git a/app/src/main/res/menu/home.xml b/app/src/main/res/menu/home.xml index f80218c0c..a0bca6d50 100644 --- a/app/src/main/res/menu/home.xml +++ b/app/src/main/res/menu/home.xml @@ -12,8 +12,7 @@ android:id="@+id/refresh_item" android:title="@string/refresh_label" android:menuCategory="container" - custom:showAsAction="always" - android:icon="@drawable/ic_refresh"/> + custom:showAsAction="never" /> <item android:id="@+id/homesettings_items" diff --git a/app/src/main/res/menu/inbox.xml b/app/src/main/res/menu/inbox.xml index fba8eefdb..725d6e90e 100644 --- a/app/src/main/res/menu/inbox.xml +++ b/app/src/main/res/menu/inbox.xml @@ -12,14 +12,11 @@ android:id="@+id/refresh_item" android:title="@string/refresh_label" android:menuCategory="container" - custom:showAsAction="always" - android:icon="@drawable/ic_refresh"/> + custom:showAsAction="never" /> <item android:id="@+id/inbox_sort" - android:title="@string/sort"> - <menu></menu> - </item> + android:title="@string/sort" /> <item android:id="@+id/remove_all_inbox_item" diff --git a/app/src/main/res/menu/mediaplayer.xml b/app/src/main/res/menu/mediaplayer.xml index 5f60fe6f5..a99151ac8 100644 --- a/app/src/main/res/menu/mediaplayer.xml +++ b/app/src/main/res/menu/mediaplayer.xml @@ -30,9 +30,9 @@ <item android:id="@+id/audio_controls" - android:icon="@drawable/ic_sliders" android:title="@string/audio_controls" - custom:showAsAction="always"> + android:visible="false" + custom:showAsAction="never"> </item> <item @@ -74,8 +74,9 @@ <item android:id="@+id/share_item" + android:icon="@drawable/ic_share" android:menuCategory="container" - custom:showAsAction="never" + custom:showAsAction="always" android:title="@string/share_label"> </item> </menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/queue.xml b/app/src/main/res/menu/queue.xml index e4bb63808..5f34d0a96 100644 --- a/app/src/main/res/menu/queue.xml +++ b/app/src/main/res/menu/queue.xml @@ -12,8 +12,7 @@ android:id="@+id/refresh_item" android:title="@string/refresh_label" android:menuCategory="container" - custom:showAsAction="always" - android:icon="@drawable/ic_refresh"/> + custom:showAsAction="never" /> <item android:id="@+id/queue_lock" @@ -23,9 +22,7 @@ <item android:id="@+id/queue_sort" - android:title="@string/sort"> - <menu></menu> - </item> + android:title="@string/sort" /> <item android:id="@+id/clear_queue" diff --git a/app/src/main/res/menu/sort_menu.xml b/app/src/main/res/menu/sort_menu.xml deleted file mode 100644 index 96ad4b9f6..000000000 --- a/app/src/main/res/menu/sort_menu.xml +++ /dev/null @@ -1,99 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - - <item - android:id="@+id/sort_date" - android:title="@string/date"> - - <menu> - <item - android:id="@+id/sort_date_asc" - android:title="@string/sort_old_new"/> - <item - android:id="@+id/sort_date_desc" - android:title="@string/sort_new_old"/> - </menu> - </item> - - <item - android:id="@+id/sort_duration" - android:title="@string/duration"> - - <menu> - <item - android:id="@+id/sort_duration_asc" - android:title="@string/sort_short_long"/> - <item - android:id="@+id/sort_duration_desc" - android:title="@string/sort_long_short"/> - </menu> - </item> - - <item - android:id="@+id/sort_episode_title" - android:title="@string/episode_title"> - - <menu> - <item - android:id="@+id/sort_episode_title_asc" - android:title="@string/sort_a_z"/> - <item - android:id="@+id/sort_episode_title_desc" - android:title="@string/sort_z_a"/> - </menu> - </item> - - <item - android:id="@+id/sort_feed_title" - android:title="@string/feed_title"> - - <menu> - <item - android:id="@+id/sort_feed_title_asc" - android:title="@string/sort_a_z"/> - <item - android:id="@+id/sort_feed_title_desc" - android:title="@string/sort_z_a"/> - </menu> - </item> - - <item - android:id="@+id/sort_random" - android:title="@string/random"> - </item> - - <item - android:id="@+id/sort_smart_shuffle" - android:title="@string/smart_shuffle"> - - <menu> - <item - android:id="@+id/sort_smart_shuffle_asc" - android:title="@string/sort_old_new"/> - <item - android:id="@+id/sort_smart_shuffle_desc" - android:title="@string/sort_new_old"/> - </menu> - </item> - - <item - android:id="@+id/sort_size" - android:title="@string/size" - android:visible="false"> - - <menu> - <item - android:id="@+id/sort_size_small_large" - android:title="@string/sort_small_large"/> - <item - android:id="@+id/sort_size_large_small" - android:title="@string/sort_large_small"/> - </menu> - </item> - - <item - android:id="@+id/keep_sorted" - android:title="@string/keep_sorted" - android:checkable="true" /> - -</menu> diff --git a/app/src/main/res/menu/subscriptions.xml b/app/src/main/res/menu/subscriptions.xml index 845ee92a2..3a2614149 100644 --- a/app/src/main/res/menu/subscriptions.xml +++ b/app/src/main/res/menu/subscriptions.xml @@ -15,8 +15,7 @@ android:id="@+id/refresh_item" android:title="@string/refresh_label" android:menuCategory="container" - custom:showAsAction="always" - android:icon="@drawable/ic_refresh"/> + custom:showAsAction="never" /> <item android:id="@+id/subscriptions_filter" android:title="@string/filter" diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml index 03da6c669..dab3a062c 100644 --- a/app/src/main/res/values/integers.xml +++ b/app/src/main/res/values/integers.xml @@ -2,6 +2,5 @@ <resources> <integer name="subscriptions_default_num_of_columns">3</integer> <integer name="nav_drawer_screen_size_percent">80</integer> - <integer name="swipe_to_refresh_duration_in_ms">750</integer> <integer name="swipe_refresh_distance">300</integer> </resources>
\ No newline at end of file diff --git a/app/src/main/res/xml/preferences_user_interface.xml b/app/src/main/res/xml/preferences_user_interface.xml index 733649ce1..5cd25d59f 100644 --- a/app/src/main/res/xml/preferences_user_interface.xml +++ b/app/src/main/res/xml/preferences_user_interface.xml @@ -70,9 +70,9 @@ android:summary="@string/pref_persistNotify_sum" android:title="@string/pref_persistNotify_title"/> <Preference - android:key="prefCompactNotificationButtons" - android:summary="@string/pref_compact_notification_buttons_sum" - android:title="@string/pref_compact_notification_buttons_title"/> + android:key="prefFullNotificationButtons" + android:summary="@string/pref_full_notification_buttons_sum" + android:title="@string/pref_full_notification_buttons_title"/> </PreferenceCategory> <PreferenceCategory android:title="@string/behavior"> <de.danoeh.antennapod.preferences.MaterialListPreference diff --git a/core/src/debug/res/mipmap-hdpi/ic_launcher.png b/core/src/debug/res/mipmap-hdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 6f8022e25..000000000 --- a/core/src/debug/res/mipmap-hdpi/ic_launcher.png +++ /dev/null diff --git a/core/src/debug/res/mipmap-hdpi/ic_launcher_background.png b/core/src/debug/res/mipmap-hdpi/ic_launcher_background.png Binary files differnew file mode 100644 index 000000000..da2b8d47b --- /dev/null +++ b/core/src/debug/res/mipmap-hdpi/ic_launcher_background.png diff --git a/core/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png b/core/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png Binary files differnew file mode 100644 index 000000000..dd7c03e27 --- /dev/null +++ b/core/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png diff --git a/core/src/debug/res/mipmap-mdpi/ic_launcher.png b/core/src/debug/res/mipmap-mdpi/ic_launcher.png Binary files differdeleted file mode 100644 index d542d555f..000000000 --- a/core/src/debug/res/mipmap-mdpi/ic_launcher.png +++ /dev/null diff --git a/core/src/debug/res/mipmap-mdpi/ic_launcher_background.png b/core/src/debug/res/mipmap-mdpi/ic_launcher_background.png Binary files differnew file mode 100644 index 000000000..701d43516 --- /dev/null +++ b/core/src/debug/res/mipmap-mdpi/ic_launcher_background.png diff --git a/core/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png b/core/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png Binary files differnew file mode 100644 index 000000000..b7a85063f --- /dev/null +++ b/core/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png diff --git a/core/src/debug/res/mipmap-xhdpi/ic_launcher.png b/core/src/debug/res/mipmap-xhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index a02ec4ca8..000000000 --- a/core/src/debug/res/mipmap-xhdpi/ic_launcher.png +++ /dev/null diff --git a/core/src/debug/res/mipmap-xhdpi/ic_launcher_background.png b/core/src/debug/res/mipmap-xhdpi/ic_launcher_background.png Binary files differnew file mode 100644 index 000000000..1adaed041 --- /dev/null +++ b/core/src/debug/res/mipmap-xhdpi/ic_launcher_background.png diff --git a/core/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png b/core/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png Binary files differnew file mode 100644 index 000000000..4d5a15f39 --- /dev/null +++ b/core/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/core/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/core/src/debug/res/mipmap-xxhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 066f9e5a5..000000000 --- a/core/src/debug/res/mipmap-xxhdpi/ic_launcher.png +++ /dev/null diff --git a/core/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png b/core/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png Binary files differnew file mode 100644 index 000000000..31d4b272e --- /dev/null +++ b/core/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png diff --git a/core/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png b/core/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png Binary files differnew file mode 100644 index 000000000..75caa42c9 --- /dev/null +++ b/core/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/core/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/core/src/debug/res/mipmap-xxxhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 7dbab284c..000000000 --- a/core/src/debug/res/mipmap-xxxhdpi/ic_launcher.png +++ /dev/null diff --git a/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png b/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png Binary files differnew file mode 100644 index 000000000..9edb9ab01 --- /dev/null +++ b/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png diff --git a/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png Binary files differnew file mode 100644 index 000000000..630363954 --- /dev/null +++ b/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_monochrome.png Binary files differnew file mode 100644 index 000000000..d7e12bda3 --- /dev/null +++ b/core/src/debug/res/mipmap-xxxhdpi/ic_launcher_monochrome.png diff --git a/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java b/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java index 3b9d6a08d..829add126 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java @@ -3,30 +3,12 @@ package de.danoeh.antennapod.core.menuhandler; import android.view.Menu; import android.view.MenuItem; -import de.danoeh.antennapod.core.R; - /** * Utilities for menu items */ public class MenuItemUtils { /** - * @param menu The menu that the MenuItem belongs to - * @param resId The id of the MenuItem - */ - public static void updateRefreshMenuItem(Menu menu, int resId, boolean isRefreshing) { - // expand actionview if feeds are being downloaded, collapse otherwise - MenuItem refreshItem = menu.findItem(resId); - if (isRefreshing) { - if (refreshItem.getActionView() == null) { - refreshItem.setActionView(R.layout.refresh_action_view); - } - } else { - refreshItem.setActionView(null); - } - } - - /** * When pressing a context menu item, Android calls onContextItemSelected * for ALL fragments in arbitrary order, not just for the fragment that the * context menu was created from. This assigns the listener to every menu item, diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java index 08bdd39bc..161af58e1 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java +++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java @@ -17,6 +17,8 @@ import de.danoeh.antennapod.core.ClientConfigurator; public class MediaButtonReceiver extends BroadcastReceiver { private static final String TAG = "MediaButtonReceiver"; public static final String EXTRA_KEYCODE = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.KEYCODE"; + public static final String EXTRA_CUSTOM_ACTION = + "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.CUSTOM_ACTION"; public static final String EXTRA_SOURCE = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.SOURCE"; public static final String EXTRA_HARDWAREBUTTON = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.HARDWAREBUTTON"; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 5c8dead81..02102db14 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -63,10 +63,12 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.service.QuickSettingsTileService; +import de.danoeh.antennapod.core.service.playback.PlaybackServiceTaskManager.SleepTimer; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.FeedSearcher; import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink; +import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.FeedUtil; import de.danoeh.antennapod.core.util.IntentUtils; @@ -84,12 +86,12 @@ import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; 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.model.feed.Chapter; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItemFilter; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.feed.FeedPreferences; -import de.danoeh.antennapod.model.feed.SortOrder; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.model.playback.Playable; import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; @@ -127,6 +129,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { private static final String CUSTOM_ACTION_REWIND = "action.de.danoeh.antennapod.core.service.rewind"; private static final String CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED = "action.de.danoeh.antennapod.core.service.changePlaybackSpeed"; + public static final String CUSTOM_ACTION_NEXT_CHAPTER = "action.de.danoeh.antennapod.core.service.next_chapter"; /** * Set a max number of episodes to load for Android Auto, otherwise there could be performance issues @@ -327,7 +330,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { if (rootHints != null && rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) { Bundle extras = new Bundle(); extras.putBoolean(BrowserRoot.EXTRA_RECENT, true); - return new BrowserRoot(getResources().getString(R.string.recently_played_episodes), extras); + Log.d(TAG, "OnGetRoot: Returning BrowserRoot " + R.string.current_playing_episode); + return new BrowserRoot(getResources().getString(R.string.current_playing_episode), extras); } // Name visible in Android Auto @@ -408,6 +412,11 @@ public class PlaybackService extends MediaBrowserServiceCompat { private List<MediaBrowserCompat.MediaItem> loadChildrenSynchronous(@NonNull String parentId) { List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>(); if (parentId.equals(getResources().getString(R.string.app_name))) { + long currentlyPlaying = PlaybackPreferences.getCurrentPlayerStatus(); + if (currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PLAYING + || currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PAUSED) { + mediaItems.add(createBrowsableMediaItem(R.string.current_playing_episode, R.drawable.ic_play_48dp, 1)); + } mediaItems.add(createBrowsableMediaItem(R.string.queue_label, R.drawable.ic_playlist_play_black, DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.QUEUED)))); mediaItems.add(createBrowsableMediaItem(R.string.downloads_label, R.drawable.ic_download_black, @@ -429,11 +438,12 @@ public class PlaybackService extends MediaBrowserServiceCompat { new FeedItemFilter(FeedItemFilter.DOWNLOADED), UserPreferences.getDownloadsSortedOrder()); } else if (parentId.equals(getResources().getString(R.string.episodes_label))) { feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED, - new FeedItemFilter(FeedItemFilter.UNPLAYED), SortOrder.DATE_NEW_OLD); + new FeedItemFilter(FeedItemFilter.UNPLAYED), UserPreferences.getAllEpisodesSortOrder()); } else if (parentId.startsWith("FeedId:")) { long feedId = Long.parseLong(parentId.split(":")[1]); - feedItems = DBReader.getFeedItemList(DBReader.getFeed(feedId)); - } else if (parentId.equals(getString(R.string.recently_played_episodes))) { + Feed feed = DBReader.getFeed(feedId); + feedItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), feed.getSortOrder()); + } else if (parentId.equals(getString(R.string.current_playing_episode))) { Playable playable = PlaybackPreferences.createInstanceFromPreferences(this); if (playable instanceof FeedMedia) { feedItems = Collections.singletonList(((FeedMedia) playable).getItem()); @@ -476,9 +486,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { notificationManager.cancel(R.id.notification_streaming_confirmation); final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1); + final String customAction = intent.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION); final boolean hardwareButton = intent.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false); Playable playable = intent.getParcelableExtra(PlaybackServiceInterface.EXTRA_PLAYABLE); - if (keycode == -1 && playable == null) { + if (keycode == -1 && playable == null && customAction == null) { Log.e(TAG, "PlaybackService was started with no arguments"); stateManager.stopService(); return Service.START_NOT_STICKY; @@ -502,7 +513,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { stateManager.stopService(); return Service.START_NOT_STICKY; } - } else { + } else if (playable != null) { stateManager.validStartCommandWasReceived(); boolean allowStreamThisTime = intent.getBooleanExtra( PlaybackServiceInterface.EXTRA_ALLOW_STREAM_THIS_TIME, false); @@ -530,6 +541,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { stateManager.stopService(); }); return Service.START_NOT_STICKY; + } else { + mediaSession.getController().getTransportControls().sendCustomAction(customAction, null); } } @@ -777,6 +790,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { @Override public void onChapterLoaded(Playable media) { sendNotificationBroadcast(PlaybackServiceInterface.NOTIFICATION_TYPE_RELOAD, 0); + updateMediaSession(mediaPlayer.getPlayerStatus()); } }; @@ -965,7 +979,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { if (event.isOver()) { mediaPlayer.pause(true, true); mediaPlayer.setVolume(1.0f, 1.0f); - } else if (event.getTimeLeft() < PlaybackServiceTaskManager.SleepTimer.NOTIFICATION_THRESHOLD) { + int newPosition = mediaPlayer.getPosition() - (int) SleepTimer.NOTIFICATION_THRESHOLD / 2; + newPosition = Math.max(newPosition, 0); + seekTo(newPosition); + } else if (event.getTimeLeft() < SleepTimer.NOTIFICATION_THRESHOLD) { final float[] multiplicators = {0.1f, 0.2f, 0.3f, 0.3f, 0.3f, 0.4f, 0.4f, 0.4f, 0.6f, 0.8f}; float multiplicator = multiplicators[Math.max(0, (int) event.getTimeLeft() / 1000)]; Log.d(TAG, "onSleepTimerAlmostExpired: " + multiplicator); @@ -1244,20 +1261,35 @@ public class PlaybackService extends MediaBrowserServiceCompat { WearMediaSession.addWearExtrasToAction(fastForwardBuilder); sessionState.addCustomAction(fastForwardBuilder.build()); - sessionState.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED, - getString(R.string.playback_speed), - R.drawable.ic_notification_playback_speed + if (UserPreferences.showPlaybackSpeedOnFullNotification()) { + sessionState.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED, + getString(R.string.playback_speed), + R.drawable.ic_notification_playback_speed ).build() - ); - sessionState.addCustomAction( + ); + } + + if (UserPreferences.showNextChapterOnFullNotification()) { + if (getPlayable() != null && getPlayable().getChapters() != null) { + sessionState.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + CUSTOM_ACTION_NEXT_CHAPTER, + getString(R.string.next_chapter), R.drawable.ic_notification_next_chapter) + .build()); + } + } + + if (UserPreferences.showSkipOnFullNotification()) { + sessionState.addCustomAction( new PlaybackStateCompat.CustomAction.Builder( - CUSTOM_ACTION_SKIP_TO_NEXT, - getString(R.string.skip_episode_label), - R.drawable.ic_notification_skip + CUSTOM_ACTION_SKIP_TO_NEXT, + getString(R.string.skip_episode_label), + R.drawable.ic_notification_skip ).build() - ); + ); + } WearMediaSession.mediaSessionSetExtraForWear(mediaSession); @@ -1765,6 +1797,12 @@ public class PlaybackService extends MediaBrowserServiceCompat { public void onPlayFromSearch(String query, Bundle extras) { Log.d(TAG, "onPlayFromSearch query=" + query + " extras=" + extras.toString()); + if (query.equals("")) { + Log.d(TAG, "onPlayFromSearch called with empty query, resuming from the last position"); + startPlayingFromPreferences(); + return; + } + List<FeedItem> results = FeedSearcher.searchFeedItems(query, 0); if (results.size() > 0 && results.get(0).getMedia() != null) { FeedMedia media = results.get(0).getMedia(); @@ -1800,6 +1838,26 @@ public class PlaybackService extends MediaBrowserServiceCompat { seekDelta(-UserPreferences.getRewindSecs() * 1000); } + public void onNextChapter() { + List<Chapter> chapters = mediaPlayer.getPlayable().getChapters(); + if (chapters == null) { + // No chapters, just fallback to next episode + mediaPlayer.skip(); + return; + } + + int nextChapter = ChapterUtils.getCurrentChapterIndex( + mediaPlayer.getPlayable(), mediaPlayer.getPosition()) + 1; + + if (chapters.size() < nextChapter + 1) { + // We are on the last chapter, just fallback to the next episode + mediaPlayer.skip(); + return; + } + + mediaPlayer.seekTo((int) chapters.get(nextChapter).getStart()); + } + @Override public void onFastForward() { Log.d(TAG, "onFastForward()"); @@ -1872,6 +1930,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { onRewind(); } else if (CUSTOM_ACTION_SKIP_TO_NEXT.equals(action)) { mediaPlayer.skip(); + } else if (CUSTOM_ACTION_NEXT_CHAPTER.equals(action)) { + onNextChapter(); } else if (CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED.equals(action)) { List<Float> selectedSpeeds = UserPreferences.getPlaybackSpeedArray(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java index fe98dbc8f..471dc7454 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java @@ -175,14 +175,12 @@ public class PlaybackServiceNotificationBuilder { ArrayList<Integer> compactActionList = new ArrayList<>(); int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction - // always let them rewind + PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_REWIND, numActions); notification.addAction(R.drawable.ic_notification_fast_rewind, context.getString(R.string.rewind_label), rewindButtonPendingIntent); - if (UserPreferences.showRewindOnCompactNotification()) { - compactActionList.add(numActions); - } + compactActionList.add(numActions); numActions++; if (playerStatus == PlayerStatus.PLAYING) { @@ -205,19 +203,24 @@ public class PlaybackServiceNotificationBuilder { KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions); notification.addAction(R.drawable.ic_notification_fast_forward, context.getString(R.string.fast_forward_label), ffButtonPendingIntent); - if (UserPreferences.showFastForwardOnCompactNotification()) { - compactActionList.add(numActions); - } + compactActionList.add(numActions); numActions++; - PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_NEXT, numActions); - notification.addAction(R.drawable.ic_notification_skip, context.getString(R.string.skip_episode_label), - skipButtonPendingIntent); - if (UserPreferences.showSkipOnCompactNotification()) { - compactActionList.add(numActions); + if (UserPreferences.showNextChapterOnFullNotification() && playable.getChapters() != null) { + PendingIntent nextChapterPendingIntent = getPendingIntentForCustomMediaAction( + PlaybackService.CUSTOM_ACTION_NEXT_CHAPTER, numActions); + notification.addAction(R.drawable.ic_notification_next_chapter, context.getString(R.string.next_chapter), + nextChapterPendingIntent); + numActions++; + } + + if (UserPreferences.showSkipOnFullNotification()) { + PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction( + KeyEvent.KEYCODE_MEDIA_NEXT, numActions); + notification.addAction(R.drawable.ic_notification_skip, context.getString(R.string.skip_episode_label), + skipButtonPendingIntent); + numActions++; } - numActions++; PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_STOP, numActions); @@ -242,6 +245,20 @@ public class PlaybackServiceNotificationBuilder { } } + private PendingIntent getPendingIntentForCustomMediaAction(String action, int requestCode) { + Intent intent = new Intent(context, PlaybackService.class); + intent.setAction("MediaAction" + action); + intent.putExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION, action); + + if (Build.VERSION.SDK_INT >= 26) { + return PendingIntent.getForegroundService(context, requestCode, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + return PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); + } + } + public void setMediaSessionToken(MediaSessionCompat.Token mediaSessionToken) { this.mediaSessionToken = mediaSessionToken; } @@ -253,4 +270,4 @@ public class PlaybackServiceNotificationBuilder { public PlayerStatus getPlayerStatus() { return playerStatus; } -}
\ No newline at end of file +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java index dbbfba379..ecfe5f4dd 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java @@ -69,7 +69,9 @@ public class AutomaticDownloadAlgorithm { Iterator<FeedItem> it = candidates.iterator(); while (it.hasNext()) { FeedItem item = it.next(); - if (!item.isAutoDownloadable(System.currentTimeMillis()) + if (!item.isAutoDownloadEnabled() + || item.isDownloaded() + || !item.hasMedia() || PlaybackStatus.isPlaying(item.getMedia()) || item.getFeed().isLocalFeed()) { it.remove(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index 084b3a7ad..492dff759 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -15,9 +15,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import de.danoeh.antennapod.core.util.FeedItemPermutors; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.comparator.DownloadResultComparator; -import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; import de.danoeh.antennapod.core.util.comparator.PlaybackCompletionDateComparator; import de.danoeh.antennapod.model.feed.Chapter; import de.danoeh.antennapod.model.feed.Feed; @@ -170,13 +170,18 @@ public final class DBReader { } public static List<FeedItem> getFeedItemList(final Feed feed, final FeedItemFilter filter) { + return getFeedItemList(feed, filter, SortOrder.DATE_NEW_OLD); + } + + public static List<FeedItem> getFeedItemList(final Feed feed, final FeedItemFilter filter, SortOrder sortOrder) { Log.d(TAG, "getFeedItemList() called with: " + "feed = [" + feed + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); try (Cursor cursor = adapter.getItemsOfFeedCursor(feed, filter)) { List<FeedItem> items = extractItemlistFromCursor(adapter, cursor); - Collections.sort(items, new FeedItemPubdateComparator()); + FeedItemPermutors.getPermutor(sortOrder).reorder(items); + feed.setItems(items); for (FeedItem item : items) { item.setFeed(feed); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java index 35d77ae4a..2ff9d8848 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java @@ -28,7 +28,7 @@ public class FeedItemDuplicateGuesser { return titlesLookSimilar(item1, item2) && datesLookSimilar(item1, item2) && durationsLookSimilar(media1, media2) - && TextUtils.equals(media1.getMime_type(), media2.getMime_type()); + && mimeTypeLooksSimilar(media1, media2); } private static boolean sameAndNotEmpty(String string1, String string2) { @@ -52,6 +52,19 @@ public class FeedItemDuplicateGuesser { return Math.abs(media1.getDuration() - media2.getDuration()) < 10 * 60L * 1000L; } + private static boolean mimeTypeLooksSimilar(FeedMedia media1, FeedMedia media2) { + String mimeType1 = media1.getMime_type(); + String mimeType2 = media2.getMime_type(); + if (mimeType1 == null || mimeType2 == null) { + return true; + } + if (mimeType1.contains("/") && mimeType2.contains("/")) { + mimeType1 = mimeType1.substring(0, mimeType1.indexOf("/")); + mimeType2 = mimeType2.substring(0, mimeType2.indexOf("/")); + } + return TextUtils.equals(mimeType1, mimeType2); + } + private static boolean titlesLookSimilar(FeedItem item1, FeedItem item2) { return sameAndNotEmpty(canonicalizeTitle(item1.getTitle()), canonicalizeTitle(item2.getTitle())); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java index 13f2af762..630507487 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java @@ -10,6 +10,7 @@ import androidx.core.app.ShareCompat; import androidx.core.content.FileProvider; import java.io.File; +import java.net.URLEncoder; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.model.feed.Feed; @@ -33,12 +34,12 @@ public class ShareUtils { } public static void shareFeedLink(Context context, Feed feed) { - String text = feed.getTitle(); - if (feed.getLink() != null) { - text += "\n" + feed.getLink(); - } - text += "\n\n" + context.getResources().getString(R.string.share_rss_address_label) - + " " + feed.getDownload_url(); + String text = feed.getTitle() + + "\n\n" + + "https://antennapod.org/deeplink/subscribe/?url=" + + URLEncoder.encode(feed.getDownload_url()) + + "&title=" + + URLEncoder.encode(feed.getTitle()); shareLink(context, text); } diff --git a/core/src/main/res/drawable/ic_disc_alert.xml b/core/src/main/res/drawable/ic_disc_alert.xml new file mode 100644 index 000000000..6a2c11187 --- /dev/null +++ b/core/src/main/res/drawable/ic_disc_alert.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" + android:width="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="?attr/action_icon_color" + android:pathData="M10 14C8.9 14 8 13.1 8 12C8 10.9 8.9 10 10 10C11.1 10 12 10.9 12 12S11.1 14 10 14M10 4C5.6 4 2 7.6 2 12S5.6 20 10 20 18 16.4 18 12 14.4 4 10 4M20 13H22V7H20M20 17H22V15H20V17Z" /> + +</vector> diff --git a/core/src/main/res/drawable/ic_disc_full.xml b/core/src/main/res/drawable/ic_disc_full.xml deleted file mode 100644 index 2aba1bc53..000000000 --- a/core/src/main/res/drawable/ic_disc_full.xml +++ /dev/null @@ -1,9 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="20dp" - android:height="16dp" - android:viewportWidth="20" - android:viewportHeight="16"> - <path - android:pathData="M17.1 11.5s0-1.7 0-1.7s1.8 0 1.8 0s0 1.7 0 1.7s-1.8 0-1.8 0zm0-8s1.8 0 1.8 0s0 4.5 0 4.5s-1.8 0-1.8 0s0-4.5 0-4.5zm-8.9-2.6c2 0 3.6 0.7 5 2.1s2.1 3 2.1 5s-0.7 3.7-2.1 5s-3 2.1-5 2.1c-1.9 0-3.6-0.7-5-2.1s-2.1-3-2.1-5s0.7-3.6 2.1-5s3.1-2.1 5-2.1zm0 8.9c0.5 0 0.9-0.2 1.3-0.5s0.5-0.8 0.5-1.3s-0.2-0.9-0.5-1.3s-0.8-0.5-1.3-0.5s-0.9 0.2-1.2 0.5s-0.6 0.8-0.6 1.3s0.2 0.9 0.6 1.3s0.7 0.5 1.2 0.5z" - android:fillColor="?android:attr/textColorPrimary" /> -</vector>
\ No newline at end of file diff --git a/core/src/main/res/drawable/ic_paperclip.xml b/core/src/main/res/drawable/ic_paperclip.xml new file mode 100644 index 000000000..dd6673c13 --- /dev/null +++ b/core/src/main/res/drawable/ic_paperclip.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:height="12dp" + android:width="12dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <group> + <path + android:fillColor="?attr/action_icon_color" + android:pathData="M16.5,6V17.5A4,4 0 0,1 12.5,21.5A4,4 0 0,1 8.5,17.5V5A2.5,2.5 0 0,1 11,2.5A2.5,2.5 0 0,1 13.5,5V15.5A1,1 0 0,1 12.5,16.5A1,1 0 0,1 11.5,15.5V6H10V15.5A2.5,2.5 0 0,0 12.5,18A2.5,2.5 0 0,0 15,15.5V5A4,4 0 0,0 11,1A4,4 0 0,0 7,5V17.5A5.5,5.5 0 0,0 12.5,23A5.5,5.5 0 0,0 18,17.5V6H16.5Z" /> + + </group> + +</vector> diff --git a/core/src/main/res/drawable/ic_sliders.xml b/core/src/main/res/drawable/ic_sliders.xml deleted file mode 100644 index c6f3de7b4..000000000 --- a/core/src/main/res/drawable/ic_sliders.xml +++ /dev/null @@ -1,5 +0,0 @@ -<vector android:height="24dp" - android:viewportHeight="24.0" android:viewportWidth="24.0" - android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="?attr/action_icon_color" android:pathData="M3,17V19H9V17H3M3,5V7H13V5H3M13,21V19H21V17H13V15H11V21H13M7,9V11H3V13H7V15H9V9H7M21,13V11H11V13H21M15,9H17V7H21V5H17V3H15V9Z"/> -</vector> diff --git a/core/src/main/res/layout/refresh_action_view.xml b/core/src/main/res/layout/refresh_action_view.xml deleted file mode 100644 index d5b88922e..000000000 --- a/core/src/main/res/layout/refresh_action_view.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<ProgressBar - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_gravity="end" - android:padding="8dp" - android:indeterminateOnly="true"/> diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml index 50afe630b..7eeab886a 100644 --- a/core/src/main/res/values/arrays.xml +++ b/core/src/main/res/values/arrays.xml @@ -246,52 +246,10 @@ <item>DownloadsSection</item> </string-array> - <!-- sort for podcast screen, not for queue --> - <string-array name="feed_episodes_sort_options"> - <item>@string/sort_date_new_old</item> - <item>@string/sort_date_old_new</item> - <item>@string/sort_title_a_z</item> - <item>@string/sort_title_z_a</item> - <item>@string/sort_duration_short_long</item> - <item>@string/sort_duration_long_short</item> - </string-array> - - <!-- sort for local feed screen --> - <string-array name="local_feed_episodes_sort_options"> - <item>@string/sort_date_new_old</item> - <item>@string/sort_date_old_new</item> - <item>@string/sort_title_a_z</item> - <item>@string/sort_title_z_a</item> - <item>@string/sort_duration_short_long</item> - <item>@string/sort_duration_long_short</item> - <item>@string/sort_filename_a_z</item> - <item>@string/sort_filename_z_a</item> - </string-array> - - <string-array name="feed_episodes_sort_values"> - <item>DATE_NEW_OLD</item> - <item>DATE_OLD_NEW</item> - <item>EPISODE_TITLE_A_Z</item> - <item>EPISODE_TITLE_Z_A</item> - <item>DURATION_SHORT_LONG</item> - <item>DURATION_LONG_SHORT</item> - </string-array> - - <string-array name="local_feed_episodes_sort_values"> - <item>DATE_NEW_OLD</item> - <item>DATE_OLD_NEW</item> - <item>EPISODE_TITLE_A_Z</item> - <item>EPISODE_TITLE_Z_A</item> - <item>DURATION_SHORT_LONG</item> - <item>DURATION_LONG_SHORT</item> - <item>EPISODE_FILENAME_A_Z</item> - <item>EPISODE_FILENAME_Z_A</item> - </string-array> - - <string-array name="compact_notification_buttons_options"> - <item>@string/rewind_label</item> - <item>@string/fast_forward_label</item> + <string-array name="full_notification_buttons_options"> <item>@string/skip_episode_label</item> + <item>@string/next_chapter</item> + <item>@string/playback_speed</item> </string-array> <string-array name="default_page_values"> diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java index a08d0897d..62775b84b 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.core.feed; import de.danoeh.antennapod.model.feed.FeedItem; -import de.danoeh.antennapod.model.feed.FeedMedia; import org.junit.Before; import org.junit.Test; @@ -11,13 +10,11 @@ import java.util.Date; import static de.danoeh.antennapod.core.feed.FeedItemMother.anyFeedItemWithImage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; public class FeedItemTest { private static final String TEXT_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; private static final String TEXT_SHORT = "Lorem ipsum"; - private static final long ONE_HOUR = 1000L * 3600L; private FeedItem original; private FeedItem changedFeedItem; @@ -139,36 +136,4 @@ public class FeedItemTest { item.setDescriptionIfLonger(contentEncoded); assertEquals(TEXT_LONG, item.getDescription()); } - - @Test - public void testAutoDownloadBackoff() { - FeedItem item = new FeedItem(); - item.setMedia(new FeedMedia(item, "https://example.com/file.mp3", 0, "audio/mpeg")); - - long now = ONE_HOUR; // In reality, this is System.currentTimeMillis() - assertTrue(item.isAutoDownloadable(now)); - item.increaseFailedAutoDownloadAttempts(now); - assertFalse(item.isAutoDownloadable(now)); - - now += ONE_HOUR; - assertTrue(item.isAutoDownloadable(now)); - item.increaseFailedAutoDownloadAttempts(now); - assertFalse(item.isAutoDownloadable(now)); - - now += ONE_HOUR; - assertFalse(item.isAutoDownloadable(now)); // Should backoff, so more than 1 hour needed - - now += ONE_HOUR; - assertTrue(item.isAutoDownloadable(now)); // Now it's enough - item.increaseFailedAutoDownloadAttempts(now); - item.increaseFailedAutoDownloadAttempts(now); - item.increaseFailedAutoDownloadAttempts(now); - - now += 1000L * ONE_HOUR; - assertFalse(item.isAutoDownloadable(now)); // Should have given up - item.increaseFailedAutoDownloadAttempts(now); - - now += 1000L * ONE_HOUR; - assertFalse(item.isAutoDownloadable(now)); // Still given up - } } diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java index ac7cdee1f..f3c993066 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java @@ -44,6 +44,9 @@ public class FeedItemDuplicateGuesserTest { assertFalse(FeedItemDuplicateGuesser.seemDuplicates( item("id1", "Title", "example.com/episode1", 10, 5 * MINUTES, "audio/*"), item("id2", "Title", "example.com/episode2", 10, 5 * MINUTES, "video/*"))); + assertTrue(FeedItemDuplicateGuesser.seemDuplicates( + item("id1", "Title", "example.com/episode1", 10, 5 * MINUTES, "audio/mpeg"), + item("id2", "Title", "example.com/episode2", 10, 5 * MINUTES, "audio/mp3"))); assertFalse(FeedItemDuplicateGuesser.seemDuplicates( item("id1", "Title", "example.com/episode1", 5 * DAYS, 5 * MINUTES, "audio/*"), item("id2", "Title", "example.com/episode2", 2 * DAYS, 5 * MINUTES, "audio/*"))); diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java index a8570ea4e..be0760bdb 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java @@ -11,7 +11,6 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.TimeUnit; /** * Item (episode) within a feed. @@ -65,7 +64,7 @@ public class FeedItem extends FeedComponent implements Serializable { private transient List<Chapter> chapters; private String imageUrl; - private long autoDownload = 1; + private boolean autoDownloadEnabled = true; /** * Any tags assigned to this item @@ -82,7 +81,7 @@ public class FeedItem extends FeedComponent implements Serializable { * */ public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId, boolean hasChapters, String imageUrl, int state, - String itemIdentifier, long autoDownload, String podcastIndexChapterUrl) { + String itemIdentifier, boolean autoDownloadEnabled, String podcastIndexChapterUrl) { this.id = id; this.title = title; this.link = link; @@ -93,7 +92,7 @@ public class FeedItem extends FeedComponent implements Serializable { this.imageUrl = imageUrl; this.state = state; this.itemIdentifier = itemIdentifier; - this.autoDownload = autoDownload; + this.autoDownloadEnabled = autoDownloadEnabled; this.podcastIndexChapterUrl = podcastIndexChapterUrl; } @@ -361,50 +360,11 @@ public class FeedItem extends FeedComponent implements Serializable { } public void disableAutoDownload() { - this.autoDownload = 0; + this.autoDownloadEnabled = false; } - public long getAutoDownloadAttemptsAndTime() { - return autoDownload; - } - - public int getFailedAutoDownloadAttempts() { - // 0: auto download disabled - // 1: auto download enabled (default) - // > 1: auto download enabled, timestamp of last failed attempt, last digit denotes number of failed attempts - if (autoDownload <= 1) { - return 0; - } - int failedAttempts = (int)(autoDownload % 10); - if (failedAttempts == 0) { - failedAttempts = 10; - } - return failedAttempts; - } - - public void increaseFailedAutoDownloadAttempts(long now) { - if (autoDownload == 0) { - return; // Don't re-enable - } - int failedAttempts = getFailedAutoDownloadAttempts() + 1; - if (failedAttempts >= 5) { - disableAutoDownload(); // giving up - } else { - autoDownload = (now / 10) * 10 + failedAttempts; - } - } - - public boolean isAutoDownloadable(long now) { - if (media == null || media.isDownloaded() || autoDownload == 0) { - return false; - } - if (autoDownload == 1) { - return true; // Never failed - } - int failedAttempts = getFailedAutoDownloadAttempts(); - long waitingTime = TimeUnit.HOURS.toMillis((long) Math.pow(2, failedAttempts - 1)); - long lastAttempt = (autoDownload / 10) * 10; - return now >= (lastAttempt + waitingTime); + public boolean isAutoDownloadEnabled() { + return this.autoDownloadEnabled; } public boolean isDownloaded() { diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java index e4a1f5a3d..cbac417a9 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java @@ -121,9 +121,9 @@ class DBUpgrader { } if (oldVersion <= 14) { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER"); + + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER"); db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS + " = " + + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED + " = " + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java index 96d80c209..ccef19dc1 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java @@ -95,7 +95,6 @@ public class PodDBAdapter { public static final String KEY_REASON_DETAILED = "reason_detailed"; public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; - public static final String KEY_AUTO_DOWNLOAD_ATTEMPTS = "auto_download"; public static final String KEY_AUTO_DOWNLOAD_ENABLED = "auto_download"; // Both tables use the same key public static final String KEY_KEEP_UPDATED = "keep_updated"; public static final String KEY_AUTO_DELETE_ACTION = "auto_delete_action"; @@ -171,7 +170,7 @@ public class PodDBAdapter { + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT," + KEY_IMAGE_URL + " TEXT," - + KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER," + + KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER," + KEY_PODCASTINDEX_CHAPTER_URL + " TEXT)"; private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE " @@ -259,7 +258,7 @@ public class PodDBAdapter { + TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + ", " - + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD_ATTEMPTS + ", " + + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD_ENABLED + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_PODCASTINDEX_CHAPTER_URL; private static final String KEYS_FEED_MEDIA = @@ -652,7 +651,7 @@ public class PodDBAdapter { } values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters()); values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); - values.put(KEY_AUTO_DOWNLOAD_ATTEMPTS, item.getAutoDownloadAttemptsAndTime()); + values.put(KEY_AUTO_DOWNLOAD_ENABLED, item.isAutoDownloadEnabled()); values.put(KEY_IMAGE_URL, item.getImageUrl()); values.put(KEY_PODCASTINDEX_CHAPTER_URL, item.getPodcastIndexChapterUrl()); diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java index fcf51e31e..c2c9b89d4 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java @@ -25,7 +25,7 @@ public abstract class FeedItemCursorMapper { int indexHasChapters = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_HAS_CHAPTERS); int indexRead = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_READ); int indexItemIdentifier = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_ITEM_IDENTIFIER); - int indexAutoDownload = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS); + int indexAutoDownload = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED); int indexImageUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_IMAGE_URL); int indexPodcastIndexChapterUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_PODCASTINDEX_CHAPTER_URL); @@ -38,11 +38,11 @@ public abstract class FeedItemCursorMapper { boolean hasChapters = cursor.getInt(indexHasChapters) > 0; int state = cursor.getInt(indexRead); String itemIdentifier = cursor.getString(indexItemIdentifier); - long autoDownload = cursor.getLong(indexAutoDownload); + boolean autoDownloadEnabled = cursor.getLong(indexAutoDownload) > 0; String imageUrl = cursor.getString(indexImageUrl); String podcastIndexChapterUrl = cursor.getString(indexPodcastIndexChapterUrl); return new FeedItem(id, title, link, pubDate, paymentLink, feedId, - hasChapters, imageUrl, state, itemIdentifier, autoDownload, podcastIndexChapterUrl); + hasChapters, imageUrl, state, itemIdentifier, autoDownloadEnabled, podcastIndexChapterUrl); } } diff --git a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java index 80a58525d..b454ee5a1 100644 --- a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java +++ b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java @@ -54,7 +54,7 @@ public class UserPreferences { public static final String PREF_USE_EPISODE_COVER = "prefEpisodeCover"; public static final String PREF_SHOW_TIME_LEFT = "showTimeLeft"; private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify"; - public static final String PREF_COMPACT_NOTIFICATION_BUTTONS = "prefCompactNotificationButtons"; + public static final String PREF_FULL_NOTIFICATION_BUTTONS = "prefFullNotificationButtons"; private static final String PREF_SHOW_DOWNLOAD_REPORT = "prefShowDownloadReport"; public static final String PREF_DEFAULT_PAGE = "prefDefaultPage"; public static final String PREF_FILTER_FEED = "prefSubscriptionsFilter"; @@ -67,6 +67,10 @@ public class UserPreferences { private static final String PREF_DOWNLOADS_SORTED_ORDER = "prefDownloadSortedOrder"; private static final String PREF_INBOX_SORTED_ORDER = "prefInboxSortedOrder"; + // Episode + public static final String PREF_SORT_ALL_EPISODES = "prefEpisodesSort"; + public static final String PREF_FILTER_ALL_EPISODES = "prefEpisodesFilter"; + // Playback public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect"; public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect"; @@ -123,9 +127,12 @@ public class UserPreferences { public static final int EPISODE_CLEANUP_DEFAULT = 0; // Constants - private static final int NOTIFICATION_BUTTON_REWIND = 0; - private static final int NOTIFICATION_BUTTON_FAST_FORWARD = 1; - private static final int NOTIFICATION_BUTTON_SKIP = 2; + public static final int NOTIFICATION_BUTTON_REWIND = 0; + public static final int NOTIFICATION_BUTTON_FAST_FORWARD = 1; + public static final int NOTIFICATION_BUTTON_SKIP = 2; + + public static final int NOTIFICATION_BUTTON_NEXT_CHAPTER = 3; + public static final int NOTIFICATION_BUTTON_PLAYBACK_SPEED = 4; public static final int EPISODE_CACHE_SIZE_UNLIMITED = -1; public static final int FEED_ORDER_COUNTER = 0; public static final int FEED_ORDER_ALPHABETICAL = 1; @@ -191,11 +198,11 @@ public class UserPreferences { return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenItems, ","))); } - public static List<Integer> getCompactNotificationButtons() { + public static List<Integer> getFullNotificationButtons() { String[] buttons = TextUtils.split( - prefs.getString(PREF_COMPACT_NOTIFICATION_BUTTONS, - NOTIFICATION_BUTTON_REWIND + "," + NOTIFICATION_BUTTON_FAST_FORWARD), - ","); + prefs.getString(PREF_FULL_NOTIFICATION_BUTTONS, + NOTIFICATION_BUTTON_SKIP + "," + NOTIFICATION_BUTTON_PLAYBACK_SPEED), ","); + List<Integer> notificationButtons = new ArrayList<>(); for (String button : buttons) { notificationButtons.add(Integer.parseInt(button)); @@ -204,27 +211,28 @@ public class UserPreferences { } /** - * Helper function to return whether the specified button should be shown on compact + * Helper function to return whether the specified button should be shown on full * notifications. * - * @param buttonId Either NOTIFICATION_BUTTON_REWIND, NOTIFICATION_BUTTON_FAST_FORWARD or - * NOTIFICATION_BUTTON_SKIP. + * @param buttonId Either NOTIFICATION_BUTTON_REWIND, NOTIFICATION_BUTTON_FAST_FORWARD, + * NOTIFICATION_BUTTON_SKIP, NOTIFICATION_BUTTON_PLAYBACK_SPEED + * or NOTIFICATION_BUTTON_NEXT_CHAPTER. * @return {@code true} if button should be shown, {@code false} otherwise */ - private static boolean showButtonOnCompactNotification(int buttonId) { - return getCompactNotificationButtons().contains(buttonId); + private static boolean showButtonOnFullNotification(int buttonId) { + return getFullNotificationButtons().contains(buttonId); } - public static boolean showRewindOnCompactNotification() { - return showButtonOnCompactNotification(NOTIFICATION_BUTTON_REWIND); + public static boolean showSkipOnFullNotification() { + return showButtonOnFullNotification(NOTIFICATION_BUTTON_SKIP); } - public static boolean showFastForwardOnCompactNotification() { - return showButtonOnCompactNotification(NOTIFICATION_BUTTON_FAST_FORWARD); + public static boolean showNextChapterOnFullNotification() { + return showButtonOnFullNotification(NOTIFICATION_BUTTON_NEXT_CHAPTER); } - public static boolean showSkipOnCompactNotification() { - return showButtonOnCompactNotification(NOTIFICATION_BUTTON_SKIP); + public static boolean showPlaybackSpeedOnFullNotification() { + return showButtonOnFullNotification(NOTIFICATION_BUTTON_PLAYBACK_SPEED); } public static int getFeedOrder() { @@ -649,10 +657,10 @@ public class UserPreferences { .apply(); } - public static void setCompactNotificationButtons(List<Integer> items) { + public static void setFullNotificationButtons(List<Integer> items) { String str = TextUtils.join(",", items); prefs.edit() - .putString(PREF_COMPACT_NOTIFICATION_BUTTONS, str) + .putString(PREF_FULL_NOTIFICATION_BUTTONS, str) .apply(); } @@ -867,4 +875,21 @@ public class UserPreferences { public static boolean shouldShowSubscriptionTitle() { return prefs.getBoolean(PREF_SUBSCRIPTION_TITLE, false); } + + public static void setAllEpisodesSortOrder(SortOrder s) { + prefs.edit().putString(PREF_SORT_ALL_EPISODES, "" + s.code).apply(); + } + + public static SortOrder getAllEpisodesSortOrder() { + return SortOrder.fromCodeString(prefs.getString(PREF_SORT_ALL_EPISODES, + "" + SortOrder.DATE_NEW_OLD.code)); + } + + public static String getPrefFilterAllEpisodes() { + return prefs.getString(PREF_FILTER_ALL_EPISODES, ""); + } + + public static void setPrefFilterAllEpisodes(String filter) { + prefs.edit().putString(PREF_FILTER_ALL_EPISODES, filter).apply(); + } } diff --git a/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/EchoActivity.java b/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/EchoActivity.java index 50b34b6e3..758eaeac7 100644 --- a/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/EchoActivity.java +++ b/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/EchoActivity.java @@ -10,8 +10,8 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.text.format.DateFormat; import android.util.Log; -import android.util.Pair; import android.view.KeyEvent; import android.view.View; import androidx.annotation.NonNull; @@ -35,6 +35,7 @@ import de.danoeh.antennapod.ui.echo.screens.FinalShareScreen; import de.danoeh.antennapod.ui.echo.screens.RotatingSquaresScreen; import de.danoeh.antennapod.ui.echo.screens.StripesScreen; import de.danoeh.antennapod.ui.echo.screens.WaveformScreen; +import de.danoeh.antennapod.ui.echo.screens.WavesScreen; import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -53,6 +54,7 @@ import java.util.Locale; import java.util.concurrent.TimeUnit; public class EchoActivity extends AppCompatActivity { + public static final int RELEASE_YEAR = 2023; private static final String TAG = "EchoActivity"; private static final int NUM_SCREENS = 7; private static final int SHARE_SIZE = 1000; @@ -67,6 +69,7 @@ public class EchoActivity extends AppCompatActivity { private long timeTouchDown; private long timeLastFrame; private Disposable disposable; + private Disposable disposableFavorite; private long totalTime = 0; private int totalActivePodcasts = 0; @@ -77,7 +80,8 @@ public class EchoActivity extends AppCompatActivity { private long queueSecondsLeft = 0; private long timeBetweenReleaseAndPlay = 0; private long oldestDate = 0; - private final ArrayList<Pair<String, Drawable>> favoritePods = new ArrayList<>(); + private final ArrayList<String> favoritePodNames = new ArrayList<>(); + private final ArrayList<Drawable> favoritePodImages = new ArrayList<>(); @SuppressLint("ClickableViewAccessibility") @Override @@ -134,7 +138,7 @@ public class EchoActivity extends AppCompatActivity { new ShareCompat.IntentBuilder(this) .setType("image/png") .addStream(fileUri) - .setText(getString(R.string.echo_share, 2023)) + .setText(getString(R.string.echo_share, RELEASE_YEAR)) .setChooserTitle(R.string.share_file_label) .startChooser(); } catch (Exception e) { @@ -176,6 +180,9 @@ public class EchoActivity extends AppCompatActivity { if (disposable != null) { disposable.dispose(); } + if (disposableFavorite != null) { + disposableFavorite.dispose(); + } } private void loadScreen(int screen, boolean force) { @@ -190,7 +197,7 @@ public class EchoActivity extends AppCompatActivity { switch (currentScreen) { case 0: viewBinding.aboveLabel.setText(R.string.echo_intro_your_year); - viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", 2023)); + viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", RELEASE_YEAR)); viewBinding.belowLabel.setText(R.string.echo_intro_in_podcasts); viewBinding.smallLabel.setText(R.string.echo_intro_locally); currentDrawable = new BubbleScreen(this); @@ -207,20 +214,27 @@ public class EchoActivity extends AppCompatActivity { viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", queueSecondsLeft / 3600)); viewBinding.belowLabel.setText(getResources().getQuantityString( R.plurals.echo_queue_hours_waiting, queueNumEpisodes, queueNumEpisodes)); - int daysUntil2024 = Math.max(356 - Calendar.getInstance().get(Calendar.DAY_OF_YEAR) + 1, 1); - long secondsPerDay = queueSecondsLeft / daysUntil2024; + Calendar dec31 = Calendar.getInstance(); + dec31.set(Calendar.DAY_OF_MONTH, 31); + dec31.set(Calendar.MONTH, Calendar.DECEMBER); + int daysUntilNextYear = Math.max(1, + dec31.get(Calendar.DAY_OF_YEAR) - Calendar.getInstance().get(Calendar.DAY_OF_YEAR) + 1); + long secondsPerDay = queueSecondsLeft / daysUntilNextYear; String timePerDay = Converter.getDurationStringLocalized( getLocalizedResources(this, getEchoLanguage()), secondsPerDay * 1000, true); double hoursPerDay = (double) (secondsPerDay / 3600); + int nextYear = RELEASE_YEAR + 1; if (hoursPerDay < 1.5) { viewBinding.aboveLabel.setText(R.string.echo_queue_title_clean); - viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_clean, timePerDay, 2024)); + viewBinding.smallLabel.setText( + getString(R.string.echo_queue_hours_clean, timePerDay, nextYear)); } else if (hoursPerDay <= 24) { viewBinding.aboveLabel.setText(R.string.echo_queue_title_many); - viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_normal, timePerDay, 2024)); + viewBinding.smallLabel.setText( + getString(R.string.echo_queue_hours_normal, timePerDay, nextYear)); } else { viewBinding.aboveLabel.setText(R.string.echo_queue_title_many); - viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_much, timePerDay, 2024)); + viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_much, timePerDay, nextYear)); } currentDrawable = new StripesScreen(this); break; @@ -257,13 +271,14 @@ public class EchoActivity extends AppCompatActivity { viewBinding.smallLabel.setText(getString(R.string.echo_hoarder_comment_clean, percentagePlayed, totalActivePodcasts)); } - currentDrawable = new StripesScreen(this); + currentDrawable = new WavesScreen(this); break; case 5: viewBinding.aboveLabel.setText(""); viewBinding.largeLabel.setText(R.string.echo_thanks_large); if (oldestDate < jan1()) { - SimpleDateFormat dateFormat = new SimpleDateFormat("MMMM yyyy", getEchoLanguage()); + String skeleton = DateFormat.getBestDateTimePattern(getEchoLanguage(), "MMMM yyyy"); + SimpleDateFormat dateFormat = new SimpleDateFormat(skeleton, getEchoLanguage()); String dateFrom = dateFormat.format(new Date(oldestDate)); viewBinding.belowLabel.setText(getString(R.string.echo_thanks_we_are_glad_old, dateFrom)); } else { @@ -277,7 +292,7 @@ public class EchoActivity extends AppCompatActivity { viewBinding.largeLabel.setText(""); viewBinding.belowLabel.setText(""); viewBinding.smallLabel.setText(""); - currentDrawable = new FinalShareScreen(this, favoritePods); + currentDrawable = new FinalShareScreen(this, favoritePodNames, favoritePodImages); break; default: // Keep } @@ -312,7 +327,7 @@ public class EchoActivity extends AppCompatActivity { date.set(Calendar.MILLISECOND, 0); date.set(Calendar.DAY_OF_MONTH, 1); date.set(Calendar.MONTH, 0); - date.set(Calendar.YEAR, 2023); + date.set(Calendar.YEAR, RELEASE_YEAR); return date.getTimeInMillis(); } @@ -329,25 +344,11 @@ public class EchoActivity extends AppCompatActivity { Collections.sort(statisticsData.feedTime, (item1, item2) -> Long.compare(item2.timePlayed, item1.timePlayed)); - favoritePods.clear(); + favoritePodNames.clear(); for (int i = 0; i < 5 && i < statisticsData.feedTime.size(); i++) { - BitmapDrawable cover = new BitmapDrawable(getResources(), (Bitmap) null); - try { - final int size = SHARE_SIZE / 3; - final int radius = (i == 0) ? (size / 16) : (size / 8); - cover = new BitmapDrawable(getResources(), Glide.with(this) - .asBitmap() - .load(statisticsData.feedTime.get(i).feed.getImageUrl()) - .apply(new RequestOptions() - .fitCenter() - .transform(new RoundedCorners(radius))) - .submit(size, size) - .get(1, TimeUnit.SECONDS)); - } catch (Exception e) { - e.printStackTrace(); - } - favoritePods.add(new Pair<>(statisticsData.feedTime.get(i).feed.getTitle(), cover)); + favoritePodNames.add(statisticsData.feedTime.get(i).feed.getTitle()); } + loadFavoritePodImages(statisticsData); totalActivePodcasts = 0; playedActivePodcasts = 0; @@ -396,4 +397,37 @@ public class EchoActivity extends AppCompatActivity { .subscribe(result -> loadScreen(currentScreen, true), error -> Log.e(TAG, Log.getStackTraceString(error))); } + + void loadFavoritePodImages(DBReader.StatisticsResult statisticsData) { + if (disposableFavorite != null) { + disposableFavorite.dispose(); + } + disposableFavorite = Observable.fromCallable( + () -> { + favoritePodImages.clear(); + for (int i = 0; i < 5 && i < statisticsData.feedTime.size(); i++) { + BitmapDrawable cover = new BitmapDrawable(getResources(), (Bitmap) null); + try { + final int size = SHARE_SIZE / 3; + final int radius = (i == 0) ? (size / 16) : (size / 8); + cover = new BitmapDrawable(getResources(), Glide.with(this) + .asBitmap() + .load(statisticsData.feedTime.get(i).feed.getImageUrl()) + .apply(new RequestOptions() + .fitCenter() + .transform(new RoundedCorners(radius))) + .submit(size, size) + .get(5, TimeUnit.SECONDS)); + } catch (Exception e) { + Log.d(TAG, "Loading cover: " + e.getMessage()); + } + favoritePodImages.add(cover); + } + return statisticsData; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { }, + error -> Log.e(TAG, Log.getStackTraceString(error))); + } } diff --git a/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/screens/FinalShareScreen.java b/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/screens/FinalShareScreen.java index 87b9cbd53..4af8941d0 100644 --- a/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/screens/FinalShareScreen.java +++ b/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/screens/FinalShareScreen.java @@ -7,9 +7,9 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; -import android.util.Pair; import androidx.appcompat.content.res.AppCompatResources; import androidx.core.content.res.ResourcesCompat; +import de.danoeh.antennapod.ui.echo.EchoActivity; import de.danoeh.antennapod.ui.echo.R; import java.util.ArrayList; @@ -19,16 +19,21 @@ public class FinalShareScreen extends BubbleScreen { private final Paint paintTextMain; private final Paint paintCoverBorder; private final String heading; + private final String year; private final Drawable logo; - private final ArrayList<Pair<String, Drawable>> favoritePods; + private final ArrayList<String> favoritePodNames; + private final ArrayList<Drawable> favoritePodImages; private final Typeface typefaceNormal; private final Typeface typefaceBold; - public FinalShareScreen(Context context, ArrayList<Pair<String, Drawable>> favoritePods) { + public FinalShareScreen(Context context, + ArrayList<String> favoritePodNames, ArrayList<Drawable> favoritePodImages) { super(context); this.heading = context.getString(R.string.echo_share_heading); this.logo = AppCompatResources.getDrawable(context, R.drawable.echo); - this.favoritePods = favoritePods; + this.favoritePodNames = favoritePodNames; + this.favoritePodImages = favoritePodImages; + this.year = String.valueOf(EchoActivity.RELEASE_YEAR); typefaceNormal = ResourcesCompat.getFont(context, R.font.sarabun_regular); typefaceBold = ResourcesCompat.getFont(context, R.font.sarabun_semi_bold); paintTextMain = new Paint(); @@ -49,12 +54,11 @@ public class FinalShareScreen extends BubbleScreen { paintTextMain.setTextSize(headingSize); canvas.drawText(heading, innerBoxX + 0.5f * innerBoxSize, innerBoxY + headingSize, paintTextMain); paintTextMain.setTextSize(0.12f * innerBoxSize); - canvas.drawText("2023", innerBoxX + 0.8f * innerBoxSize, innerBoxY + 0.25f * innerBoxSize, paintTextMain); + canvas.drawText(year, innerBoxX + 0.8f * innerBoxSize, innerBoxY + 0.25f * innerBoxSize, paintTextMain); - paintTextMain.setTextAlign(Paint.Align.LEFT); float fontSizePods = innerBoxSize / 18; // First one only float textY = innerBoxY + 0.62f * innerBoxSize; - for (int i = 0; i < favoritePods.size(); i++) { + for (int i = 0; i < favoritePodNames.size(); i++) { float coverSize = (i == 0) ? (0.4f * innerBoxSize) : (0.2f * innerBoxSize); float coverX = COVER_POSITIONS[i][0]; float coverY = COVER_POSITIONS[i][1]; @@ -68,12 +72,20 @@ public class FinalShareScreen extends BubbleScreen { logo1Pos.inset((int) (0.003f * innerBoxSize), (int) (0.003f * innerBoxSize)); Rect pos = new Rect(); logo1Pos.round(pos); - favoritePods.get(i).second.setBounds(pos); - favoritePods.get(i).second.draw(canvas); + if (favoritePodImages.size() > i) { + favoritePodImages.get(i).setBounds(pos); + favoritePodImages.get(i).draw(canvas); + } else { + canvas.drawText(" ...", pos.left, pos.centerY(), paintTextMain); + } + paintTextMain.setTextAlign(Paint.Align.CENTER); paintTextMain.setTextSize(fontSizePods); - canvas.drawText((i + 1) + ".", innerBoxX, textY, paintTextMain); - canvas.drawText(favoritePods.get(i).first, innerBoxX + 0.055f * innerBoxSize, textY, paintTextMain); + final float numberWidth = 0.06f * innerBoxSize; + canvas.drawText((i + 1) + ".", innerBoxX + numberWidth / 2, textY, paintTextMain); + paintTextMain.setTextAlign(Paint.Align.LEFT); + String ellipsizedTitle = ellipsize(favoritePodNames.get(i), paintTextMain, innerBoxSize - numberWidth); + canvas.drawText(ellipsizedTitle, innerBoxX + numberWidth, textY, paintTextMain); fontSizePods = innerBoxSize / 24; // Starting with second text is smaller textY += 1.3f * fontSizePods; paintTextMain.setTypeface(typefaceNormal); @@ -86,4 +98,14 @@ public class FinalShareScreen extends BubbleScreen { (int) (innerBoxY + innerBoxSize)); logo.draw(canvas); } + + String ellipsize(String string, Paint paint, float maxWidth) { + if (paint.measureText(string) <= maxWidth) { + return string; + } + while (paint.measureText(string + "…") > maxWidth || string.endsWith(" ")) { + string = string.substring(0, string.length() - 1); + } + return string + "…"; + } } diff --git a/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/screens/WavesScreen.java b/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/screens/WavesScreen.java new file mode 100644 index 000000000..eb521f0a2 --- /dev/null +++ b/ui/echo/src/main/java/de/danoeh/antennapod/ui/echo/screens/WavesScreen.java @@ -0,0 +1,38 @@ +package de.danoeh.antennapod.ui.echo.screens; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import androidx.annotation.NonNull; + +public class WavesScreen extends BaseScreen { + protected static final int NUM_PARTICLES = 10; + + public WavesScreen(Context context) { + super(context); + paintParticles.setStyle(Paint.Style.STROKE); + for (int i = 0; i < NUM_PARTICLES; i++) { + particles.add(new Particle(0, 0, 1.0f * i / NUM_PARTICLES, 0)); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + paintParticles.setStrokeWidth(0.05f * getBounds().height()); + super.draw(canvas); + } + + @Override + protected void drawParticle(@NonNull Canvas canvas, Particle p, float width, float height, + float innerBoxX, float innerBoxY, float innerBoxSize) { + canvas.drawCircle(width / 2, 1.1f * height, (float) (p.positionZ * 1.2f * height), paintParticles); + } + + @Override + protected void particleTick(Particle p, long timeSinceLastFrame) { + p.positionZ += 0.00005 * timeSinceLastFrame; + if (p.positionZ > 1f) { + p.positionZ -= 1f; + } + } +} diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml index c1872969a..e40e03035 100644 --- a/ui/i18n/src/main/res/values/strings.xml +++ b/ui/i18n/src/main/res/values/strings.xml @@ -26,7 +26,7 @@ <string name="episode_cache_full_message">The episode cache limit has been reached. You can increase the cache size in the Settings.</string> <string name="years_statistics_label">Years</string> <string name="notification_pref_fragment">Notifications</string> - <string name="recently_played_episodes">Recently played episodes</string> + <string name="current_playing_episode">Current</string> <string name="antennapod_echo" translatable="false">AntennaPod Echo</string> <string name="antennapod_echo_year" translatable="false">AntennaPod Echo %d</string> @@ -123,7 +123,6 @@ <string name="description_label">Description</string> <string name="shownotes_label">Shownotes</string> <string name="shownotes_contentdescription">swipe up to read shownotes</string> - <string name="episodes_suffix">\u0020episodes</string> <string name="close_label">Close</string> <string name="retry_label">Retry</string> <string name="auto_download_label">Include in auto downloads</string> @@ -185,7 +184,6 @@ <string name="remove_feed_label">Remove podcast</string> <string name="share_label">Share</string> <string name="share_file_label">Share file</string> - <string name="share_rss_address_label">RSS address:</string> <string name="feed_delete_confirmation_msg">Please confirm that you want to delete the podcast \"%1$s\", ALL its episodes (including downloaded episodes), and its statistics.</string> <string name="feed_delete_confirmation_msg_batch">Please confirm that you want to remove the selected podcasts, ALL their episodes (including downloaded episodes), and its statistics.</string> <string name="feed_delete_confirmation_local_msg">Please confirm that you want to remove the podcast \"%1$s\" and its statistics. The files in the local source folder will not be deleted.</string> @@ -490,10 +488,9 @@ <string name="pref_expandNotify_sum">This usually expands the notification to show playback buttons.</string> <string name="pref_persistNotify_title">Persistent playback controls</string> <string name="pref_persistNotify_sum">Keep notification and lockscreen controls when playback is paused</string> - <string name="pref_compact_notification_buttons_title">Set compact notification buttons</string> - <string name="pref_compact_notification_buttons_sum">Change the playback buttons when the notification is collapsed. The play/pause button is always included.</string> - <string name="pref_compact_notification_buttons_dialog_title">Select a maximum of %1$d items</string> - <string name="pref_compact_notification_buttons_dialog_error">You can only select a maximum of %1$d items.</string> + <string name="pref_compact_notification_buttons_dialog_error_exact">You must select exactly %1$d items.</string> + <string name="pref_full_notification_buttons_title">Set notification buttons</string> + <string name="pref_full_notification_buttons_sum">Change the playback buttons on the playback notification.</string> <string name="pref_enqueue_location_title">Enqueue location</string> <string name="pref_enqueue_location_sum">Add episodes to: %1$s</string> <string name="enqueue_location_back">Back</string> @@ -579,7 +576,7 @@ <string name="database_import_summary">Import AntennaPod database from another device</string> <string name="opml_import_label">OPML import</string> <string name="opml_add_podcast_label">Import podcast list (OPML)</string> - <string name="opml_reader_error">An error has occurred while reading the OPML document:</string> + <string name="opml_reader_error">An error has occurred while reading the file. Make sure that you have actually selected an OPML file and that the file is valid.</string> <string name="opml_import_error_no_file">No file selected!</string> <string name="select_all_label">Select all</string> <string name="deselect_all_label">Deselect all</string> @@ -773,25 +770,7 @@ <string name="not_paused">Not paused</string> <string name="hide_played_episodes_label">Played</string> <string name="not_played">Not played</string> - - <!-- Sort --> - <string name="sort_title_a_z">Title (A \u2192 Z)</string> - <string name="sort_title_z_a">Title (Z \u2192 A)</string> - <string name="sort_date_new_old">Date (New \u2192 Old)</string> - <string name="sort_date_old_new">Date (Old \u2192 New)</string> - <string name="sort_duration_short_long">Duration (Short \u2192 Long)</string> - <string name="sort_duration_long_short">Duration (Long \u2192 Short)</string> - <string name="sort_filename_a_z">File Name (A \u2192 Z)</string> - <string name="sort_filename_z_a">File Name (Z \u2192 A)</string> - - <string name="sort_a_z">A \u2192 Z</string> - <string name="sort_z_a">Z \u2192 A</string> - <string name="sort_new_old">New \u2192 Old</string> - <string name="sort_old_new">Old \u2192 New</string> - <string name="sort_short_long">Short \u2192 Long</string> - <string name="sort_long_short">Long \u2192 Short</string> - <string name="sort_small_large">Small \u2192 Large</string> - <string name="sort_large_small">Large \u2192 Small</string> + <string name="filename">File name</string> <!-- Share episode dialog --> <string name="share_playback_position_dialog_label">Include playback position</string> @@ -804,7 +783,6 @@ <!-- Audio controls --> <string name="audio_controls">Audio controls</string> <string name="playback_speed">Playback speed</string> - <string name="audio_effects">Audio effects</string> <string name="player_switch_to_audio_only">Switch to audio only</string> <!-- proxy settings --> diff --git a/ui/png-icons/src/main/res/drawable/ic_notification_next_chapter.xml b/ui/png-icons/src/main/res/drawable/ic_notification_next_chapter.xml new file mode 100644 index 000000000..cb55e93a9 --- /dev/null +++ b/ui/png-icons/src/main/res/drawable/ic_notification_next_chapter.xml @@ -0,0 +1,7 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="#fff" android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M8,8V16L13,12M14,8V16H16V8" />
+</vector>
diff --git a/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/downloads/DownloadStatisticsListAdapter.java b/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/downloads/DownloadStatisticsListAdapter.java index 4839aa891..083a87514 100644 --- a/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/downloads/DownloadStatisticsListAdapter.java +++ b/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/downloads/DownloadStatisticsListAdapter.java @@ -10,7 +10,6 @@ import de.danoeh.antennapod.ui.statistics.StatisticsListAdapter; import de.danoeh.antennapod.ui.statistics.feed.FeedStatisticsDialogFragment; import java.util.List; -import java.util.Locale; /** * Adapter for the download statistics list. @@ -45,10 +44,10 @@ public class DownloadStatisticsListAdapter extends StatisticsListAdapter { @Override protected void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem item) { - holder.value.setText(Formatter.formatShortFileSize(context, item.totalDownloadSize) - + " • " - + String.format(Locale.getDefault(), "%d%s", - item.episodesDownloadCount, context.getString(R.string.episodes_suffix))); + int numEpisodes = (int) item.episodesDownloadCount; + String text = Formatter.formatShortFileSize(context, item.totalDownloadSize); + text += " • " + context.getResources().getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes); + holder.value.setText(text); holder.itemView.setOnClickListener(v -> { FeedStatisticsDialogFragment yourDialogFragment = FeedStatisticsDialogFragment.newInstance( diff --git a/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/PlaybackStatisticsListAdapter.java b/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/PlaybackStatisticsListAdapter.java index 3936118ca..9b1f234c8 100644 --- a/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/PlaybackStatisticsListAdapter.java +++ b/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/PlaybackStatisticsListAdapter.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.ui.statistics.subscriptions; +import android.text.format.DateFormat; import androidx.fragment.app.Fragment; import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; @@ -39,7 +40,8 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { if (includeMarkedAsPlayed) { return context.getString(R.string.statistics_counting_total); } - SimpleDateFormat dateFormat = new SimpleDateFormat("MMM yyyy", Locale.getDefault()); + String skeleton = DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMM yyyy"); + SimpleDateFormat dateFormat = new SimpleDateFormat(skeleton, Locale.getDefault()); String dateFrom = dateFormat.format(new Date(timeFilterFrom)); // FilterTo is first day of next month => Subtract one day String dateTo = dateFormat.format(new Date(timeFilterTo - 24L * 3600000L)); diff --git a/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/StatisticsFilterDialog.java b/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/StatisticsFilterDialog.java index 077883321..e46e48240 100644 --- a/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/StatisticsFilterDialog.java +++ b/ui/statistics/src/main/java/de/danoeh/antennapod/ui/statistics/subscriptions/StatisticsFilterDialog.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.ui.statistics.subscriptions; import android.content.Context; import android.content.SharedPreferences; +import android.text.format.DateFormat; import android.view.LayoutInflater; import android.widget.ArrayAdapter; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -114,7 +115,8 @@ public class StatisticsFilterDialog { date.set(Calendar.DAY_OF_MONTH, 1); ArrayList<String> names = new ArrayList<>(); ArrayList<Long> timestamps = new ArrayList<>(); - SimpleDateFormat dateFormat = new SimpleDateFormat("MMM yyyy", Locale.getDefault()); + String skeleton = DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMM yyyy"); + SimpleDateFormat dateFormat = new SimpleDateFormat(skeleton, Locale.getDefault()); while (date.getTimeInMillis() < System.currentTimeMillis()) { names.add(dateFormat.format(new Date(date.getTimeInMillis()))); if (!inclusive) { |