diff options
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod/fragment')
29 files changed, 557 insertions, 139 deletions
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 09dfac37a..ed88214fe 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -12,7 +12,6 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -30,6 +29,8 @@ import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.util.SortOrder; +import de.danoeh.antennapod.databinding.AddfeedBinding; +import de.danoeh.antennapod.databinding.EditTextDialogBinding; import de.danoeh.antennapod.discovery.CombinedSearcher; import de.danoeh.antennapod.discovery.FyydPodcastSearcher; import de.danoeh.antennapod.discovery.ItunesPodcastSearcher; @@ -50,7 +51,7 @@ public class AddFeedFragment extends Fragment { private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; - private EditText combinedFeedSearchBox; + private AddfeedBinding viewBinding; private MainActivity activity; @Override @@ -59,29 +60,30 @@ public class AddFeedFragment extends Fragment { @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); - View root = inflater.inflate(R.layout.addfeed, container, false); + viewBinding = AddfeedBinding.inflate(getLayoutInflater()); activity = (MainActivity) getActivity(); - Toolbar toolbar = root.findViewById(R.id.toolbar); + + Toolbar toolbar = viewBinding.toolbar; ((MainActivity) getActivity()).setupToolbarToggle(toolbar); - root.findViewById(R.id.btn_search_itunes).setOnClickListener(v + viewBinding.searchItunesButton.setOnClickListener(v -> activity.loadChildFragment(OnlineSearchFragment.newInstance(ItunesPodcastSearcher.class))); - root.findViewById(R.id.btn_search_fyyd).setOnClickListener(v + viewBinding.searchFyydButton.setOnClickListener(v -> activity.loadChildFragment(OnlineSearchFragment.newInstance(FyydPodcastSearcher.class))); - root.findViewById(R.id.btn_search_gpodder).setOnClickListener(v + viewBinding.searchGPodderButton.setOnClickListener(v -> activity.loadChildFragment(new GpodnetMainFragment())); - root.findViewById(R.id.btn_search_podcastindex).setOnClickListener(v + viewBinding.searchPodcastIndexButton.setOnClickListener(v -> activity.loadChildFragment(OnlineSearchFragment.newInstance(PodcastIndexPodcastSearcher.class))); - combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox); - combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> { + viewBinding.combinedFeedSearchEditText.setOnEditorActionListener((v, actionId, event) -> { performSearch(); return true; }); - root.findViewById(R.id.btn_add_via_url).setOnClickListener(v + + viewBinding.addViaUrlButton.setOnClickListener(v -> showAddViaUrlDialog()); - root.findViewById(R.id.btn_opml_import).setOnClickListener(v -> { + viewBinding.opmlImportButton.setOnClickListener(v -> { try { Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); @@ -93,7 +95,8 @@ public class AddFeedFragment extends Fragment { .showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG); } }); - root.findViewById(R.id.btn_add_local_folder).setOnClickListener(v -> { + + viewBinding.addLocalFolderButton.setOnClickListener(v -> { if (Build.VERSION.SDK_INT < 21) { return; } @@ -108,25 +111,29 @@ public class AddFeedFragment extends Fragment { } }); if (Build.VERSION.SDK_INT < 21) { - root.findViewById(R.id.btn_add_local_folder).setVisibility(View.GONE); + viewBinding.addLocalFolderButton.setVisibility(View.GONE); } - root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch()); - return root; + + viewBinding.searchButton.setOnClickListener(view -> performSearch()); + + return viewBinding.getRoot(); } private void showAddViaUrlDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(R.string.add_podcast_by_url); View content = View.inflate(getContext(), R.layout.edit_text_dialog, null); - EditText editText = content.findViewById(R.id.text); - editText.setHint(R.string.add_podcast_by_url_hint); + EditTextDialogBinding alertViewBinding = EditTextDialogBinding.bind(content); + alertViewBinding.urlEditText.setHint(R.string.add_podcast_by_url_hint); + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); String clipboardContent = clipboard.getText() != null ? clipboard.getText().toString() : ""; if (clipboardContent.trim().startsWith("http")) { - editText.setText(clipboardContent.trim()); + alertViewBinding.urlEditText.setText(clipboardContent.trim()); } - builder.setView(content); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> addUrl(editText.getText().toString())); + builder.setView(alertViewBinding.getRoot()); + builder.setPositiveButton(R.string.confirm_label, + (dialog, which) -> addUrl(alertViewBinding.urlEditText.getText().toString())); builder.setNegativeButton(R.string.cancel_label, null); builder.show(); } @@ -138,8 +145,7 @@ public class AddFeedFragment extends Fragment { } private void performSearch() { - String query = combinedFeedSearchBox.getText().toString(); - + String query = viewBinding.combinedFeedSearchEditText.getText().toString(); if (query.matches("http[s]?://.*")) { addUrl(query); return; 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 ae3ba3a54..612959c04 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import androidx.annotation.NonNull; @@ -105,13 +104,12 @@ public class AllEpisodesFragment extends EpisodesListFragment { @NonNull @Override protected List<FeedItem> loadData() { - return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE)); + return DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE, feedItemFilter); } @NonNull @Override protected List<FeedItem> loadMoreData() { - return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, - EPISODES_PER_PAGE)); + return DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter); } } 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 5a65f956c..8ff8866e0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java @@ -1,8 +1,6 @@ package de.danoeh.antennapod.fragment; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -17,16 +15,21 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; +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.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.CastEnabledActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.event.FavoritesEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; @@ -43,8 +46,7 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog; import de.danoeh.antennapod.dialog.SleepTimerDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; -import de.danoeh.antennapod.view.PagerIndicatorView; -import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView; +import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -67,14 +69,12 @@ public class AudioPlayerFragment extends Fragment implements private static final int POS_DESCR = 1; private static final int POS_CHAPTERS = 2; private static final int NUM_CONTENT_FRAGMENTS = 3; - private static final String PREFS = "AudioPlayerFragmentPreferences"; - private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft"; + public static final String PREFS = "AudioPlayerFragmentPreferences"; private static final float EPSILON = 0.001f; PlaybackSpeedIndicatorView butPlaybackSpeed; TextView txtvPlaybackSpeed; private ViewPager2 pager; - private PagerIndicatorView pageIndicator; private TextView txtvPosition; private TextView txtvLength; private SeekBar sbPosition; @@ -86,10 +86,14 @@ public class AudioPlayerFragment extends Fragment implements private ImageButton butSkip; private Toolbar toolbar; private ProgressBar progressIndicator; + private CardView cardViewSeek; + private TextView txtvSeek; private PlaybackController controller; private Disposable disposable; private boolean showTimeLeft; + private boolean hasChapters = false; + private TabLayoutMediator tabLayoutMediator; @Override public View onCreateView(@NonNull LayoutInflater inflater, @@ -120,6 +124,8 @@ public class AudioPlayerFragment extends Fragment implements txtvFF = root.findViewById(R.id.txtvFF); butSkip = root.findViewById(R.id.butSkip); progressIndicator = root.findViewById(R.id.progLoading); + cardViewSeek = root.findViewById(R.id.cardViewSeek); + txtvSeek = root.findViewById(R.id.txtvSeek); setupLengthTextView(); setupControlButtons(); @@ -141,13 +147,37 @@ public class AudioPlayerFragment extends Fragment implements }); } }); - pageIndicator = root.findViewById(R.id.page_indicator); - pageIndicator.setViewPager(pager); - pageIndicator.setOnClickListener(v -> - pager.setCurrentItem((pager.getCurrentItem() + 1) % NUM_CONTENT_FRAGMENTS)); + + TabLayout tabLayout = root.findViewById(R.id.sliding_tabs); + tabLayoutMediator = new TabLayoutMediator(tabLayout, pager, (tab, position) -> { + tab.view.setAlpha(1.0f); + switch (position) { + case POS_COVER: + tab.setText(R.string.cover_label); + break; + case POS_DESCR: + tab.setText(R.string.description_label); + break; + case POS_CHAPTERS: + tab.setText(R.string.chapters_label); + if (!hasChapters) { + tab.view.setAlpha(0.5f); + } + break; + default: + break; + } + }); + tabLayoutMediator.attach(); return root; } + public void setHasChapters(boolean hasChapters) { + this.hasChapters = hasChapters; + tabLayoutMediator.detach(); + tabLayoutMediator.attach(); + } + public View getExternalPlayerHolder() { return getView().findViewById(R.id.playerFragment); } @@ -185,16 +215,25 @@ public class AudioPlayerFragment extends Fragment implements IntentUtils.sendLocalBroadcast(getActivity(), PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onUnreadItemsUpdate(UnreadItemsUpdateEvent event) { + if (controller == null) { + return; + } + updatePosition(new PlaybackPositionEvent(controller.getPosition(), + controller.getDuration())); + } + private void setupLengthTextView() { - SharedPreferences prefs = getContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE); - showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); + showTimeLeft = UserPreferences.shouldShowRemainingTime(); txtvLength.setOnClickListener(v -> { if (controller == null) { return; } showTimeLeft = !showTimeLeft; - prefs.edit().putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft).apply(); - updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration())); + UserPreferences.setShowRemainTimeSetting(showTimeLeft); + updatePosition(new PlaybackPositionEvent(controller.getPosition(), + controller.getDuration())); }); } @@ -362,10 +401,6 @@ public class AudioPlayerFragment extends Fragment implements setupOptionsMenu(media); } - public void setHasChapters(boolean hasChapters) { - pageIndicator.setDisabledPage(hasChapters ? -1 : 2); - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -411,6 +446,7 @@ public class AudioPlayerFragment extends Fragment implements return; } txtvPosition.setText(Converter.getDurationStringLong(currentPosition)); + showTimeLeft = UserPreferences.shouldShowRemainingTime(); if (showTimeLeft) { txtvLength.setText("-" + Converter.getDurationStringLong(remainingTime)); } else { @@ -432,22 +468,22 @@ public class AudioPlayerFragment extends Fragment implements } if (fromUser) { float prog = progress / ((float) seekBar.getMax()); - int duration = controller.getDuration(); TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier()); - int position = converter.convert((int) (prog * duration)); - txtvPosition.setText(Converter.getDurationStringLong(position)); - - if (showTimeLeft && prog != 0) { - int timeLeft = converter.convert(duration - (int) (prog * duration)); - String length = "-" + Converter.getDurationStringLong(timeLeft); - txtvLength.setText(length); - } + int position = converter.convert((int) (prog * controller.getDuration())); + txtvSeek.setText(Converter.getDurationStringLong(position)); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { // interrupt position Observer, restart later + cardViewSeek.setScaleX(.8f); + cardViewSeek.setScaleY(.8f); + cardViewSeek.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(1f).scaleX(1f).scaleY(1f) + .setDuration(200) + .start(); } @Override @@ -456,6 +492,13 @@ public class AudioPlayerFragment extends Fragment implements float prog = seekBar.getProgress() / ((float) seekBar.getMax()); controller.seekTo((int) (prog * controller.getDuration())); } + cardViewSeek.setScaleX(1f); + cardViewSeek.setScaleY(1f); + cardViewSeek.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(0f).scaleX(.8f).scaleY(.8f) + .setDuration(200) + .start(); } public void setupOptionsMenu(Playable media) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java index 6f95d71da..624345907 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -8,9 +8,9 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChaptersListAdapter; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; @@ -45,7 +45,8 @@ public class ChaptersFragment extends Fragment { RecyclerView recyclerView = root.findViewById(R.id.recyclerView); layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); - recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); + recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), + layoutManager.getOrientation())); adapter = new ChaptersListAdapter(getActivity(), pos -> { if (controller.getStatus() != PlayerStatus.PLAYING) { @@ -106,6 +107,7 @@ public class ChaptersFragment extends Fragment { @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(PlaybackPositionEvent event) { updateChapterSelection(getCurrentChapter(media)); + adapter.notifyTimeChanged(event.getPosition()); } private int getCurrentChapter(Playable media) { 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 59b2cd234..3519a34b4 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -4,7 +4,6 @@ import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index 648fc614a..60fcac03d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -13,11 +13,9 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.FitCenter; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; @@ -25,9 +23,11 @@ import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.util.ChapterUtils; +import de.danoeh.antennapod.core.util.DateUtils; import de.danoeh.antennapod.core.util.EmbeddedChapterImage; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; @@ -35,6 +35,7 @@ import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -93,7 +94,12 @@ public class CoverFragment extends Fragment { } private void displayMediaInfo(@NonNull Playable media) { - txtvPodcastTitle.setText(media.getFeedTitle()); + String pubDateStr = DateUtils.formatAbbrev(getActivity(), ((FeedMedia) media).getPubDate()); + txtvPodcastTitle.setText(StringUtils.stripToEmpty(media.getFeedTitle()) + + "\u00A0" + + "・" + + "\u00A0" + + StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0")); txtvEpisodeTitle.setText(media.getEpisodeTitle()); displayedChapterIndex = -2; // Force refresh displayCoverImage(media.getPosition()); @@ -151,23 +157,25 @@ public class CoverFragment extends Fragment { if (chapter != displayedChapterIndex) { displayedChapterIndex = chapter; + RequestOptions options = new RequestOptions() + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .dontAnimate() + .transforms(new FitCenter(), + new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density))); + RequestBuilder<Drawable> cover = Glide.with(this) .load(ImageResourceUtils.getImageLocation(media)) - .apply(new RequestOptions() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .dontAnimate() - .transforms(new FitCenter(), - new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)))); + .error(Glide.with(this) + .load(ImageResourceUtils.getFallbackImageLocation(media)) + .apply(options)) + .apply(options); + if (chapter == -1 || TextUtils.isEmpty(media.getChapters().get(chapter).getImageUrl())) { cover.into(imgvCover); } else { Glide.with(this) .load(EmbeddedChapterImage.getModelFor(media, chapter)) - .apply(new RequestOptions() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .dontAnimate() - .transforms(new FitCenter(), - new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)))) + .apply(options) .thumbnail(cover) .error(cover) .into(imgvCover); @@ -208,7 +216,7 @@ public class CoverFragment extends Fragment { imgvCover.setLayoutParams(params); } } else { - double percentageHeight = ratio * 0.8; + double percentageHeight = ratio * 0.6; mainContainer.setOrientation(LinearLayout.HORIZONTAL); if (newConfig.screenHeightDp > 0) { params.height = (int) (convertDpToPixel(newConfig.screenHeightDp) * percentageHeight); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index c2c45c581..2e11ea4ec 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -4,7 +4,6 @@ import android.app.Dialog; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.content.res.TypedArray; import android.os.Bundle; import androidx.annotation.NonNull; @@ -13,7 +12,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.ListFragment; import android.util.Log; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ListView; 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 8dae310ba..39f935bbe 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -383,6 +383,14 @@ public abstract class EpisodesListFragment extends Fragment { @NonNull protected abstract List<FeedItem> loadData(); + /** + * Load a new page of data as defined by {@link #page} and {@link #EPISODES_PER_PAGE}. + * If the number of items returned is less than {@link #EPISODES_PER_PAGE}, + * it will be assumed that the underlying data is exhausted + * and this method will not be called again. + * + * @return The items from the next page of data + */ @NonNull protected abstract List<FeedItem> loadMoreData(); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 5d701472f..f2fe23135 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -198,14 +198,19 @@ public class ExternalPlayerFragment extends Fragment { feedName.setText(media.getFeedTitle()); onPositionObserverUpdate(); + RequestOptions options = new RequestOptions() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate(); + Glide.with(getActivity()) .load(ImageResourceUtils.getImageLocation(media)) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate()) + .error(Glide.with(getActivity()) + .load(ImageResourceUtils.getFallbackImageLocation(media)) + .apply(options)) + .apply(options) .into(imgvCover); if (controller != null && controller.isPlayingVideoLocally()) { 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 abb597e60..dd8a02893 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -45,7 +45,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.ui.common.ThemeUtils; import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; import de.danoeh.antennapod.fragment.preferences.StatisticsFragment; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; @@ -130,6 +130,8 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic protected void doTint(Context themedContext) { toolbar.getMenu().findItem(R.id.visit_website_item) .setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.location_web_site)); + toolbar.getMenu().findItem(R.id.share_parent) + .setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.ic_share)); } }; iconTintManager.updateTint(); @@ -284,9 +286,13 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic } private void refreshToolbarState() { + boolean shareLinkVisible = feed != null && feed.getLink() != null; + boolean downloadUrlVisible = feed != null && !feed.isLocalFeed(); + toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(feed != null && feed.isLocalFeed()); - toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(feed != null && !feed.isLocalFeed()); - toolbar.getMenu().findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null); + toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(downloadUrlVisible); + toolbar.getMenu().findItem(R.id.share_link_item).setVisible(shareLinkVisible); + toolbar.getMenu().findItem(R.id.share_parent).setVisible(downloadUrlVisible || shareLinkVisible); toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null && IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink())))); } 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 583d49afb..0bfb63718 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -57,7 +57,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.FeedItemPermutors; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.Optional; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.ui.common.ThemeUtils; import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.dialog.FilterDialog; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index 568b56304..fb9ac4a59 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -159,6 +159,7 @@ public class FeedSettingsFragment extends Fragment { setupEpisodeFilterPreference(); setupPlaybackSpeedPreference(); setupFeedAutoSkipPreference(); + setupEpisodeNotificationPreference(); updateAutoDeleteSummary(); updateVolumeReductionValue(); @@ -394,6 +395,19 @@ public class FeedSettingsFragment extends Fragment { } } + private void setupEpisodeNotificationPreference() { + SwitchPreferenceCompat pref = findPreference("episodeNotification"); + + pref.setChecked(feedPreferences.getShowEpisodeNotification()); + pref.setOnPreferenceChangeListener((preference, newValue) -> { + boolean checked = newValue == Boolean.TRUE; + feedPreferences.setShowEpisodeNotification(checked); + feed.savePreferences(); + pref.setChecked(checked); + return false; + }); + } + private class ApplyToEpisodesDialog extends ConfirmationDialog { private final boolean autoDownload; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index fc3052e20..18a61f1e6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -8,10 +8,8 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; import de.danoeh.antennapod.view.ShownotesWebView; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index 669dbdac2..2fc3d4f0e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -17,7 +17,6 @@ import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.core.text.TextUtilsCompat; import androidx.core.util.ObjectsCompat; import androidx.core.view.ViewCompat; @@ -58,7 +57,7 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.ui.common.ThemeUtils; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; import de.danoeh.antennapod.view.ShownotesWebView; @@ -71,7 +70,6 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import java.util.Date; import java.util.List; import java.util.Locale; @@ -120,6 +118,7 @@ public class ItemFragment extends Fragment { private View butAction2; private ItemActionButton actionButton1; private ItemActionButton actionButton2; + private View noMediaLabel; private Disposable disposable; private PlaybackController controller; @@ -169,6 +168,7 @@ public class ItemFragment extends Fragment { butAction2Icon = layout.findViewById(R.id.butAction2Icon); butAction1Text = layout.findViewById(R.id.butAction1Text); butAction2Text = layout.findViewById(R.id.butAction2Text); + noMediaLabel = layout.findViewById(R.id.noMediaLabel); butAction1.setOnClickListener(v -> { if (actionButton1 instanceof StreamActionButton && !UserPreferences.isStreamOverDownload() @@ -291,14 +291,19 @@ public class ItemFragment extends Fragment { txtvPublished.setContentDescription(DateUtils.formatForAccessibility(getContext(), item.getPubDate())); } + RequestOptions options = new RequestOptions() + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .transforms(new FitCenter(), + new RoundedCorners((int) (4 * getResources().getDisplayMetrics().density))) + .dontAnimate(); + Glide.with(getActivity()) .load(ImageResourceUtils.getImageLocation(item)) - .apply(new RequestOptions() - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .transforms(new FitCenter(), - new RoundedCorners((int) (4 * getResources().getDisplayMetrics().density))) - .dontAnimate()) + .error(Glide.with(getActivity()) + .load(ImageResourceUtils.getFallbackImageLocation(item)) + .apply(options)) + .apply(options) .into(imgvCover); updateButtons(); } @@ -319,7 +324,9 @@ public class ItemFragment extends Fragment { if (media == null) { actionButton1 = new MarkAsPlayedActionButton(item); actionButton2 = new VisitWebsiteActionButton(item); + noMediaLabel.setVisibility(View.VISIBLE); } else { + noMediaLabel.setVisibility(View.GONE); if (media.getDuration() > 0) { txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); txtvDuration.setContentDescription( diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index d748d14c9..1aa66dcbb 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.view.MenuInflater; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.ItemTouchHelper; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java index c994b4d8b..14f355b52 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java @@ -46,6 +46,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. private FeedDiscoverAdapter adapter; private GridView discoverGridLayout; private TextView errorTextView; + private TextView poweredByTextView; private LinearLayout errorView; private Button errorRetry; @@ -63,6 +64,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. errorTextView = root.findViewById(R.id.discover_error_txtV); errorRetry = root.findViewById(R.id.discover_error_retry_btn); errorRetry.setOnClickListener((listener) -> loadToplist()); + poweredByTextView = root.findViewById(R.id.discover_powered_by_itunes); adapter = new FeedDiscoverAdapter((MainActivity) getActivity()); discoverGridLayout.setAdapter(adapter); @@ -110,6 +112,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. discoverGridLayout.setVisibility(View.INVISIBLE); errorView.setVisibility(View.GONE); errorRetry.setVisibility(View.INVISIBLE); + poweredByTextView.setVisibility(View.VISIBLE); ItunesTopListLoader loader = new ItunesTopListLoader(getContext()); SharedPreferences prefs = getActivity().getSharedPreferences(ItunesTopListLoader.PREFS, MODE_PRIVATE); @@ -122,6 +125,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. progressBar.setVisibility(View.GONE); discoverGridLayout.setVisibility(View.INVISIBLE); errorRetry.setVisibility(View.INVISIBLE); + poweredByTextView.setVisibility(View.INVISIBLE); return; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java index 087abc327..fc500a223 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java @@ -6,7 +6,6 @@ import androidx.annotation.NonNull; import androidx.fragment.app.ListFragment; import android.util.Log; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ListView; 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 0894c2df6..4735cab42 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -27,6 +27,7 @@ import android.widget.TextView; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.joanzapata.iconify.Iconify; +import java.util.Locale; import java.util.concurrent.Callable; import de.danoeh.antennapod.R; @@ -65,6 +66,12 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem public static final String TAG = "SubscriptionFragment"; private static final String PREFS = "SubscriptionFragment"; private static final String PREF_NUM_COLUMNS = "columns"; + private static final int MIN_NUM_COLUMNS = 2; + private static final int[] COLUMN_CHECKBOX_IDS = { + R.id.subscription_num_columns_2, + R.id.subscription_num_columns_3, + R.id.subscription_num_columns_4, + R.id.subscription_num_columns_5}; private GridView subscriptionGridLayout; private DBReader.NavDrawerData navDrawerData; @@ -97,6 +104,11 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem toolbar.setOnMenuItemClickListener(this); ((MainActivity) getActivity()).setupToolbarToggle(toolbar); toolbar.inflateMenu(R.menu.subscriptions); + for (int i = 0; i < COLUMN_CHECKBOX_IDS.length; i++) { + // Do this in Java to localize numbers + toolbar.getMenu().findItem(COLUMN_CHECKBOX_IDS[i]) + .setTitle(String.format(Locale.getDefault(), "%d", i + MIN_NUM_COLUMNS)); + } refreshToolbarState(); subscriptionGridLayout = root.findViewById(R.id.subscriptions_grid); @@ -119,10 +131,7 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem private void refreshToolbarState() { int columns = prefs.getInt(PREF_NUM_COLUMNS, getDefaultNumOfColumns()); - toolbar.getMenu().findItem(R.id.subscription_num_columns_2).setChecked(columns == 2); - toolbar.getMenu().findItem(R.id.subscription_num_columns_3).setChecked(columns == 3); - toolbar.getMenu().findItem(R.id.subscription_num_columns_4).setChecked(columns == 4); - toolbar.getMenu().findItem(R.id.subscription_num_columns_5).setChecked(columns == 5); + toolbar.getMenu().findItem(COLUMN_CHECKBOX_IDS[columns - MIN_NUM_COLUMNS]).setChecked(true); isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item, updateRefreshMenuItemChecker); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index e272b2869..1f5434688 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -4,12 +4,9 @@ import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; -import androidx.appcompat.widget.SearchView; import android.util.Log; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java index 0d6e79e84..ec61c82f2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java @@ -174,7 +174,9 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat { String[] entries = new String[values.length]; for (int x = 0; x < values.length; x++) { int v = Integer.parseInt(values[x]); - if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) { + if (v == UserPreferences.EPISODE_CLEANUP_EXCEPT_FAVORITE) { + entries[x] = res.getString(R.string.episode_cleanup_except_favorite_removal); + } else if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) { entries[x] = res.getString(R.string.episode_cleanup_queue_removal); } else if (v == UserPreferences.EPISODE_CLEANUP_NULL){ entries[x] = res.getString(R.string.episode_cleanup_never); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java new file mode 100644 index 000000000..187e8480b --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java @@ -0,0 +1,302 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Paint; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RadioGroup; +import android.widget.TextView; +import android.widget.ViewFlipper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.textfield.TextInputLayout; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice; +import de.danoeh.antennapod.core.util.FileNameGenerator; +import de.danoeh.antennapod.core.util.IntentUtils; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Guides the user through the authentication process. + */ +public class GpodderAuthenticationFragment extends DialogFragment { + public static final String TAG = "GpodnetAuthActivity"; + + private ViewFlipper viewFlipper; + + private static final int STEP_DEFAULT = -1; + private static final int STEP_HOSTNAME = 0; + private static final int STEP_LOGIN = 1; + private static final int STEP_DEVICE = 2; + private static final int STEP_FINISH = 3; + + private int currentStep = -1; + + private GpodnetService service; + private volatile String username; + private volatile String password; + private volatile GpodnetDevice selectedDevice; + private List<GpodnetDevice> devices; + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + dialog.setTitle(GpodnetService.DEFAULT_BASE_HOST); + dialog.setNegativeButton(R.string.cancel_label, null); + dialog.setCancelable(false); + this.setCancelable(false); + + View root = View.inflate(getContext(), R.layout.gpodnetauth_dialog, null); + viewFlipper = root.findViewById(R.id.viewflipper); + advance(); + dialog.setView(root); + + return dialog.create(); + } + + private void setupHostView(View view) { + final Button selectHost = view.findViewById(R.id.chooseHostButton); + final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup); + final EditText serverUrlText = view.findViewById(R.id.serverUrlText); + if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHostname())) { + serverUrlText.setText(GpodnetPreferences.getHostname()); + } + final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput); + serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { + serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE); + }); + selectHost.setOnClickListener(v -> { + if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) { + GpodnetPreferences.setHostname(serverUrlText.getText().toString()); + } else { + GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST); + } + service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname()); + getDialog().setTitle(GpodnetPreferences.getHostname()); + advance(); + }); + } + + private void setupLoginView(View view) { + final EditText username = view.findViewById(R.id.etxtUsername); + final EditText password = view.findViewById(R.id.etxtPassword); + final Button login = view.findViewById(R.id.butLogin); + final TextView txtvError = view.findViewById(R.id.credentialsError); + final ProgressBar progressBar = view.findViewById(R.id.progBarLogin); + final TextView createAccount = view.findViewById(R.id.createAccountButton); + + createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/")); + + password.setOnEditorActionListener((v, actionID, event) -> + actionID == EditorInfo.IME_ACTION_GO && login.performClick()); + + login.setOnClickListener(v -> { + final String usernameStr = username.getText().toString(); + final String passwordStr = password.getText().toString(); + + if (usernameHasUnwantedChars(usernameStr)) { + txtvError.setText(R.string.gpodnetsync_username_characters_error); + txtvError.setVisibility(View.VISIBLE); + return; + } + + login.setEnabled(false); + progressBar.setVisibility(View.VISIBLE); + txtvError.setVisibility(View.GONE); + InputMethodManager inputManager = (InputMethodManager) getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + + Completable.fromAction(() -> { + service.authenticate(usernameStr, passwordStr); + devices = service.getDevices(); + GpodderAuthenticationFragment.this.username = usernameStr; + GpodderAuthenticationFragment.this.password = passwordStr; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + login.setEnabled(true); + progressBar.setVisibility(View.GONE); + advance(); + }, error -> { + login.setEnabled(true); + progressBar.setVisibility(View.GONE); + txtvError.setText(error.getCause().getMessage()); + txtvError.setVisibility(View.VISIBLE); + }); + + }); + } + + private void setupDeviceView(View view) { + final EditText deviceName = view.findViewById(R.id.deviceName); + final LinearLayout devicesContainer = view.findViewById(R.id.devicesContainer); + deviceName.setText(generateDeviceName()); + + MaterialButton createDeviceButton = view.findViewById(R.id.createDeviceButton); + createDeviceButton.setOnClickListener(v -> createDevice(view)); + + for (GpodnetDevice device : devices) { + View row = View.inflate(getContext(), R.layout.gpodnetauth_device_row, null); + Button selectDeviceButton = row.findViewById(R.id.selectDeviceButton); + selectDeviceButton.setOnClickListener(v -> { + selectedDevice = device; + advance(); + }); + selectDeviceButton.setText(device.getCaption()); + devicesContainer.addView(row); + } + } + + private void createDevice(View view) { + final EditText deviceName = view.findViewById(R.id.deviceName); + final TextView txtvError = view.findViewById(R.id.deviceSelectError); + final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice); + + String deviceNameStr = deviceName.getText().toString(); + if (isDeviceInList(deviceNameStr)) { + return; + } + progBarCreateDevice.setVisibility(View.VISIBLE); + txtvError.setVisibility(View.GONE); + deviceName.setEnabled(false); + + Observable.fromCallable(() -> { + String deviceId = generateDeviceId(deviceNameStr); + service.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE); + return new GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(device -> { + progBarCreateDevice.setVisibility(View.GONE); + selectedDevice = device; + advance(); + }, error -> { + deviceName.setEnabled(true); + progBarCreateDevice.setVisibility(View.GONE); + txtvError.setText(error.getMessage()); + txtvError.setVisibility(View.VISIBLE); + }); + } + + private String generateDeviceName() { + String baseName = getString(R.string.gpodnetauth_device_name_default, Build.MODEL); + String name = baseName; + int num = 1; + while (isDeviceInList(name)) { + name = baseName + " (" + num + ")"; + num++; + } + return name; + } + + private String generateDeviceId(String name) { + // devices names must be of a certain form: + // https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices + return FileNameGenerator.generateFileName(name).replaceAll("\\W", "_").toLowerCase(Locale.US); + } + + private boolean isDeviceInList(String name) { + if (devices == null) { + return false; + } + String id = generateDeviceId(name); + for (GpodnetDevice device : devices) { + if (device.getId().equals(id) || device.getCaption().equals(name)) { + return true; + } + } + return false; + } + + private GpodnetDevice findDevice(String id) { + if (devices == null) { + return null; + } + for (GpodnetDevice device : devices) { + if (device.getId().equals(id)) { + return device; + } + } + return null; + } + + private void setupFinishView(View view) { + final Button sync = view.findViewById(R.id.butSyncNow); + + sync.setOnClickListener(v -> { + dismiss(); + SyncService.sync(getContext()); + }); + } + + private void writeLoginCredentials() { + GpodnetPreferences.setUsername(username); + GpodnetPreferences.setPassword(password); + GpodnetPreferences.setDeviceID(selectedDevice.getId()); + } + + private void advance() { + if (currentStep < STEP_FINISH) { + + View view = viewFlipper.getChildAt(currentStep + 1); + if (currentStep == STEP_DEFAULT) { + setupHostView(view); + } else if (currentStep == STEP_HOSTNAME) { + setupLoginView(view); + } else if (currentStep == STEP_LOGIN) { + if (username == null || password == null) { + throw new IllegalStateException("Username and password must not be null here"); + } else { + setupDeviceView(view); + } + } else if (currentStep == STEP_DEVICE) { + if (selectedDevice == null) { + throw new IllegalStateException("Device must not be null here"); + } else { + writeLoginCredentials(); + setupFinishView(view); + } + } + if (currentStep != STEP_DEFAULT) { + viewFlipper.showNext(); + } + currentStep++; + } else { + dismiss(); + } + } + + private boolean usernameHasUnwantedChars(String username) { + Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]"); + Matcher containsUnwantedChars = special.matcher(username); + return containsUnwantedChars.find(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java index bec73894c..4fb734e17 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java @@ -1,15 +1,12 @@ package de.danoeh.antennapod.fragment.preferences; import android.app.Activity; -import android.content.SharedPreferences; import android.os.Bundle; import androidx.core.text.HtmlCompat; -import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import android.text.Spanned; import android.text.format.DateUtils; -import android.widget.Toast; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; @@ -17,19 +14,16 @@ import de.danoeh.antennapod.core.event.SyncServiceEvent; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.sync.SyncService; import de.danoeh.antennapod.dialog.AuthenticationDialog; -import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; - public class GpodderPreferencesFragment extends PreferenceFragmentCompat { private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate"; private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; private static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync"; private static final String PREF_GPODNET_FORCE_FULL_SYNC = "pref_gpodnet_force_full_sync"; private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; - private static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname"; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -54,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) public void syncStatusChanged(SyncServiceEvent event) { + updateGpodnetPreferenceScreen(); if (!GpodnetPreferences.loggedIn()) { return; } @@ -69,6 +64,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { private void setupGpodderScreen() { final Activity activity = getActivity(); + findPreference(PREF_GPODNET_LOGIN).setOnPreferenceClickListener(preference -> { + new GpodderAuthenticationFragment().show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG); + return true; + }); findPreference(PREF_GPODNET_SETLOGIN_INFORMATION) .setOnPreferenceClickListener(preference -> { AuthenticationDialog dialog = new AuthenticationDialog(activity, @@ -97,11 +96,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { updateGpodnetPreferenceScreen(); return true; }); - findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> { - GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener( - dialog -> updateGpodnetPreferenceScreen()); - return true; - }); } private void updateGpodnetPreferenceScreen() { @@ -122,7 +116,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { } else { findPreference(PREF_GPODNET_LOGOUT).setSummary(null); } - findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname()); } private void updateLastGpodnetSyncReport(boolean successful, long lastTime) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 99fd12021..7bf602e35 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -1,8 +1,11 @@ package de.danoeh.antennapod.fragment.preferences; import android.content.Intent; +import android.os.Build; import android.os.Bundle; +import android.provider.Settings; import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import com.bytehamster.lib.preferencesearch.SearchConfiguration; import com.bytehamster.lib.preferencesearch.SearchPreference; @@ -20,7 +23,7 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork"; private static final String PREF_SCREEN_GPODDER = "prefScreenGpodder"; private static final String PREF_SCREEN_STORAGE = "prefScreenStorage"; - private static final String PREF_FAQ = "prefFaq"; + private static final String PREF_DOCUMENTATION = "prefDocumentation"; private static final String PREF_VIEW_FORUM = "prefViewForum"; private static final String PREF_SEND_BUG_REPORT = "prefSendBugReport"; private static final String PREF_CATEGORY_PROJECT = "project"; @@ -35,10 +38,18 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { setupSearch(); // If you are writing a spin-off, please update the details on screens like "About" and "Report bug" - // and afterwards remove the following lines. + // and afterwards remove the following lines. Please keep in mind that AntennaPod is licensed under the GPL. + // This means that your application needs to be open-source under the GPL, too. + // It must also include a prominent copyright notice. String packageName = getContext().getPackageName(); if (!"de.danoeh.antennapod".equals(packageName) && !"de.danoeh.antennapod.debug".equals(packageName)) { findPreference(PREF_CATEGORY_PROJECT).setVisible(false); + Preference copyrightNotice = new Preference(getContext()); + copyrightNotice.setSummary("This application is based on AntennaPod." + + " The AntennaPod team does NOT provide support for this unofficial version." + + " If you can read this message, the developers of this modification" + + " violate the GNU General Public License (GPL)."); + findPreference(PREF_CATEGORY_PROJECT).getParent().addPreference(copyrightNotice); } } @@ -70,10 +81,16 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { return true; }); findPreference(PREF_NOTIFICATION).setOnPreferenceClickListener(preference -> { - ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications); + if (Build.VERSION.SDK_INT >= 26) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, getActivity().getPackageName()); + startActivity(intent); + } else { + ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications); + } return true; }); - findPreference(PREF_ABOUT).setOnPreferenceClickListener( preference -> { getParentFragmentManager().beginTransaction().replace(R.id.content, new AboutFragment()) @@ -88,8 +105,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { return true; } ); - findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> { - IntentUtils.openInBrowser(getContext(), "https://antennapod.org/faq.html"); + findPreference(PREF_DOCUMENTATION).setOnPreferenceClickListener(preference -> { + IntentUtils.openInBrowser(getContext(), "https://antennapod.org/documentation/"); return true; }); findPreference(PREF_VIEW_FORUM).setOnPreferenceClickListener(preference -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java index fcc37f644..3889034fa 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java @@ -15,7 +15,6 @@ import org.apache.commons.lang3.ArrayUtils; import java.util.Calendar; import java.util.GregorianCalendar; -import java.util.Locale; import java.util.concurrent.TimeUnit; public class NetworkPreferencesFragment extends PreferenceFragmentCompat { @@ -72,13 +71,13 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { Context context = getActivity().getApplicationContext(); String val; long interval = UserPreferences.getUpdateInterval(); - if(interval > 0) { + if (interval > 0) { int hours = (int) TimeUnit.MILLISECONDS.toHours(interval); - String hoursStr = context.getResources().getQuantityString(R.plurals.time_hours_quantified, hours, hours); - val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_every), hoursStr); + val = context.getResources().getQuantityString( + R.plurals.pref_autoUpdateIntervallOrTime_every_hours, hours, hours); } else { int[] timeOfDay = UserPreferences.getUpdateTimeOfDay(); - if(timeOfDay.length == 2) { + if (timeOfDay.length == 2) { Calendar cal = new GregorianCalendar(); cal.set(Calendar.HOUR_OF_DAY, timeOfDay[0]); cal.set(Calendar.MINUTE, timeOfDay[1]); @@ -97,8 +96,7 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { private void setParallelDownloadsText(int downloads) { final Resources res = getActivity().getResources(); - String s = String.format(Locale.getDefault(), "%d%s", - downloads, res.getString(R.string.parallel_downloads_suffix)); + String s = res.getString(R.string.parallel_downloads, downloads); findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s); } 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 689a72ba7..4d1b79965 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 @@ -9,11 +9,14 @@ import androidx.preference.PreferenceFragmentCompat; import android.widget.ListView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.FeedSortDialog; import de.danoeh.antennapod.fragment.NavDrawerFragment; import org.apache.commons.lang3.ArrayUtils; +import org.greenrobot.eventbus.EventBus; import java.util.List; @@ -37,8 +40,17 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat { (preference, newValue) -> { getActivity().recreate(); return true; - } - ); + }); + + findPreference(UserPreferences.PREF_SHOW_TIME_LEFT) + .setOnPreferenceChangeListener( + (preference, newValue) -> { + UserPreferences.setShowRemainTimeSetting((Boolean) newValue); + EventBus.getDefault().post(new UnreadItemsUpdateEvent()); + EventBus.getDefault().post(new PlayerStatusEvent()); + return true; + }); + findPreference(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS) .setOnPreferenceClickListener(preference -> { showDrawerPreferencesDialog(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java index b440d053b..0a64bbe71 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java @@ -33,7 +33,7 @@ public class AboutFragment extends PreferenceFragmentCompat { return true; }); findPreference("about_privacy_policy").setOnPreferenceClickListener((preference) -> { - IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy.html"); + IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy/"); return true; }); findPreference("about_licenses").setOnPreferenceClickListener((preference) -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java index 60d9f95dd..b844234b7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java @@ -6,8 +6,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.SimpleIconListAdapter; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java index 6db1389ea..d759a5ff2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java @@ -6,8 +6,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.SimpleIconListAdapter; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java index e8d8e113b..b77c74de6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java @@ -6,8 +6,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.SimpleIconListAdapter; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; |