diff options
author | ByteHamster <info@bytehamster.com> | 2021-03-05 10:09:10 +0100 |
---|---|---|
committer | ByteHamster <info@bytehamster.com> | 2021-03-05 10:12:35 +0100 |
commit | f76d3ad09e41c544b8af2f33db0529e3bcdabc0e (patch) | |
tree | f33d371de5a74e3ac75ff9431168b4a7c76a9246 /app/src/main/java/de/danoeh | |
parent | 5a8bfc0ea483d0af4db8f266969f1e52c2cd529d (diff) | |
parent | c58aa40b212c7ff5a798c2b3faafabbaaeac0b3f (diff) | |
download | AntennaPod-f76d3ad09e41c544b8af2f33db0529e3bcdabc0e.zip |
Merge branch 'develop' into folders
Diffstat (limited to 'app/src/main/java/de/danoeh')
64 files changed, 1236 insertions, 1446 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java index 912038e4c..0f1d38db6 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java @@ -1,95 +1,76 @@ package de.danoeh.antennapod.activity; -import android.app.Activity; -import android.content.Intent; import android.os.Bundle; - -import androidx.annotation.NonNull; +import android.text.TextUtils; import androidx.appcompat.app.AppCompatActivity; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; - -import org.apache.commons.lang3.Validate; - import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadRequest; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.dialog.AuthenticationDialog; +import io.reactivex.Completable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.Validate; + /** * Shows a username and a password text field. * The activity MUST be started with the ARG_DOWNlOAD_REQUEST argument set to a non-null value. - * Other arguments are optional. - * The activity's result will be the same DownloadRequest with the entered username and password. */ public class DownloadAuthenticationActivity extends AppCompatActivity { /** - * The download request object that contains information about the resource that requires a username and a password + * The download request object that contains information about the resource that requires a username and a password. */ public static final String ARG_DOWNLOAD_REQUEST = "request"; - /** - * True if the request should be sent to the DownloadRequester when this activity is finished, false otherwise. - * The default value is false. - */ - public static final String ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL = "send_to_downloadrequester"; - - private static final String RESULT_REQUEST = "request"; - - private EditText etxtUsername; - private EditText etxtPassword; @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getNoTitleTheme()); + setTheme(UserPreferences.getTranslucentTheme()); super.onCreate(savedInstanceState); - setContentView(R.layout.download_authentication_activity); - TextView txtvDescription = findViewById(R.id.txtvDescription); - etxtUsername = findViewById(R.id.etxtUsername); - etxtPassword = findViewById(R.id.etxtPassword); - Button butConfirm = findViewById(R.id.butConfirm); - Button butCancel = findViewById(R.id.butCancel); - Validate.isTrue(getIntent().hasExtra(ARG_DOWNLOAD_REQUEST), "Download request missing"); DownloadRequest request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST); - boolean sendToDownloadRequester = getIntent().getBooleanExtra(ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, false); - - String newDescription = txtvDescription.getText() + ":\n\n" + request.getTitle(); - txtvDescription.setText(newDescription); - - if (savedInstanceState != null) { - etxtUsername.setText(savedInstanceState.getString("username")); - etxtPassword.setText(savedInstanceState.getString("password")); - } - butConfirm.setOnClickListener(v -> { - String username = etxtUsername.getText().toString(); - String password = etxtPassword.getText().toString(); - request.setUsername(username); - request.setPassword(password); - Intent result = new Intent(); - result.putExtra(RESULT_REQUEST, request); - setResult(Activity.RESULT_OK, result); - - if (sendToDownloadRequester) { - DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request); + new AuthenticationDialog(this, R.string.authentication_label, true, "", "") { + @Override + protected void onConfirmed(String username, String password) { + Completable.fromAction( + () -> { + request.setUsername(username); + request.setPassword(password); + + if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + long mediaId = request.getFeedfileId(); + FeedMedia media = DBReader.getFeedMedia(mediaId); + if (media != null) { + FeedPreferences preferences = media.getItem().getFeed().getPreferences(); + if (TextUtils.isEmpty(preferences.getPassword()) + || TextUtils.isEmpty(preferences.getUsername())) { + preferences.setUsername(username); + preferences.setPassword(password); + DBWriter.setFeedPreferences(preferences); + } + } + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request); + finish(); + }); } - finish(); - }); - - butCancel.setOnClickListener(v -> { - setResult(Activity.RESULT_CANCELED); - finish(); - }); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString("username", etxtUsername.getText().toString()); - outState.putString("password", etxtPassword.getText().toString()); + @Override + protected void onCancelled() { + finish(); + } + }.show(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index d1716e009..b5edcc878 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.media.AudioManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -40,7 +41,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.StorageUtils; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.ui.common.ThemeUtils; import de.danoeh.antennapod.core.util.download.AutoUpdateManager; import de.danoeh.antennapod.dialog.RatingDialog; import de.danoeh.antennapod.fragment.AddFeedFragment; @@ -51,9 +52,11 @@ import de.danoeh.antennapod.fragment.FeedItemlistFragment; import de.danoeh.antennapod.fragment.NavDrawerFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; +import de.danoeh.antennapod.fragment.SearchFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; import de.danoeh.antennapod.fragment.TransitionEffect; import de.danoeh.antennapod.preferences.PreferenceUpgrader; +import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; import de.danoeh.antennapod.view.LockableBottomSheetBehavior; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.Validate; @@ -75,7 +78,6 @@ public class MainActivity extends CastEnabledActivity { public static final String EXTRA_FRAGMENT_TAG = "fragment_tag"; public static final String EXTRA_FRAGMENT_ARGS = "fragment_args"; public static final String EXTRA_FEED_ID = "fragment_feed_id"; - public static final String EXTRA_OPEN_PLAYER = "open_player"; public static final String EXTRA_REFRESH_ON_START = "refresh_on_start"; public static final String EXTRA_STARTED_FROM_SEARCH = "started_from_search"; public static final String KEY_GENERATED_VIEW_ID = "generated_view_id"; @@ -184,16 +186,16 @@ public class MainActivity extends CastEnabledActivity { } }; - public void setupToolbarToggle(@Nullable Toolbar toolbar) { + public void setupToolbarToggle(@NonNull Toolbar toolbar, boolean displayUpArrow) { if (drawerLayout != null) { // Tablet layout does not have a drawer drawerLayout.removeDrawerListener(drawerToggle); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close); drawerLayout.addDrawerListener(drawerToggle); drawerToggle.syncState(); - drawerToggle.setDrawerIndicatorEnabled(getSupportFragmentManager().getBackStackEntryCount() == 0); + drawerToggle.setDrawerIndicatorEnabled(!displayUpArrow); drawerToggle.setToolbarNavigationClickListener(v -> getSupportFragmentManager().popBackStack()); - } else if (getSupportFragmentManager().getBackStackEntryCount() == 0) { + } else if (!displayUpArrow) { toolbar.setNavigationIcon(null); } else { toolbar.setNavigationIcon(ThemeUtils.getDrawableFromAttr(this, R.attr.homeAsUpIndicator)); @@ -508,9 +510,11 @@ public class MainActivity extends CastEnabledActivity { } } sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); - } else if (intent.getBooleanExtra(EXTRA_OPEN_PLAYER, false)) { + } else if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, false)) { sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); bottomSheetCallback.onSlide(null, 1.0f); + } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { + handleDeeplink(intent.getData()); } // to avoid handling the intent twice when the configuration changes setIntent(new Intent(MainActivity.this, MainActivity.class)); @@ -520,6 +524,7 @@ public class MainActivity extends CastEnabledActivity { protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); + handleNavIntent(); } public Snackbar showSnackbarAbovePlayer(CharSequence text, int duration) { @@ -540,6 +545,59 @@ public class MainActivity extends CastEnabledActivity { return showSnackbarAbovePlayer(getResources().getText(text), duration); } + /** + * Handles the deep link incoming via App Actions. + * Performs an in-app search or opens the relevant feature of the app + * depending on the query. + * + * @param uri incoming deep link + */ + private void handleDeeplink(Uri uri) { + if (uri == null || uri.getPath() == null) { + return; + } + Log.d(TAG, "Handling deeplink: " + uri.toString()); + switch (uri.getPath()) { + case "/deeplink/search": + String query = uri.getQueryParameter("query"); + if (query == null) { + return; + } + + this.loadChildFragment(SearchFragment.newInstance(query)); + break; + case "/deeplink/main": + String feature = uri.getQueryParameter("page"); + if (feature == null) { + return; + } + switch (feature) { + case "DOWNLOADS": + loadFragment(DownloadsFragment.TAG, null); + break; + case "HISTORY": + loadFragment(PlaybackHistoryFragment.TAG, null); + break; + case "EPISODES": + loadFragment(EpisodesFragment.TAG, null); + break; + case "QUEUE": + loadFragment(QueueFragment.TAG, null); + break; + case "SUBSCRIPTIONS": + loadFragment(SubscriptionFragment.TAG, null); + break; + default: + showSnackbarAbovePlayer(getString(R.string.app_action_not_found, feature), + Snackbar.LENGTH_LONG); + return; + } + break; + default: + break; + } + } + //Hardware keyboard support @Override public boolean onKeyUp(int keyCode, KeyEvent event) { @@ -592,5 +650,4 @@ public class MainActivity extends CastEnabledActivity { } return super.onKeyUp(keyCode, event); } - } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index deb2fe0db..56a66ba93 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -1,10 +1,8 @@ package de.danoeh.antennapod.activity; -import android.Manifest; import android.annotation.TargetApi; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.graphics.PixelFormat; import android.os.Build; import android.os.Bundle; @@ -17,7 +15,6 @@ import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; -import android.widget.Toast; import com.bumptech.glide.Glide; @@ -28,17 +25,16 @@ import org.greenrobot.eventbus.ThreadMode; import java.text.NumberFormat; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.core.app.ActivityCompat; +import androidx.cardview.widget.CardView; import androidx.core.app.ActivityOptionsCompat; -import androidx.core.content.ContextCompat; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBReader; @@ -50,11 +46,9 @@ import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; -import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.MediaPlayerError; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; -import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; import de.danoeh.antennapod.dialog.PlaybackControlsDialog; import de.danoeh.antennapod.dialog.ShareDialog; import de.danoeh.antennapod.dialog.SkipPreferenceDialog; @@ -64,7 +58,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; - /** * Provides general features which are both needed for playing audio and video * files. @@ -72,9 +65,6 @@ import io.reactivex.schedulers.Schedulers; public abstract class MediaplayerActivity extends CastEnabledActivity implements OnSeekBarChangeListener { private static final String TAG = "MediaplayerActivity"; private static final String PREFS = "MediaPlayerActivityPreferences"; - private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft"; - private static final int REQUEST_CODE_STORAGE_PLAY_VIDEO = 42; - private static final int REQUEST_CODE_STORAGE_PLAY_AUDIO = 43; PlaybackController controller; @@ -87,6 +77,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements private ImageButton butFF; private TextView txtvFF; private ImageButton butSkip; + private CardView cardViewSeek; + private TextView txtvSeek; private boolean showTimeLeft = false; @@ -96,12 +88,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements private PlaybackController newPlaybackController() { return new PlaybackController(this) { - - @Override - public void setupGUI() { - MediaplayerActivity.this.setupGUI(); - } - @Override public void onPositionObserverUpdate() { MediaplayerActivity.this.onPositionObserverUpdate(); @@ -143,8 +129,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements } @Override - public boolean loadMediaInfo() { - return MediaplayerActivity.this.loadMediaInfo(); + public void loadMediaInfo() { + MediaplayerActivity.this.loadMediaInfo(); } @Override @@ -467,17 +453,15 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements * to the PlaybackService to ensure that the activity has the right * FeedMedia object. */ - boolean loadMediaInfo() { + void loadMediaInfo() { Log.d(TAG, "loadMediaInfo()"); - if(controller == null || controller.getMedia() == null) { - return false; + if (controller == null || controller.getMedia() == null) { + return; } - SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); - showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); + showTimeLeft = UserPreferences.shouldShowRemainingTime(); onPositionObserverUpdate(); checkFavorite(); updatePlaybackSpeedButton(); - return true; } void updatePlaybackSpeedButton() { @@ -492,9 +476,11 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements setContentView(getContentViewResourceId()); sbPosition = findViewById(R.id.sbPosition); txtvPosition = findViewById(R.id.txtvPosition); + cardViewSeek = findViewById(R.id.cardViewSeek); + txtvSeek = findViewById(R.id.txtvSeek); SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); - showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); + showTimeLeft = UserPreferences.shouldShowRemainingTime(); Log.d("timeleft", showTimeLeft ? "true" : "false"); txtvLength = findViewById(R.id.txtvLength); if (txtvLength != null) { @@ -518,9 +504,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements } txtvLength.setText(length); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft); - editor.apply(); + UserPreferences.setShowRemainTimeSetting(showTimeLeft); Log.d("timeleft on click", showTimeLeft ? "true" : "false"); }); } @@ -618,21 +602,21 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements } if (fromUser) { 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) { - int timeLeft = converter.convert(duration - (int) (prog * duration)); - txtvLength.setText("-" + Converter.getDurationStringLong(timeLeft)); - } + int position = converter.convert((int) (prog * controller.getDuration())); + txtvSeek.setText(Converter.getDurationStringLong(position)); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { - + cardViewSeek.setScaleX(.8f); + cardViewSeek.setScaleY(.8f); + cardViewSeek.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(1f).scaleX(1f).scaleY(1f) + .setDuration(200) + .start(); } @Override @@ -640,6 +624,13 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements if (controller != null) { 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(); } private void checkFavorite() { @@ -663,50 +654,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements }, error -> Log.e(TAG, Log.getStackTraceString(error))); } - void playExternalMedia(Intent intent, MediaType type) { - if (intent == null || intent.getData() == null) { - return; - } - if (Build.VERSION.SDK_INT >= 23 - && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show(); - } - - int code = REQUEST_CODE_STORAGE_PLAY_AUDIO; - if (type == MediaType.VIDEO) { - code = REQUEST_CODE_STORAGE_PLAY_VIDEO; - } - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, code); - return; - } - - Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath()); - ExternalMedia media = new ExternalMedia(intent.getData().getPath(), type); - - new PlaybackServiceStarter(this, media) - .callEvenIfRunning(true) - .startWhenPrepared(true) - .shouldStream(false) - .prepareImmediately(true) - .start(); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (requestCode == REQUEST_CODE_STORAGE_PLAY_AUDIO) { - playExternalMedia(getIntent(), MediaType.AUDIO); - } else if (requestCode == REQUEST_CODE_STORAGE_PLAY_VIDEO) { - playExternalMedia(getIntent(), MediaType.VIDEO); - } - } else { - Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show(); - } - } - @Nullable private static FeedItem getFeedItem(@Nullable Playable playable) { if (playable instanceof FeedMedia) { 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 18620a56a..a5883ca14 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -4,10 +4,14 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.LightingColorFilter; import android.os.Build; import android.os.Bundle; +import android.text.Spannable; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -15,6 +19,7 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -28,7 +33,6 @@ import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.PlayerStatusEvent; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.core.glide.ApGlideSettings; @@ -41,6 +45,7 @@ import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.service.download.HttpDownloader; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.syndication.handler.FeedHandler; @@ -49,7 +54,6 @@ import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeExceptio import de.danoeh.antennapod.core.util.DownloadError; import de.danoeh.antennapod.core.util.FileNameGenerator; import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.Optional; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.URLChecker; import de.danoeh.antennapod.core.util.playback.RemoteMedia; @@ -58,9 +62,11 @@ import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.discovery.PodcastSearcherRegistry; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; +import io.reactivex.observers.DisposableMaybeObserver; import io.reactivex.schedulers.Schedulers; import org.apache.commons.lang3.StringUtils; import org.greenrobot.eventbus.EventBus; @@ -87,6 +93,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { // Optional argument: specify a title for the actionbar. private static final int RESULT_ERROR = 2; private static final String TAG = "OnlineFeedViewActivity"; + private static final String PREFS = "OnlineFeedViewActivityPreferences"; + private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload"; + private volatile List<Feed> feeds; private Feed feed; private String selectedDownloadUrl; @@ -248,7 +257,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { url = URLChecker.prepareURL(url); feed = new Feed(url, null); if (username != null && password != null) { - feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, username, password)); + feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, + VolumeAdaptionSetting.OFF, username, password)); } String fileUrl = new File(getExternalCacheDir(), FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); @@ -283,11 +293,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { dialog.show(); } } else { - String errorMsg = status.getReason().getErrorString(OnlineFeedViewActivity.this); - if (status.getReasonDetailed() != null) { - errorMsg += " (" + status.getReasonDetailed() + ")"; - } - showErrorDialog(errorMsg); + showErrorDialog(status.getReason().getErrorString(OnlineFeedViewActivity.this), status.getReasonDetailed()); } } @@ -316,37 +322,47 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } Log.d(TAG, "Parsing feed"); - parser = Observable.fromCallable(this::doParseFeed) + parser = Maybe.fromCallable(this::doParseFeed) .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(optionalResult -> { - if(optionalResult.isPresent()) { - FeedHandlerResult result = optionalResult.get(); - beforeShowFeedInformation(result.feed); + .subscribeWith(new DisposableMaybeObserver<FeedHandlerResult>() { + @Override + public void onSuccess(@NonNull FeedHandlerResult result) { showFeedInformation(result.feed, result.alternateFeedUrls); } - }, error -> { - String errorMsg = DownloadError.ERROR_PARSER_EXCEPTION.getErrorString( - OnlineFeedViewActivity.this) + " (" + error.getMessage() + ")"; - showErrorDialog(errorMsg); - Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error)); + + @Override + public void onComplete() { + // Ignore null result: We showed the discovery dialog. + } + + @Override + public void onError(@NonNull Throwable error) { + showErrorDialog(error.getMessage(), ""); + Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error)); + } }); } - @NonNull - private Optional<FeedHandlerResult> doParseFeed() throws Exception { + /** + * Try to parse the feed. + * @return The FeedHandlerResult if successful. + * Null if unsuccessful but we started another attempt. + * @throws Exception If unsuccessful but we do not know a resolution. + */ + @Nullable + private FeedHandlerResult doParseFeed() throws Exception { FeedHandler handler = new FeedHandler(); try { - return Optional.of(handler.parseFeed(feed)); + return handler.parseFeed(feed); } catch (UnsupportedFeedtypeException e) { Log.d(TAG, "Unsupported feed type detected"); if ("html".equalsIgnoreCase(e.getRootElement())) { boolean dialogShown = showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url()); if (dialogShown) { - return Optional.empty(); + return null; // Should not display an error message } else { - Log.d(TAG, "Supplied feed is an HTML web page that has no references to any feed"); - throw e; + throw new UnsupportedFeedtypeException(getString(R.string.download_error_unsupported_type_html)); } } else { throw e; @@ -361,23 +377,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } /** - * Called after the feed has been downloaded and parsed and before showFeedInformation is called. - * This method is executed on a background thread - */ - private void beforeShowFeedInformation(Feed feed) { - Log.d(TAG, "Removing HTML from feed description"); - - feed.setDescription(HtmlToPlainText.getPlainText(feed.getDescription())); - - Log.d(TAG, "Removing HTML from shownotes"); - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - item.setDescription(HtmlToPlainText.getPlainText(item.getDescription())); - } - } - } - - /** * Called when feed parsed successfully. * This method is executed on the GUI thread. */ @@ -420,7 +419,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { viewBinding.titleLabel.setText(feed.getTitle()); viewBinding.authorLabel.setText(feed.getAuthor()); - description.setText(feed.getDescription()); + description.setText(HtmlToPlainText.getPlainText(feed.getDescription())); viewBinding.subscribeButton.setOnClickListener(v -> { if (feedInFeedlist(feed)) { @@ -445,6 +444,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity { IntentUtils.sendLocalBroadcast(this, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE); }); + if (UserPreferences.isEnableAutodownload()) { + SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE); + viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)); + } + final int MAX_LINES_COLLAPSED = 10; description.setMaxLines(MAX_LINES_COLLAPSED); description.setOnClickListener(v -> { @@ -511,10 +515,17 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (didPressSubscribe) { didPressSubscribe = false; if (UserPreferences.isEnableAutodownload()) { + boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked(); + Feed feed1 = DBReader.getFeed(getFeedId(feed)); FeedPreferences feedPreferences = feed1.getPreferences(); - feedPreferences.setAutoDownload(viewBinding.autoDownloadCheckBox.isChecked()); - feed1.savePreferences(); + feedPreferences.setAutoDownload(autoDownload); + DBWriter.setFeedPreferences(feedPreferences); + + SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload); + editor.apply(); } openFeed(); } @@ -553,12 +564,16 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } @UiThread - private void showErrorDialog(String errorMsg) { + private void showErrorDialog(String errorMsg, String details) { if (!isFinishing() && !isPaused) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.error_label); if (errorMsg != null) { - builder.setMessage(errorMsg); + String total = errorMsg + "\n\n" + details; + SpannableString errorMessage = new SpannableString(total); + errorMessage.setSpan(new ForegroundColorSpan(0x88888888), + errorMsg.length(), total.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setMessage(errorMessage); } else { builder.setMessage(R.string.download_error_error_unknown); } 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 1c8619e99..15d0bec4a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -17,7 +17,6 @@ import android.widget.ImageView; import androidx.core.view.WindowCompat; import androidx.appcompat.app.ActionBar; -import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.view.Menu; @@ -37,12 +36,12 @@ import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicBoolean; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; import de.danoeh.antennapod.view.AspectRatioVideoView; /** @@ -88,9 +87,7 @@ public class VideoplayerActivity extends MediaplayerActivity { @Override protected void onResume() { super.onResume(); - if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { - playExternalMedia(getIntent(), MediaType.VIDEO); - } else if (PlaybackService.isCasting()) { + if (PlaybackService.isCasting()) { Intent intent = PlaybackService.getPlayerActivityIntent(this); if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) { destroyingDueToReload = true; @@ -135,17 +132,13 @@ public class VideoplayerActivity extends MediaplayerActivity { } @Override - protected boolean loadMediaInfo() { - if (!super.loadMediaInfo() || controller == null) { - return false; - } + protected void loadMediaInfo() { + super.loadMediaInfo(); Playable media = controller.getMedia(); if (media != null) { getSupportActionBar().setSubtitle(media.getEpisodeTitle()); getSupportActionBar().setTitle(media.getFeedTitle()); - return true; } - return false; } @Override @@ -347,7 +340,7 @@ public class VideoplayerActivity extends MediaplayerActivity { Log.d(TAG, "ReloadNotification received, switching to Castplayer now"); destroyingDueToReload = true; finish(); - startActivity(new Intent(this, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true)); + new MainActivityStarter(this).withOpenPlayer().start(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java index 4805dba10..3020aba43 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java @@ -2,33 +2,34 @@ package de.danoeh.antennapod.activity; import android.Manifest; import android.app.WallpaperManager; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.widget.ImageView; -import androidx.appcompat.app.AppCompatActivity; - import android.appwidget.AppWidgetManager; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.view.View; -import android.widget.RelativeLayout; +import android.widget.CheckBox; +import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; - +import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.PlayerWidget; -import de.danoeh.antennapod.core.service.PlayerWidgetJobService; +import de.danoeh.antennapod.core.widget.WidgetUpdaterJobService; public class WidgetConfigActivity extends AppCompatActivity { private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; private SeekBar opacitySeekBar; private TextView opacityTextView; - private RelativeLayout widgetPreview; + private View widgetPreview; + private CheckBox ckRewind; + private CheckBox ckFastForward; + private CheckBox ckSkip; @Override protected void onCreate(Bundle savedInstanceState) { @@ -73,6 +74,32 @@ public class WidgetConfigActivity extends AppCompatActivity { } }); + + widgetPreview.findViewById(R.id.txtNoPlaying).setVisibility(View.GONE); + TextView title = widgetPreview.findViewById(R.id.txtvTitle); + title.setVisibility(View.VISIBLE); + title.setText(R.string.app_name); + TextView progress = widgetPreview.findViewById(R.id.txtvProgress); + progress.setVisibility(View.VISIBLE); + progress.setText(R.string.position_default_label); + + ckRewind = findViewById(R.id.ckRewind); + ckRewind.setOnClickListener(v -> displayPreviewPanel()); + ckFastForward = findViewById(R.id.ckFastForward); + ckFastForward.setOnClickListener(v -> displayPreviewPanel()); + ckSkip = findViewById(R.id.ckSkip); + ckSkip.setOnClickListener(v -> displayPreviewPanel()); + } + + private void displayPreviewPanel() { + boolean showExtendedPreview = ckRewind.isChecked() || ckFastForward.isChecked() || ckSkip.isChecked(); + widgetPreview.findViewById(R.id.extendedButtonsContainer) + .setVisibility(showExtendedPreview ? View.VISIBLE : View.GONE); + widgetPreview.findViewById(R.id.butPlay).setVisibility(showExtendedPreview ? View.GONE : View.VISIBLE); + widgetPreview.findViewById(R.id.butFastForward) + .setVisibility(ckFastForward.isChecked() ? View.VISIBLE : View.GONE); + widgetPreview.findViewById(R.id.butSkip).setVisibility(ckSkip.isChecked() ? View.VISIBLE : View.GONE); + widgetPreview.findViewById(R.id.butRew).setVisibility(ckRewind.isChecked() ? View.VISIBLE : View.GONE); } private void displayDeviceBackground() { @@ -91,13 +118,16 @@ public class WidgetConfigActivity extends AppCompatActivity { SharedPreferences prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor); + editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked()); + editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked()); + editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked()); editor.apply(); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); setResult(RESULT_OK, resultValue); finish(); - PlayerWidgetJobService.updateWidget(this); + WidgetUpdaterJobService.performBackgroundUpdate(this); } private int getColorWithAlpha(int color, int opacity) { diff --git a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java deleted file mode 100644 index cfd6ec702..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java +++ /dev/null @@ -1,395 +0,0 @@ -package de.danoeh.antennapod.activity.gpoddernet; - -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.ViewFlipper; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -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.GpodnetServiceException; -import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice; - -/** - * Guides the user through the authentication process - * Step 1: Request username and password from user - * Step 2: Choose device from a list of available devices or create a new one - * Step 3: Choose from a list of actions - */ -public class GpodnetAuthenticationActivity extends AppCompatActivity { - private static final String TAG = "GpodnetAuthActivity"; - - private ViewFlipper viewFlipper; - - private static final int STEP_DEFAULT = -1; - private static final int STEP_LOGIN = 0; - private static final int STEP_DEVICE = 1; - private static final int STEP_FINISH = 2; - - private int currentStep = -1; - - private GpodnetService service; - private volatile String username; - private volatile String password; - private volatile GpodnetDevice selectedDevice; - - private View[] views; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - setContentView(R.layout.gpodnetauth_activity); - service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname()); - - viewFlipper = findViewById(R.id.viewflipper); - LayoutInflater inflater = (LayoutInflater) - getSystemService(Context.LAYOUT_INFLATER_SERVICE); - views = new View[]{ - inflater.inflate(R.layout.gpodnetauth_credentials, viewFlipper, false), - inflater.inflate(R.layout.gpodnetauth_device, viewFlipper, false), - inflater.inflate(R.layout.gpodnetauth_finish, viewFlipper, false) - }; - for (View view : views) { - viewFlipper.addView(view); - } - advance(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - 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.txtvError); - final ProgressBar progressBar = view.findViewById(R.id.progBarLogin); - - password.setOnEditorActionListener((v, actionID, event) -> - actionID == EditorInfo.IME_ACTION_GO && login.performClick()); - - login.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View 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; - } - if (BuildConfig.DEBUG) Log.d(TAG, "Checking login credentials"); - AsyncTask<GpodnetService, Void, Void> authTask = new AsyncTask<GpodnetService, Void, Void>() { - - volatile Exception exception; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - login.setEnabled(false); - progressBar.setVisibility(View.VISIBLE); - txtvError.setVisibility(View.GONE); - // hide the keyboard - InputMethodManager inputManager = (InputMethodManager) - getSystemService(Context.INPUT_METHOD_SERVICE); - inputManager.hideSoftInputFromWindow(login.getWindowToken(), - InputMethodManager.HIDE_NOT_ALWAYS); - - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - login.setEnabled(true); - progressBar.setVisibility(View.GONE); - - if (exception == null) { - advance(); - } else { - txtvError.setText(exception.getCause().getMessage()); - txtvError.setVisibility(View.VISIBLE); - } - } - - @Override - protected Void doInBackground(GpodnetService... params) { - try { - params[0].authenticate(usernameStr, passwordStr); - GpodnetAuthenticationActivity.this.username = usernameStr; - GpodnetAuthenticationActivity.this.password = passwordStr; - } catch (GpodnetServiceException e) { - e.printStackTrace(); - exception = e; - } - return null; - } - }; - authTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, service); - } - }); - } - - private void setupDeviceView(View view) { - final EditText deviceID = view.findViewById(R.id.etxtDeviceID); - final EditText caption = view.findViewById(R.id.etxtCaption); - final Button createNewDevice = view.findViewById(R.id.butCreateNewDevice); - final Button chooseDevice = view.findViewById(R.id.butChooseExistingDevice); - final TextView txtvError = view.findViewById(R.id.txtvError); - final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice); - final Spinner spinnerDevices = view.findViewById(R.id.spinnerChooseDevice); - - - // load device list - final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<>(); - new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() { - - @Override - protected void onPreExecute() { - super.onPreExecute(); - chooseDevice.setEnabled(false); - spinnerDevices.setEnabled(false); - createNewDevice.setEnabled(false); - } - - @Override - protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) { - super.onPostExecute(gpodnetDevices); - if (gpodnetDevices != null) { - List<String> deviceNames = new ArrayList<>(); - for (GpodnetDevice device : gpodnetDevices) { - deviceNames.add(device.getCaption()); - } - spinnerDevices.setAdapter(new ArrayAdapter<>(GpodnetAuthenticationActivity.this, - android.R.layout.simple_spinner_dropdown_item, deviceNames)); - spinnerDevices.setEnabled(true); - if (!deviceNames.isEmpty()) { - chooseDevice.setEnabled(true); - } - devices.set(gpodnetDevices); - deviceID.setText(generateDeviceID(gpodnetDevices)); - createNewDevice.setEnabled(true); - } - } - - @Override - protected List<GpodnetDevice> doInBackground(GpodnetService... params) { - try { - return params[0].getDevices(); - } catch (GpodnetServiceException e) { - e.printStackTrace(); - return null; - } - } - }.execute(service); - - - createNewDevice.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (checkDeviceIDText(deviceID, caption, txtvError, devices.get())) { - final String deviceStr = deviceID.getText().toString(); - final String captionStr = caption.getText().toString(); - - new AsyncTask<GpodnetService, Void, GpodnetDevice>() { - - private volatile Exception exception; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - createNewDevice.setEnabled(false); - chooseDevice.setEnabled(false); - progBarCreateDevice.setVisibility(View.VISIBLE); - txtvError.setVisibility(View.GONE); - } - - @Override - protected void onPostExecute(GpodnetDevice result) { - super.onPostExecute(result); - createNewDevice.setEnabled(true); - chooseDevice.setEnabled(true); - progBarCreateDevice.setVisibility(View.GONE); - if (exception == null) { - selectedDevice = result; - advance(); - } else { - txtvError.setText(exception.getMessage()); - txtvError.setVisibility(View.VISIBLE); - } - } - - @Override - protected GpodnetDevice doInBackground(GpodnetService... params) { - try { - params[0].configureDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE); - return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0); - } catch (GpodnetServiceException e) { - e.printStackTrace(); - exception = e; - } - return null; - } - }.execute(service); - } - } - }); - - chooseDevice.setOnClickListener(v -> { - final int position = spinnerDevices.getSelectedItemPosition(); - if (position != AdapterView.INVALID_POSITION) { - selectedDevice = devices.get().get(position); - advance(); - } - }); - } - - - private String generateDeviceID(List<GpodnetDevice> gpodnetDevices) { - // devices names must be of a certain form: - // https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices - // This is more restrictive than needed, but I think it makes for more readable names. - String baseId = Build.MODEL.replaceAll("\\W", ""); - String id = baseId; - int num = 0; - - while (isDeviceWithIdInList(id, gpodnetDevices)) { - id = baseId + "_" + num; - num++; - } - - return id; - } - - private boolean isDeviceWithIdInList(String id, List<GpodnetDevice> gpodnetDevices) { - if (gpodnetDevices == null) { - return false; - } - for (GpodnetDevice device : gpodnetDevices) { - if (device.getId().equals(id)) { - return true; - } - } - return false; - } - - private boolean checkDeviceIDText(EditText deviceID, EditText caption, TextView txtvError, List<GpodnetDevice> devices) { - String text = deviceID.getText().toString(); - if (text.length() == 0) { - txtvError.setText(R.string.gpodnetauth_device_errorEmpty); - txtvError.setVisibility(View.VISIBLE); - return false; - } else if (caption.length() == 0) { - txtvError.setText(R.string.gpodnetauth_device_caption_errorEmpty); - txtvError.setVisibility(View.VISIBLE); - return false; - } else { - if (devices != null) { - if (isDeviceWithIdInList(text, devices)) { - txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed); - txtvError.setVisibility(View.VISIBLE); - return false; - } - txtvError.setVisibility(View.GONE); - return true; - } - return true; - } - - } - - private void setupFinishView(View view) { - final Button sync = view.findViewById(R.id.butSyncNow); - final Button back = view.findViewById(R.id.butGoMainscreen); - - sync.setOnClickListener(v -> { - finish(); - SyncService.sync(getApplicationContext()); - }); - back.setOnClickListener(v -> { - Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - }); - } - - private void writeLoginCredentials() { - if (BuildConfig.DEBUG) Log.d(TAG, "Writing login credentials"); - GpodnetPreferences.setUsername(username); - GpodnetPreferences.setPassword(password); - GpodnetPreferences.setDeviceID(selectedDevice.getId()); - } - - private void advance() { - if (currentStep < STEP_FINISH) { - - View view = views[currentStep + 1]; - if (currentStep == STEP_DEFAULT) { - 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 { - finish(); - } - } - - 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/adapter/ChaptersListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java index 4fa8acc43..d4b32ee06 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java @@ -20,9 +20,9 @@ import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.EmbeddedChapterImage; 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.playback.Playable; -import de.danoeh.antennapod.view.CircularProgressBar; +import de.danoeh.antennapod.ui.common.CircularProgressBar; public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapter.ChapterHolder> { private Playable media; @@ -42,7 +42,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte hasImages = false; if (media.getChapters() != null) { for (Chapter chapter : media.getChapters()) { - if (!ignoreChapter(chapter) && !TextUtils.isEmpty(chapter.getImageUrl())) { + if (!TextUtils.isEmpty(chapter.getImageUrl())) { hasImages = true; } } @@ -125,14 +125,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte if (media == null || media.getChapters() == null) { return 0; } - // ignore invalid chapters - int counter = 0; - for (Chapter chapter : media.getChapters()) { - if (!ignoreChapter(chapter)) { - counter++; - } - } - return counter; + return media.getChapters().size(); } static class ChapterHolder extends RecyclerView.ViewHolder { @@ -171,22 +164,8 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte notifyItemChanged(currentChapterIndex, "foo"); } - private boolean ignoreChapter(Chapter c) { - return media.getDuration() > 0 && media.getDuration() < c.getStart(); - } - public Chapter getItem(int position) { - int i = 0; - for (Chapter chapter : media.getChapters()) { - if (!ignoreChapter(chapter)) { - if (i == position) { - return chapter; - } else { - i++; - } - } - } - return null; + return media.getChapters().get(position); } public interface Callback { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index 0c4aaf6ed..811e1e31b 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -20,7 +20,7 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.ui.common.ThemeUtils; import de.danoeh.antennapod.view.viewholder.DownloadItemViewHolder; /** @@ -68,16 +68,14 @@ public class DownloadLogAdapter extends BaseAdapter { holder.icon.setContentDescription(context.getString(R.string.download_successful)); holder.secondaryActionButton.setVisibility(View.INVISIBLE); holder.reason.setVisibility(View.GONE); + holder.tapForDetails.setVisibility(View.GONE); } else { holder.icon.setTextColor(ContextCompat.getColor(context, R.color.download_failed_red)); holder.icon.setText("{fa-times-circle}"); holder.icon.setContentDescription(context.getString(R.string.error_label)); - String reasonText = status.getReason().getErrorString(context); - if (status.getReasonDetailed() != null) { - reasonText += ": " + status.getReasonDetailed(); - } - holder.reason.setText(reasonText); + holder.reason.setText(status.getReason().getErrorString(context)); holder.reason.setVisibility(View.VISIBLE); + holder.tapForDetails.setVisibility(View.VISIBLE); if (newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) { holder.secondaryActionButton.setVisibility(View.INVISIBLE); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java index 268a21409..9363edc9f 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java @@ -15,8 +15,8 @@ import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.service.download.Downloader; -import de.danoeh.antennapod.core.util.ThemeUtils; -import de.danoeh.antennapod.view.CircularProgressBar; +import de.danoeh.antennapod.ui.common.ThemeUtils; +import de.danoeh.antennapod.ui.common.CircularProgressBar; public class DownloadlistAdapter extends BaseAdapter { @@ -68,8 +68,8 @@ public class DownloadlistAdapter extends BaseAdapter { holder.secondaryActionButton.setContentDescription(context.getString(R.string.cancel_download_label)); holder.secondaryActionButton.setTag(downloader); holder.secondaryActionButton.setOnClickListener(butSecondaryListener); - holder.secondaryActionProgress.setPercentage(0, request); + boolean percentageWasSet = false; String status = ""; if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { status += context.getString(R.string.download_type_feed); @@ -85,8 +85,12 @@ public class DownloadlistAdapter extends BaseAdapter { status += " / " + Formatter.formatShortFileSize(context, request.getSize()); holder.secondaryActionProgress.setPercentage( 0.01f * Math.max(1, request.getProgressPercent()), request); + percentageWasSet = true; } } + if (!percentageWasSet) { + holder.secondaryActionProgress.setPercentage(0, request); + } holder.status.setText(status); return convertView; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java index 8cb0fd30a..50b924e49 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java @@ -20,6 +20,7 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.DateUtils; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; +import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; import de.danoeh.antennapod.dialog.StreamingConfirmationDialog; import java.util.List; @@ -59,7 +60,7 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { holder.title.setText(item.getTitle()); holder.pubDate.setText(DateUtils.formatAbbrev(getContext(), item.getPubDate())); if (item.getDescription() != null) { - String description = item.getDescription() + String description = HtmlToPlainText.getPlainText(item.getDescription()) .replaceAll("\n", " ") .replaceAll("\\s+", " ") .trim(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java index 2e5ba31c9..dbb9ce0d0 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java @@ -10,7 +10,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.fragment.FeedItemlistFragment; -import de.danoeh.antennapod.view.SquareImageView; +import de.danoeh.antennapod.ui.common.SquareImageView; import java.lang.ref.WeakReference; import java.util.ArrayList; 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 5533197b9..8bfcf66cc 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -24,7 +24,6 @@ import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.NavDrawerData; -import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.fragment.AddFeedFragment; import de.danoeh.antennapod.fragment.DownloadsFragment; import de.danoeh.antennapod.fragment.EpisodesFragment; @@ -32,6 +31,7 @@ import de.danoeh.antennapod.fragment.NavDrawerFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; +import de.danoeh.antennapod.ui.common.ThemeUtils; import org.apache.commons.lang3.ArrayUtils; import java.lang.ref.WeakReference; @@ -76,7 +76,7 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> } public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)) { + if (UserPreferences.PREF_HIDDEN_DRAWER_ITEMS.equals(key)) { loadItems(); } } @@ -314,7 +314,7 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> } Glide.with(context) - .load(feed.getImageLocation()) + .load(feed.getImageUrl()) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java index 72482b06d..23b5cfdce 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java @@ -66,7 +66,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle StatisticsHolder holder = (StatisticsHolder) h; StatisticsItem statsItem = statisticsData.get(position - 1); Glide.with(context) - .load(statsItem.feed.getImageLocation()) + .load(statsItem.feed.getImageUrl()) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java index 5c0ecfa3c..1d85cdaff 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java @@ -22,8 +22,8 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.LocalFeedUpdater; import de.danoeh.antennapod.core.storage.NavDrawerData; -import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.fragment.FeedItemlistFragment; +import de.danoeh.antennapod.ui.common.ThemeUtils; import jp.shts.android.library.TriangleLabelView; /** @@ -112,7 +112,7 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI boolean textAndImageCombined = feed.isLocalFeed() && LocalFeedUpdater.getDefaultIconUrl(convertView.getContext()).equals(feed.getImageUrl()); new CoverLoader(mainActivityRef.get()) - .withUri(feed.getImageLocation()) + .withUri(feed.getImageUrl()) .withPlaceholderView(holder.feedTitle, textAndImageCombined) .withCoverView(holder.imageView) .load(); @@ -123,7 +123,6 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI .withCoverView(holder.imageView) .load(); } - return convertView; } diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index 7a5cf431f..a45eb5199 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -2,21 +2,19 @@ package de.danoeh.antennapod.config; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.storage.APDownloadAlgorithm; /** * Configures the ClientConfig class of the core package. */ class ClientConfigurator { - private ClientConfigurator(){} + private ClientConfigurator() { + } static { ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME; ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl(); ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); - ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); - ClientConfig.automaticDownloadAlgorithm = new APDownloadAlgorithm(); ClientConfig.castCallbacks = new CastCallbackImpl(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java index 55bf05e43..f782308d1 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java @@ -30,8 +30,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { @Override public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request) { final Intent activityIntent = new Intent(context.getApplicationContext(), DownloadAuthenticationActivity.class); + activityIntent.setAction("request" + request.getFeedfileId()); activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request); - activityIntent.putExtra(DownloadAuthenticationActivity.ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, true); return PendingIntent.getActivity(context.getApplicationContext(), R.id.pending_intent_download_service_auth, activityIntent, PendingIntent.FLAG_ONE_SHOT); } diff --git a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java deleted file mode 100644 index f70cb26ff..000000000 --- a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.danoeh.antennapod.config; - -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.activity.VideoplayerActivity; -import de.danoeh.antennapod.core.PlaybackServiceCallbacks; -import de.danoeh.antennapod.core.feed.MediaType; - -public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks { - @Override - public Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback) { - if (mediaType == MediaType.AUDIO || remotePlayback) { - return new Intent(context, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true); - } else { - Intent i = new Intent(context, VideoplayerActivity.class); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - } - return i; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java index 39d321f18..d7b2dc536 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java @@ -1,37 +1,50 @@ package de.danoeh.antennapod.dialog; import android.content.Context; -import android.view.View; -import android.widget.EditText; +import android.text.method.HideReturnsTransformationMethod; +import android.text.method.PasswordTransformationMethod; +import android.view.LayoutInflater; import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.databinding.AuthenticationDialogBinding; /** * Displays a dialog with a username and password text field and an optional checkbox to save username and preferences. */ public abstract class AuthenticationDialog extends AlertDialog.Builder { + boolean passwordHidden = true; public AuthenticationDialog(Context context, int titleRes, boolean enableUsernameField, String usernameInitialValue, String passwordInitialValue) { super(context); setTitle(titleRes); - View rootView = View.inflate(context, R.layout.authentication_dialog, null); - setView(rootView); + AuthenticationDialogBinding viewBinding = AuthenticationDialogBinding.inflate(LayoutInflater.from(context)); + setView(viewBinding.getRoot()); - final EditText etxtUsername = rootView.findViewById(R.id.etxtUsername); - final EditText etxtPassword = rootView.findViewById(R.id.etxtPassword); - - etxtUsername.setEnabled(enableUsernameField); + viewBinding.usernameEditText.setEnabled(enableUsernameField); if (usernameInitialValue != null) { - etxtUsername.setText(usernameInitialValue); + viewBinding.usernameEditText.setText(usernameInitialValue); } if (passwordInitialValue != null) { - etxtPassword.setText(passwordInitialValue); + viewBinding.passwordEditText.setText(passwordInitialValue); } + viewBinding.showPasswordButton.setOnClickListener(v -> { + if (passwordHidden) { + viewBinding.passwordEditText.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); + viewBinding.showPasswordButton.setAlpha(1.0f); + } else { + viewBinding.passwordEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); + viewBinding.showPasswordButton.setAlpha(0.6f); + } + passwordHidden = !passwordHidden; + }); + setOnCancelListener(dialog -> onCancelled()); + setOnDismissListener(dialog -> onCancelled()); setNegativeButton(R.string.cancel_label, null); setPositiveButton(R.string.confirm_label, (dialog, which) - -> onConfirmed(etxtUsername.getText().toString(), etxtPassword.getText().toString())); + -> onConfirmed(viewBinding.usernameEditText.getText().toString(), + viewBinding.passwordEditText.getText().toString())); } protected void onCancelled() { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java index efaff1da3..e1e8f1c2e 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -28,7 +28,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.FeedItemPermutors; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.SortOrder; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.ui.common.ThemeUtils; import java.util.ArrayList; import java.util.Arrays; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java index 80df87891..779248e2f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java @@ -16,7 +16,7 @@ import java.util.Set; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItemFilter; import de.danoeh.antennapod.core.feed.FeedItemFilterGroup; -import de.danoeh.antennapod.view.RecursiveRadioGroup; +import de.danoeh.antennapod.ui.common.RecursiveRadioGroup; public abstract class FilterDialog { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java deleted file mode 100644 index 8119dffcb..000000000 --- a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.danoeh.antennapod.dialog; - -import android.content.Context; -import androidx.appcompat.app.AlertDialog; -import android.text.Editable; -import android.text.InputType; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.LinearLayout; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; - -/** - * Creates a dialog that lets the user change the hostname for the gpodder.net service. - */ -public class GpodnetSetHostnameDialog { - - private GpodnetSetHostnameDialog(){} - - private static final String TAG = "GpodnetSetHostnameDialog"; - - public static AlertDialog createDialog(final Context context) { - AlertDialog.Builder dialog = new AlertDialog.Builder(context); - final EditText et = new EditText(context); - et.setText(GpodnetPreferences.getHostname()); - et.setInputType(InputType.TYPE_TEXT_VARIATION_URI); - dialog.setTitle(R.string.pref_gpodnet_sethostname_title) - .setView(setupContentView(context, et)) - .setPositiveButton(R.string.confirm_label, (dialog1, which) -> { - final Editable e = et.getText(); - if (e != null) { - GpodnetPreferences.setHostname(e.toString()); - } - dialog1.dismiss(); - }) - .setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel()) - .setNeutralButton(R.string.pref_gpodnet_sethostname_use_default_host, (dialog1, which) -> { - GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST); - dialog1.dismiss(); - }) - .setCancelable(true); - return dialog.show(); - } - - private static View setupContentView(Context context, EditText et) { - LinearLayout ll = new LinearLayout(context); - ll.addView(et); - LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) et.getLayoutParams(); - if (params != null) { - params.setMargins(8, 8, 8, 8); - params.width = ViewGroup.LayoutParams.MATCH_PARENT; - params.height = ViewGroup.LayoutParams.MATCH_PARENT; - } - return ll; - } -} 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 98f6cc117..195891499 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -41,7 +41,7 @@ public class PlaybackControlsDialog extends DialogFragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public void setupGUI() { + public void loadMediaInfo() { setupUi(); setupAudioTracks(); } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java index f1a41d753..fa5c2d8c3 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -47,12 +47,12 @@ public class SleepTimerDialog extends DialogFragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public void setupGUI() { + public void onSleepTimerUpdate() { updateTime(); } @Override - public void onSleepTimerUpdate() { + public void loadMediaInfo() { updateTime(); } }; 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 8a87fef25..29172bb5e 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java @@ -20,7 +20,7 @@ import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.SubscriptionsFilter; import de.danoeh.antennapod.core.feed.SubscriptionsFilterGroup; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.view.RecursiveRadioGroup; +import de.danoeh.antennapod.ui.common.RecursiveRadioGroup; public class SubscriptionsFilterDialog { public static void showDialog(Context context) { 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 1fc7a77b2..65e7c4424 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -56,12 +56,12 @@ public class VariableSpeedDialog extends DialogFragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public void setupGUI() { + public void onPlaybackSpeedChange() { updateSpeed(); } @Override - public void onPlaybackSpeedChange() { + public void loadMediaInfo() { updateSpeed(); } }; diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java index 53237579f..6de2186e0 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java @@ -18,7 +18,7 @@ public class GpodnetPodcastSearcher implements PodcastSearcher { return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> { try { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHostname()); + GpodnetPreferences.getHosturl()); List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0); List<PodcastSearchResult> results = new ArrayList<>(); for (GpodnetPodcast podcast : gpodnetPodcasts) { diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java index ad574cab6..16c5548be 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java @@ -15,11 +15,11 @@ public class PodcastSearcherRegistry { public static List<SearcherInfo> getSearchProviders() { if (searchProviders == null) { searchProviders = new ArrayList<>(); - searchProviders.add(new SearcherInfo(new CombinedSearcher(), 1.f)); - searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.f)); - searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.f)); + searchProviders.add(new SearcherInfo(new CombinedSearcher(), 1.0f)); searchProviders.add(new SearcherInfo(new GpodnetPodcastSearcher(), 0.0f)); - searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 0.0f)); + searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.0f)); + searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.0f)); + searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 1.0f)); } return searchProviders; } 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 06a974dfd..08e23fc7f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -50,9 +50,11 @@ public class AddFeedFragment extends Fragment { public static final String TAG = "AddFeedFragment"; private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; + private static final String KEY_UP_ARROW = "up_arrow"; private AddfeedBinding viewBinding; private MainActivity activity; + private boolean displayUpArrow; @Override @Nullable @@ -64,7 +66,11 @@ public class AddFeedFragment extends Fragment { activity = (MainActivity) getActivity(); Toolbar toolbar = viewBinding.toolbar; - ((MainActivity) getActivity()).setupToolbarToggle(toolbar); + displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0; + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); + } + ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); viewBinding.searchItunesButton.setOnClickListener(v -> activity.loadChildFragment(OnlineSearchFragment.newInstance(ItunesPodcastSearcher.class))); @@ -119,6 +125,12 @@ public class AddFeedFragment extends Fragment { return viewBinding.getRoot(); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_UP_ARROW, displayUpArrow); + super.onSaveInstanceState(outState); + } + private void showAddViaUrlDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(R.string.add_podcast_by_url); @@ -196,7 +208,11 @@ public class AddFeedFragment extends Fragment { if (documentFile == null) { throw new IllegalArgumentException("Unable to retrieve document tree"); } - Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, documentFile.getName()); + String title = documentFile.getName(); + if (title == null) { + title = getString(R.string.local_folder); + } + Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, title); dirFeed.setItems(Collections.emptyList()); dirFeed.setSortOrder(SortOrder.EPISODE_TITLE_A_Z); Feed fromDatabase = DBTasks.updateFeed(getContext(), dirFeed, false); 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 4423a2ebe..612959c04 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -104,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 82e2b3a6a..51f264e56 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,7 +15,9 @@ 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; @@ -29,11 +29,14 @@ 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.feed.Chapter; +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; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; @@ -45,7 +48,8 @@ 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.PlaybackSpeedIndicatorView; +import de.danoeh.antennapod.view.ChapterSeekBar; +import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -62,14 +66,13 @@ import java.util.List; * Shows the audio player. */ public class AudioPlayerFragment extends Fragment implements - SeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener { + ChapterSeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener { public static final String TAG = "AudioPlayerFragment"; private static final int POS_COVER = 0; 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; @@ -77,7 +80,7 @@ public class AudioPlayerFragment extends Fragment implements private ViewPager2 pager; private TextView txtvPosition; private TextView txtvLength; - private SeekBar sbPosition; + private ChapterSeekBar sbPosition; private ImageButton butRev; private TextView txtvRev; private ImageButton butPlay; @@ -86,6 +89,8 @@ 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; @@ -122,6 +127,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(); @@ -168,12 +175,33 @@ public class AudioPlayerFragment extends Fragment implements return root; } - public void setHasChapters(boolean hasChapters) { + private void setHasChapters(boolean hasChapters) { this.hasChapters = hasChapters; tabLayoutMediator.detach(); tabLayoutMediator.attach(); } + private void setChapterDividers(Playable media) { + + if (media == null) { + return; + } + + float[] dividerPos = null; + + if (hasChapters) { + List<Chapter> chapters = media.getChapters(); + dividerPos = new float[chapters.size()]; + float duration = media.getDuration(); + + for (int i = 0; i < chapters.size(); i++) { + dividerPos[i] = chapters.get(i).getStart() / duration; + } + } + + sbPosition.setDividerPos(dividerPos); + } + public View getExternalPlayerHolder() { return getView().findViewById(R.id.playerFragment); } @@ -211,16 +239,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())); }); } @@ -285,26 +322,21 @@ public class AudioPlayerFragment extends Fragment implements disposable = Maybe.create(emitter -> { Playable media = controller.getMedia(); if (media != null) { + ChapterUtils.loadChapters(media, getContext()); emitter.onSuccess(media); } else { emitter.onComplete(); } }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(media -> updateUi((Playable) media), - error -> Log.e(TAG, Log.getStackTraceString(error)), - () -> updateUi(null)); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(media -> updateUi((Playable) media), + error -> Log.e(TAG, Log.getStackTraceString(error)), + () -> updateUi(null)); } private PlaybackController newPlaybackController() { return new PlaybackController(getActivity()) { - - @Override - public void setupGUI() { - AudioPlayerFragment.this.loadMediaInfo(); - } - @Override public void onBufferStart() { progressIndicator.setVisibility(View.VISIBLE); @@ -352,9 +384,8 @@ public class AudioPlayerFragment extends Fragment implements } @Override - public boolean loadMediaInfo() { + public void loadMediaInfo() { AudioPlayerFragment.this.loadMediaInfo(); - return true; } @Override @@ -383,8 +414,15 @@ public class AudioPlayerFragment extends Fragment implements if (controller == null) { return; } + + if (media != null && media.getChapters() != null) { + setHasChapters(media.getChapters().size() > 0); + } else { + setHasChapters(false); + } updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration())); updatePlaybackSpeedButton(media); + setChapterDividers(media); setupOptionsMenu(media); } @@ -433,6 +471,7 @@ public class AudioPlayerFragment extends Fragment implements return; } txtvPosition.setText(Converter.getDurationStringLong(currentPosition)); + showTimeLeft = UserPreferences.shouldShowRemainingTime(); if (showTimeLeft) { txtvLength.setText("-" + Converter.getDurationStringLong(remainingTime)); } else { @@ -454,22 +493,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 @@ -478,6 +517,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 d781d0774..acda462bd 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) { @@ -71,13 +72,7 @@ public class ChaptersFragment extends Fragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public boolean loadMediaInfo() { - ChaptersFragment.this.loadMediaInfo(); - return true; - } - - @Override - public void setupGUI() { + public void loadMediaInfo() { ChaptersFragment.this.loadMediaInfo(); } @@ -123,7 +118,7 @@ public class ChaptersFragment extends Fragment { disposable = Maybe.create(emitter -> { Playable media = controller.getMedia(); if (media != null) { - media.loadChapterMarks(getContext()); + ChapterUtils.loadChapters(media, getContext()); emitter.onSuccess(media); } else { emitter.onComplete(); @@ -142,7 +137,6 @@ public class ChaptersFragment extends Fragment { return; } adapter.setMedia(media); - ((AudioPlayerFragment) getParentFragment()).setHasChapters(adapter.getItemCount() > 0); int positionOfCurrentChapter = getCurrentChapter(media); updateChapterSelection(positionOfCurrentChapter); } 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..d8c382cb2 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()); @@ -111,13 +117,7 @@ public class CoverFragment extends Fragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public boolean loadMediaInfo() { - CoverFragment.this.loadMediaInfo(); - return true; - } - - @Override - public void setupGUI() { + public void loadMediaInfo() { CoverFragment.this.loadMediaInfo(); } }; @@ -151,23 +151,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)))); + .load(media.getImageLocation()) + .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 +210,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/DownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java index ffb3e71fa..5c83cee57 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java @@ -28,16 +28,17 @@ public class DownloadsFragment extends PagedToolbarFragment { public static final String TAG = "DownloadsFragment"; public static final String ARG_SELECTED_TAB = "selected_tab"; + private static final String PREF_LAST_TAB_POSITION = "tab_position"; + private static final String KEY_UP_ARROW = "up_arrow"; public static final int POS_RUNNING = 0; private static final int POS_COMPLETED = 1; public static final int POS_LOG = 2; private static final int TOTAL_COUNT = 3; - private static final String PREF_LAST_TAB_POSITION = "tab_position"; - private ViewPager2 viewPager; private TabLayout tabLayout; + private boolean displayUpArrow; @Override public View onCreateView(@NonNull LayoutInflater inflater, @@ -48,7 +49,11 @@ public class DownloadsFragment extends PagedToolbarFragment { Toolbar toolbar = root.findViewById(R.id.toolbar); toolbar.setTitle(R.string.downloads_label); toolbar.inflateMenu(R.menu.downloads); - ((MainActivity) getActivity()).setupToolbarToggle(toolbar); + displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0; + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); + } + ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); viewPager = root.findViewById(R.id.viewpager); viewPager.setAdapter(new DownloadsPagerAdapter(this)); @@ -82,6 +87,12 @@ public class DownloadsFragment extends PagedToolbarFragment { } @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_UP_ARROW, displayUpArrow); + super.onSaveInstanceState(outState); + } + + @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (getArguments() != null) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java index eff23f7a3..1ca5d524b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java @@ -24,6 +24,7 @@ public class EpisodesFragment extends PagedToolbarFragment { public static final String TAG = "EpisodesFragment"; private static final String PREF_LAST_TAB_POSITION = "tab_position"; + private static final String KEY_UP_ARROW = "up_arrow"; private static final int POS_NEW_EPISODES = 0; private static final int POS_ALL_EPISODES = 1; @@ -31,6 +32,7 @@ public class EpisodesFragment extends PagedToolbarFragment { private static final int TOTAL_COUNT = 3; private TabLayout tabLayout; + private boolean displayUpArrow; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -44,7 +46,11 @@ public class EpisodesFragment extends PagedToolbarFragment { toolbar.setTitle(R.string.episodes_label); toolbar.inflateMenu(R.menu.episodes); MenuItemUtils.setupSearchItem(toolbar.getMenu(), (MainActivity) getActivity(), 0, ""); - ((MainActivity) getActivity()).setupToolbarToggle(toolbar); + displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0; + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); + } + ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); ViewPager2 viewPager = rootView.findViewById(R.id.viewpager); viewPager.setAdapter(new EpisodesPagerAdapter(this)); @@ -88,6 +94,12 @@ public class EpisodesFragment extends PagedToolbarFragment { editor.apply(); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_UP_ARROW, displayUpArrow); + super.onSaveInstanceState(outState); + } + static class EpisodesPagerAdapter extends FragmentStateAdapter { EpisodesPagerAdapter(@NonNull Fragment fragment) { 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..d77935910 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -108,12 +108,7 @@ public class ExternalPlayerFragment extends Fragment { } @Override - public boolean loadMediaInfo() { - return ExternalPlayerFragment.this.loadMediaInfo(); - } - - @Override - public void setupGUI() { + public void loadMediaInfo() { ExternalPlayerFragment.this.loadMediaInfo(); } @@ -170,11 +165,11 @@ public class ExternalPlayerFragment extends Fragment { } } - private boolean loadMediaInfo() { + private void loadMediaInfo() { Log.d(TAG, "Loading media info"); if (controller == null) { Log.w(TAG, "loadMediaInfo was called while PlaybackController was null!"); - return false; + return; } if (disposable != null) { @@ -186,7 +181,6 @@ public class ExternalPlayerFragment extends Fragment { .subscribe(this::updateUi, error -> Log.e(TAG, Log.getStackTraceString(error)), () -> ((MainActivity) getActivity()).setPlayerVisible(false)); - return true; } private void updateUi(Playable media) { @@ -198,14 +192,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()) + .load(ImageResourceUtils.getEpisodeListImageLocation(media)) + .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..25ab925eb 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(); @@ -201,7 +203,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic Log.d(TAG, "Author is " + feed.getAuthor()); Log.d(TAG, "URL is " + feed.getDownload_url()); Glide.with(getContext()) - .load(feed.getImageLocation()) + .load(feed.getImageUrl()) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) @@ -210,7 +212,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic .dontAnimate()) .into(imgvCover); Glide.with(getContext()) - .load(feed.getImageLocation()) + .load(feed.getImageUrl()) .apply(new RequestOptions() .placeholder(R.color.image_readability_tint) .error(R.color.image_readability_tint) @@ -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 8e14214d2..acb929dd2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -16,7 +16,6 @@ import android.widget.AdapterView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; -import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -57,8 +56,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException; 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; @@ -89,6 +87,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem Toolbar.OnMenuItemClickListener { private static final String TAG = "ItemlistFragment"; private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; + private static final String KEY_UP_ARROW = "up_arrow"; private FeedItemListAdapter adapter; private MoreContentListFooterUtil nextPageLoader; @@ -106,6 +105,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem private View header; private Toolbar toolbar; private ToolbarIconTintManager iconTintManager; + private boolean displayUpArrow; private long feedID; private Feed feed; @@ -146,7 +146,11 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem toolbar = root.findViewById(R.id.toolbar); toolbar.inflateMenu(R.menu.feedlist); toolbar.setOnMenuItemClickListener(this); - ((MainActivity) getActivity()).setupToolbarToggle(toolbar); + displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0; + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); + } + ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); refreshToolbarState(); recyclerView = root.findViewById(R.id.recyclerView); @@ -231,6 +235,12 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem adapter = null; } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_UP_ARROW, displayUpArrow); + super.onSaveInstanceState(outState); + } + private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() { @Override public boolean isRefreshing() { @@ -451,10 +461,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem if (feed.getItemFilter() != null) { FeedItemFilter filter = feed.getItemFilter(); if (filter.getValues().length > 0) { - if (feed.hasLastUpdateFailed()) { - RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) txtvInformation.getLayoutParams(); - p.addRule(RelativeLayout.BELOW, R.id.txtvFailure); - } txtvInformation.setText("{md-info-outline} " + this.getString(R.string.filtered_label)); Iconify.addIcons(txtvInformation); txtvInformation.setOnClickListener((l) -> { @@ -514,7 +520,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem private void loadFeedImage() { Glide.with(getActivity()) - .load(feed.getImageLocation()) + .load(feed.getImageUrl()) .apply(new RequestOptions() .placeholder(R.color.image_readability_tint) .error(R.color.image_readability_tint) @@ -524,7 +530,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem .into(imgvBackground); Glide.with(getActivity()) - .load(feed.getImageLocation()) + .load(feed.getImageUrl()) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) @@ -542,27 +548,32 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem disposable = Observable.fromCallable(this::loadData) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - feed = result.orElse(null); - refreshHeaderView(); - displayList(); - }, error -> Log.e(TAG, Log.getStackTraceString(error))); + .subscribe( + result -> { + feed = result; + refreshHeaderView(); + displayList(); + }, error -> { + feed = null; + refreshHeaderView(); + displayList(); + Log.e(TAG, Log.getStackTraceString(error)); + }); } - @NonNull - private Optional<Feed> loadData() { - Feed feed = DBReader.getFeed(feedID); - if (feed != null && feed.getItemFilter() != null) { - DBReader.loadAdditionalFeedItemListData(feed.getItems()); - FeedItemFilter filter = feed.getItemFilter(); - feed.setItems(filter.filter(feed.getItems())); + @Nullable + private Feed loadData() { + Feed feed = DBReader.getFeed(feedID, true); + if (feed == null) { + return null; } - if (feed != null && feed.getSortOrder() != null) { + DBReader.loadAdditionalFeedItemListData(feed.getItems()); + if (feed.getSortOrder() != null) { List<FeedItem> feedItems = feed.getItems(); FeedItemPermutors.getPermutor(feed.getSortOrder()).reorder(feedItems); feed.setItems(feedItems); } - return Optional.ofNullable(feed); + return feed; } private static class FeedItemListAdapter extends EpisodeItemListAdapter { 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 1253a8ad2..c000107a7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -164,6 +164,7 @@ public class FeedSettingsFragment extends Fragment { setupEpisodeFilterPreference(); setupPlaybackSpeedPreference(); setupFeedAutoSkipPreference(); + setupEpisodeNotificationPreference(); setupTags(); updateAutoDeleteSummary(); @@ -198,7 +199,7 @@ public class FeedSettingsFragment extends Fragment { protected void onConfirmed(int skipIntro, int skipEnding) { feedPreferences.setFeedSkipIntro(skipIntro); feedPreferences.setFeedSkipEnding(skipEnding); - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); EventBus.getDefault().post( new SkipIntroEndingChangedEvent(feedPreferences.getFeedSkipIntro(), feedPreferences.getFeedSkipEnding(), @@ -226,7 +227,7 @@ public class FeedSettingsFragment extends Fragment { feedPlaybackSpeedPreference.setEntries(entries); feedPlaybackSpeedPreference.setOnPreferenceChangeListener((preference, newValue) -> { feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) newValue)); - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); updatePlaybackSpeedPreference(); EventBus.getDefault().post( new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId())); @@ -240,7 +241,7 @@ public class FeedSettingsFragment extends Fragment { @Override protected void onConfirmed(FeedFilter filter) { feedPreferences.setFilter(filter); - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); } }.show(); return false; @@ -256,7 +257,7 @@ public class FeedSettingsFragment extends Fragment { protected void onConfirmed(String username, String password) { feedPreferences.setUsername(username); feedPreferences.setPassword(password); - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); } }.show(); return false; @@ -276,7 +277,7 @@ public class FeedSettingsFragment extends Fragment { feedPreferences.setAutoDeleteAction(FeedPreferences.AutoDeleteAction.NO); break; } - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); updateAutoDeleteSummary(); return false; }); @@ -322,7 +323,7 @@ public class FeedSettingsFragment extends Fragment { feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.HEAVY_REDUCTION); break; } - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); updateVolumeReductionValue(); EventBus.getDefault().post( new VolumeAdaptionChangedEvent(feedPreferences.getVolumeAdaptionSetting(), feed.getId())); @@ -353,7 +354,7 @@ public class FeedSettingsFragment extends Fragment { pref.setOnPreferenceChangeListener((preference, newValue) -> { boolean checked = newValue == Boolean.TRUE; feedPreferences.setKeepUpdated(checked); - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); pref.setChecked(checked); return false; }); @@ -384,7 +385,7 @@ public class FeedSettingsFragment extends Fragment { boolean checked = newValue == Boolean.TRUE; feedPreferences.setAutoDownload(checked); - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); updateAutoDownloadEnabled(); ApplyToEpisodesDialog dialog = new ApplyToEpisodesDialog(getActivity(), checked); dialog.createNewDialog().show(); @@ -412,7 +413,7 @@ public class FeedSettingsFragment extends Fragment { feedPreferences.getTags().clear(); feedPreferences.getTags().addAll(new HashSet<>(Arrays.asList( foldersString.split(FeedPreferences.TAG_SEPARATOR)))); - feed.savePreferences(); + DBWriter.setFeedPreferences(feedPreferences); }) .setNegativeButton(R.string.cancel_label, null) .show(); @@ -420,6 +421,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); + DBWriter.setFeedPreferences(feedPreferences); + 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 18a61f1e6..2e13bbd79 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -10,6 +10,9 @@ import android.view.View; import android.view.ViewGroup; import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.storage.DBReader; +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; @@ -82,7 +85,15 @@ public class ItemDescriptionFragment extends Fragment { webViewLoader.dispose(); } webViewLoader = Maybe.<String>create(emitter -> { - Timeline timeline = new Timeline(getActivity(), controller.getMedia()); + Playable media = controller.getMedia(); + if (media instanceof FeedMedia) { + FeedMedia feedMedia = ((FeedMedia) media); + if (feedMedia.getItem() == null) { + feedMedia.setItem(DBReader.getFeedItem(feedMedia.getItemId())); + } + DBReader.loadDescriptionOfFeedItem(feedMedia.getItem()); + } + Timeline timeline = new Timeline(getActivity(), media.getDescription(), media.getDuration()); emitter.onSuccess(timeline.processShownotes()); }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -140,14 +151,8 @@ public class ItemDescriptionFragment extends Fragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public boolean loadMediaInfo() { + public void loadMediaInfo() { load(); - return true; - } - - @Override - public void setupGUI() { - ItemDescriptionFragment.this.load(); } }; controller.init(); 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 07f59bb42..224210d63 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -57,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; @@ -238,7 +238,12 @@ public class ItemFragment extends Fragment { public void onStart() { super.onStart(); EventBus.getDefault().register(this); - controller = new PlaybackController(getActivity()); + controller = new PlaybackController(getActivity()) { + @Override + public void loadMediaInfo() { + // Do nothing + } + }; controller.init(); } @@ -291,14 +296,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()) + .load(item.getImageLocation()) + .error(Glide.with(getActivity()) + .load(ImageResourceUtils.getFallbackImageLocation(item)) + .apply(options)) + .apply(options) .into(imgvCover); updateButtons(); } @@ -429,7 +439,9 @@ public class ItemFragment extends Fragment { FeedItem feedItem = DBReader.getFeedItem(itemId); Context context = getContext(); if (feedItem != null && context != null) { - Timeline t = new Timeline(context, feedItem); + int duration = feedItem.getMedia() != null ? feedItem.getMedia().getDuration() : Integer.MAX_VALUE; + DBReader.loadDescriptionOfFeedItem(feedItem); + Timeline t = new Timeline(context, feedItem.getDescription(), duration); webviewData = t.processShownotes(); } return feedItem; 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 3d82bf7a1..e8c04336f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -424,7 +424,7 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS flatItemList = result.second; updateSelection(); // Selected item might be a feed navAdapter.notifyDataSetChanged(); - progressBar.setVisibility(View.GONE); + progressBar.setVisibility(View.GONE); // Stays hidden once there is something in the list }, error -> { Log.e(TAG, Log.getStackTraceString(error)); progressBar.setVisibility(View.GONE); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index 973fcb978..e97b7cd7f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -41,6 +41,7 @@ import java.util.List; public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuItemClickListener { public static final String TAG = "PlaybackHistoryFragment"; + private static final String KEY_UP_ARROW = "up_arrow"; private List<FeedItem> playbackHistory; private PlaybackHistoryListAdapter adapter; @@ -49,6 +50,7 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI private EmptyViewHandler emptyView; private ProgressBar progressBar; private Toolbar toolbar; + private boolean displayUpArrow; @Override public void onCreate(Bundle savedInstanceState) { @@ -63,7 +65,11 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI toolbar = root.findViewById(R.id.toolbar); toolbar.setTitle(R.string.playback_history_label); toolbar.setOnMenuItemClickListener(this); - ((MainActivity) getActivity()).setupToolbarToggle(toolbar); + displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0; + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); + } + ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); toolbar.inflateMenu(R.menu.playback_history); refreshToolbarState(); @@ -98,6 +104,12 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI } } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_UP_ARROW, displayUpArrow); + super.onSaveInstanceState(outState); + } + @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); 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 983bf4de1..2850acc15 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -12,6 +12,7 @@ import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; @@ -67,6 +68,7 @@ import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REM */ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickListener { public static final String TAG = "QueueFragment"; + private static final String KEY_UP_ARROW = "up_arrow"; private TextView infoBar; private EpisodeItemListRecyclerView recyclerView; @@ -74,6 +76,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi private EmptyViewHandler emptyView; private ProgressBar progLoading; private Toolbar toolbar; + private boolean displayUpArrow; private List<FeedItem> queue; @@ -420,7 +423,11 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi View root = inflater.inflate(R.layout.queue_fragment, container, false); toolbar = root.findViewById(R.id.toolbar); toolbar.setOnMenuItemClickListener(this); - ((MainActivity) getActivity()).setupToolbarToggle(toolbar); + displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0; + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); + } + ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); toolbar.inflateMenu(R.menu.queue); MenuItemUtils.setupSearchItem(toolbar.getMenu(), (MainActivity) getActivity(), 0, ""); refreshToolbarState(); @@ -530,6 +537,12 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi return root; } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_UP_ARROW, displayUpArrow); + super.onSaveInstanceState(outState); + } + private void onFragmentLoaded(final boolean restoreScrollPosition) { if (queue != null && queue.size() > 0) { if (recyclerAdapter == null) { 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 bb00d88e1..3c529d941 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -8,6 +8,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.widget.ProgressBar; +import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; @@ -67,6 +68,8 @@ 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 String KEY_UP_ARROW = "up_arrow"; + private static final int MIN_NUM_COLUMNS = 2; private static final int[] COLUMN_CHECKBOX_IDS = { R.id.subscription_num_columns_2, @@ -85,6 +88,7 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem private int mPosition = -1; private boolean isUpdatingFeeds = false; + private boolean displayUpArrow; private Disposable disposable; private SharedPreferences prefs; @@ -103,7 +107,11 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem View root = inflater.inflate(R.layout.fragment_subscriptions, container, false); toolbar = root.findViewById(R.id.toolbar); toolbar.setOnMenuItemClickListener(this); - ((MainActivity) getActivity()).setupToolbarToggle(toolbar); + displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0; + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); + } + ((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow); toolbar.inflateMenu(R.menu.subscriptions); for (int i = 0; i < COLUMN_CHECKBOX_IDS.length; i++) { // Do this in Java to localize numbers @@ -130,6 +138,12 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem return root; } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_UP_ARROW, displayUpArrow); + super.onSaveInstanceState(outState); + } + private void refreshToolbarState() { int columns = prefs.getInt(PREF_NUM_COLUMNS, getDefaultNumOfColumns()); toolbar.getMenu().findItem(COLUMN_CHECKBOX_IDS[columns - MIN_NUM_COLUMNS]).setChecked(true); @@ -218,16 +232,19 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem disposable.dispose(); } emptyView.hide(); - progressBar.setVisibility(View.VISIBLE); disposable = Observable.fromCallable(DBReader::getNavDrawerData) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - navDrawerData = result; - subscriptionAdapter.notifyDataSetChanged(); - emptyView.updateVisibility(); - progressBar.setVisibility(View.GONE); - }, error -> Log.e(TAG, Log.getStackTraceString(error))); + .subscribe( + result -> { + navDrawerData = result; + subscriptionAdapter.notifyDataSetChanged(); + emptyView.updateVisibility(); + progressBar.setVisibility(View.GONE); // Keep hidden to avoid flickering while refreshing + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressBar.setVisibility(View.GONE); + }); if (UserPreferences.getSubscriptionsFilter().isEnabled()) { feedsFilteredMsg.setText("{md-info-outline} " + getString(R.string.subscriptions_are_filtered)); 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 1f5434688..7ee0936d0 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 @@ -1,10 +1,7 @@ package de.danoeh.antennapod.fragment.gpodnet; -import android.content.Context; import android.content.Intent; -import android.os.AsyncTask; import android.os.Bundle; -import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -13,9 +10,7 @@ import android.widget.Button; import android.widget.GridView; import android.widget.ProgressBar; import android.widget.TextView; - -import java.util.List; - +import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; @@ -25,6 +20,12 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.util.List; /** * Displays a list of GPodnetPodcast-Objects in a GridView @@ -36,6 +37,7 @@ public abstract class PodcastListFragment extends Fragment { private ProgressBar progressBar; private TextView txtvError; private Button butRetry; + private Disposable disposable; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -64,60 +66,44 @@ public abstract class PodcastListFragment extends Fragment { protected abstract List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException; final void loadData() { - AsyncTask<Void, Void, List<GpodnetPodcast>> loaderTask = new AsyncTask<Void, Void, List<GpodnetPodcast>>() { - volatile Exception exception = null; - - @Override - protected List<GpodnetPodcast> doInBackground(Void... params) { - try { + if (disposable != null) { + disposable.dispose(); + } + gridView.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + txtvError.setVisibility(View.GONE); + butRetry.setVisibility(View.GONE); + disposable = Observable.fromCallable( + () -> { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHostname()); + GpodnetPreferences.getHosturl()); return loadPodcastData(service); - } catch (GpodnetServiceException e) { - exception = e; - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(List<GpodnetPodcast> gpodnetPodcasts) { - super.onPostExecute(gpodnetPodcasts); - final Context context = getActivity(); - if (context != null && gpodnetPodcasts != null && gpodnetPodcasts.size() > 0) { - PodcastListAdapter listAdapter = new PodcastListAdapter(context, 0, gpodnetPodcasts); - gridView.setAdapter(listAdapter); - listAdapter.notifyDataSetChanged(); - - progressBar.setVisibility(View.GONE); - gridView.setVisibility(View.VISIBLE); - txtvError.setVisibility(View.GONE); - butRetry.setVisibility(View.GONE); - } else if (context != null && gpodnetPodcasts != null) { - gridView.setVisibility(View.GONE); - progressBar.setVisibility(View.GONE); - txtvError.setText(getString(R.string.search_status_no_results)); - txtvError.setVisibility(View.VISIBLE); - butRetry.setVisibility(View.GONE); - } else if (context != null) { - gridView.setVisibility(View.GONE); - progressBar.setVisibility(View.GONE); - txtvError.setText(getString(R.string.error_msg_prefix) + exception.getMessage()); - txtvError.setVisibility(View.VISIBLE); - butRetry.setVisibility(View.VISIBLE); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - gridView.setVisibility(View.GONE); - progressBar.setVisibility(View.VISIBLE); - txtvError.setVisibility(View.GONE); - butRetry.setVisibility(View.GONE); - } - }; - - loaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + podcasts -> { + progressBar.setVisibility(View.GONE); + butRetry.setVisibility(View.GONE); + + if (podcasts.size() > 0) { + PodcastListAdapter listAdapter = new PodcastListAdapter(getContext(), 0, podcasts); + gridView.setAdapter(listAdapter); + listAdapter.notifyDataSetChanged(); + gridView.setVisibility(View.VISIBLE); + txtvError.setVisibility(View.GONE); + } else { + gridView.setVisibility(View.GONE); + txtvError.setText(getString(R.string.search_status_no_results)); + txtvError.setVisibility(View.VISIBLE); + } + }, error -> { + gridView.setVisibility(View.GONE); + progressBar.setVisibility(View.GONE); + txtvError.setText(getString(R.string.error_msg_prefix) + error.getMessage()); + txtvError.setVisibility(View.VISIBLE); + butRetry.setVisibility(View.VISIBLE); + Log.e(TAG, Log.getStackTraceString(error)); + }); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index 2c41ee070..9d0f99aa9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -1,32 +1,34 @@ package de.danoeh.antennapod.fragment.gpodnet; -import android.content.Context; -import android.os.AsyncTask; import android.os.Bundle; +import android.util.Log; import android.view.View; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.fragment.app.ListFragment; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag; - -import java.util.List; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; public class TagListFragment extends ListFragment { private static final int COUNT = 50; + private static final String TAG = "TagListFragment"; + private Disposable disposable; @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); getListView().setOnItemClickListener((parent, view1, position, id) -> { GpodnetTag tag = (GpodnetTag) getListAdapter().getItem(position); - MainActivity activity = (MainActivity) getActivity(); - activity.loadChildFragment(TagFragment.newInstance(tag)); + ((MainActivity) getActivity()).loadChildFragment(TagFragment.newInstance(tag)); }); startLoadTask(); @@ -35,59 +37,36 @@ public class TagListFragment extends ListFragment { @Override public void onDestroyView() { super.onDestroyView(); - cancelLoadTask(); - } - private AsyncTask<Void, Void, List<GpodnetTag>> loadTask; - - private void cancelLoadTask() { - if (loadTask != null && !loadTask.isCancelled()) { - loadTask.cancel(true); + if (disposable != null) { + disposable.dispose(); } } private void startLoadTask() { - cancelLoadTask(); - loadTask = new AsyncTask<Void, Void, List<GpodnetTag>>() { - private Exception exception; - - @Override - protected List<GpodnetTag> doInBackground(Void... params) { + if (disposable != null) { + disposable.dispose(); + } + setListShown(false); + disposable = Observable.fromCallable( + () -> { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHostname()); - try { - return service.getTopTags(COUNT); - } catch (GpodnetServiceException e) { - e.printStackTrace(); - exception = e; - return null; - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - setListShown(false); - } - - @Override - protected void onPostExecute(List<GpodnetTag> gpodnetTags) { - super.onPostExecute(gpodnetTags); - final Context context = getActivity(); - if (context != null) { - if (gpodnetTags != null) { - setListAdapter(new TagListAdapter(context, android.R.layout.simple_list_item_1, gpodnetTags)); - } else if (exception != null) { + GpodnetPreferences.getHosturl()); + return service.getTopTags(COUNT); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + tags -> { + setListAdapter(new TagListAdapter(getContext(), android.R.layout.simple_list_item_1, tags)); + setListShown(true); + }, error -> { TextView txtvError = new TextView(getActivity()); - txtvError.setText(exception.getMessage()); + txtvError.setText(error.getMessage()); getListView().setEmptyView(txtvError); - } - setListShown(true); - - } - } - }; - loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + setListShown(true); + Log.e(TAG, Log.getStackTraceString(error)); + }); } } 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..6eb19aff2 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java @@ -0,0 +1,307 @@ +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.getHosturl())) { + serverUrlText.setText(GpodnetPreferences.getHosturl()); + } + final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput); + serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { + serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE); + }); + selectHost.setOnClickListener(v -> { + if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) { + GpodnetPreferences.setHosturl(serverUrlText.getText().toString()); + } else { + GpodnetPreferences.setHosturl(GpodnetService.DEFAULT_BASE_HOST); + } + service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHosturl()); + getDialog().setTitle(GpodnetPreferences.getHosturl()); + 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); + final TextView createAccountWarning = view.findViewById(R.id.createAccountWarning); + + createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/")); + + if (GpodnetPreferences.getHosturl().startsWith("http://")) { + createAccountWarning.setVisibility(View.VISIBLE); + } + 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 eb23a5eb1..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 @@ -14,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) { @@ -51,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) public void syncStatusChanged(SyncServiceEvent event) { + updateGpodnetPreferenceScreen(); if (!GpodnetPreferences.loggedIn()) { return; } @@ -66,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, @@ -94,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() { @@ -119,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/NetworkPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java index 77f8063f2..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 @@ -71,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]); 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/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java index 9c54a529b..fbfdf537f 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java @@ -9,7 +9,7 @@ import androidx.appcompat.widget.SearchView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.ui.common.ThemeUtils; import de.danoeh.antennapod.fragment.SearchFragment; import java.util.HashMap; 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 311f44881..03a8edbf0 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.preferences; import android.content.Context; import android.content.SharedPreferences; +import android.view.KeyEvent; import androidx.preference.PreferenceManager; import de.danoeh.antennapod.BuildConfig; @@ -92,5 +93,16 @@ public class PreferenceUpgrader { if (oldVersion < 1080100) { prefs.edit().putString(UserPreferences.PREF_VIDEO_BEHAVIOR, "pip").apply(); } + if (oldVersion < 2010300) { + // Migrate hardware button preferences + if (prefs.getBoolean("prefHardwareForwardButtonSkips", false)) { + prefs.edit().putString(UserPreferences.PREF_HARDWARE_FORWARD_BUTTON, + String.valueOf(KeyEvent.KEYCODE_MEDIA_NEXT)).apply(); + } + if (prefs.getBoolean("prefHardwarePreviousButtonRestarts", false)) { + prefs.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON, + String.valueOf(KeyEvent.KEYCODE_MEDIA_PREVIOUS)).apply(); + } + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/ChapterSeekBar.java b/app/src/main/java/de/danoeh/antennapod/view/ChapterSeekBar.java new file mode 100644 index 000000000..5e80198d5 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/ChapterSeekBar.java @@ -0,0 +1,129 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import de.danoeh.antennapod.ui.common.ThemeUtils; + +public class ChapterSeekBar extends androidx.appcompat.widget.AppCompatSeekBar { + + private float top; + private float width; + private float bottom; + private float density; + private float progressPrimary; + private float progressSecondary; + private float[] dividerPos; + private final Paint paintBackground = new Paint(); + private final Paint paintProgressPrimary = new Paint(); + private final Paint paintProgressSecondary = new Paint(); + + public ChapterSeekBar(Context context) { + super(context); + init(context); + } + + public ChapterSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public ChapterSeekBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context context) { + setBackground(null); // Removes the thumb shadow + dividerPos = null; + density = context.getResources().getDisplayMetrics().density; + paintBackground.setColor(ThemeUtils.getColorFromAttr(getContext(), + de.danoeh.antennapod.core.R.attr.currently_playing_background)); + paintBackground.setAlpha(128); + paintProgressPrimary.setColor(ThemeUtils.getColorFromAttr(getContext(), + de.danoeh.antennapod.core.R.attr.colorPrimary)); + paintProgressSecondary.setColor(ThemeUtils.getColorFromAttr(getContext(), + de.danoeh.antennapod.core.R.attr.seek_background)); + } + + /** + * Sets the relative positions of the chapter dividers. + * @param dividerPos of the chapter dividers relative to the duration of the media. + */ + public void setDividerPos(final float[] dividerPos) { + if (dividerPos != null) { + this.dividerPos = new float[dividerPos.length + 2]; + this.dividerPos[0] = 0; + System.arraycopy(dividerPos, 0, this.dividerPos, 1, dividerPos.length); + this.dividerPos[this.dividerPos.length - 1] = 1; + } else { + this.dividerPos = null; + } + } + + @Override + protected synchronized void onDraw(Canvas canvas) { + top = getTop() + density * 7.5f; + bottom = getBottom() - density * 7.5f; + width = (float) (getRight() - getPaddingRight() - getLeft() - getPaddingLeft()); + progressSecondary = getSecondaryProgress() / (float) getMax() * width; + progressPrimary = getProgress() / (float) getMax() * width; + + if (dividerPos == null) { + drawProgress(canvas); + } else { + drawProgressChapters(canvas); + } + drawThumb(canvas); + } + + private void drawProgress(Canvas canvas) { + final int saveCount = canvas.save(); + canvas.translate(getPaddingLeft(), getPaddingTop()); + canvas.drawRect(0, top, width, bottom, paintBackground); + canvas.drawRect(0, top, progressSecondary, bottom, paintProgressSecondary); + canvas.drawRect(0, top, progressPrimary, bottom, paintProgressPrimary); + canvas.restoreToCount(saveCount); + } + + private void drawProgressChapters(Canvas canvas) { + final int saveCount = canvas.save(); + int currChapter = 1; + float chapterMargin = density * 0.6f; + float topExpanded = getTop() + density * 7; + float bottomExpanded = getBottom() - density * 7; + + canvas.translate(getPaddingLeft(), getPaddingTop()); + + for (int i = 1; i < dividerPos.length; i++) { + float right = dividerPos[i] * width - chapterMargin; + float left = dividerPos[i - 1] * width + chapterMargin; + float rightCurr = dividerPos[currChapter] * width - chapterMargin; + float leftCurr = dividerPos[currChapter - 1] * width + chapterMargin; + + canvas.drawRect(left, top, right, bottom, paintBackground); + + if (right < progressPrimary) { + currChapter = i + 1; + canvas.drawRect(left, top, right, bottom, paintProgressPrimary); + } else if (isPressed()) { + canvas.drawRect(leftCurr, topExpanded, rightCurr, bottomExpanded, paintBackground); + canvas.drawRect(leftCurr, topExpanded, progressPrimary, bottomExpanded, paintProgressPrimary); + } else { + if (progressSecondary > leftCurr) { + canvas.drawRect(leftCurr, top, progressSecondary, bottom, paintProgressSecondary); + } + canvas.drawRect(leftCurr, top, progressPrimary, bottom, paintProgressPrimary); + } + } + canvas.restoreToCount(saveCount); + } + + private void drawThumb(Canvas canvas) { + final int saveCount = canvas.save(); + canvas.translate(getPaddingLeft() - getThumbOffset(), getPaddingTop()); + getThumb().draw(canvas); + canvas.restoreToCount(saveCount); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java deleted file mode 100644 index 2fd570ece..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.View; -import androidx.annotation.Nullable; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.util.ThemeUtils; - -public class CircularProgressBar extends View { - public static final float MINIMUM_PERCENTAGE = 0.005f; - public static final float MAXIMUM_PERCENTAGE = 1 - MINIMUM_PERCENTAGE; - - private final Paint paintBackground = new Paint(); - private final Paint paintProgress = new Paint(); - private float percentage = 0; - private float targetPercentage = 0; - private Object tag = null; - private final RectF bounds = new RectF(); - - public CircularProgressBar(Context context) { - super(context); - setup(); - } - - public CircularProgressBar(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - setup(); - } - - public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setup(); - } - - private void setup() { - paintBackground.setAntiAlias(true); - paintBackground.setStyle(Paint.Style.STROKE); - - paintProgress.setAntiAlias(true); - paintProgress.setStyle(Paint.Style.STROKE); - paintProgress.setStrokeCap(Paint.Cap.ROUND); - - int color = ThemeUtils.getColorFromAttr(getContext(), R.attr.action_icon_color); - paintProgress.setColor(color); - paintBackground.setColor(color); - } - - /** - * Sets the percentage to be displayed. - * @param percentage Number from 0 to 1 - * @param tag When the tag is the same as last time calling setPercentage, the update is animated - */ - public void setPercentage(float percentage, Object tag) { - targetPercentage = percentage; - - if (tag == null || !tag.equals(this.tag)) { - // Do not animate - this.percentage = percentage; - this.tag = tag; - } - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - float padding = getHeight() * 0.07f; - paintBackground.setStrokeWidth(getHeight() * 0.02f); - paintProgress.setStrokeWidth(padding); - bounds.set(padding, padding, getWidth() - padding, getHeight() - padding); - canvas.drawArc(bounds, 0, 360, false, paintBackground); - - if (MINIMUM_PERCENTAGE <= percentage && percentage <= MAXIMUM_PERCENTAGE) { - canvas.drawArc(bounds, -90, percentage * 360, false, paintProgress); - } - - if (Math.abs(percentage - targetPercentage) > MINIMUM_PERCENTAGE) { - float speed = 0.02f; - if (Math.abs(targetPercentage - percentage) < 0.1 && targetPercentage > percentage) { - speed = 0.006f; - } - float delta = Math.min(speed, Math.abs(targetPercentage - percentage)); - float direction = ((targetPercentage - percentage) > 0 ? 1f : -1f); - percentage += delta * direction; - invalidate(); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java b/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java index 83d90f98b..fb1c533c5 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java +++ b/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java @@ -6,9 +6,9 @@ import android.content.res.Configuration; import android.util.AttributeSet; import android.view.View; import androidx.appcompat.view.ContextThemeWrapper; +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 io.reactivex.annotations.Nullable; @@ -39,7 +39,7 @@ public class EpisodeItemListRecyclerView extends RecyclerView { layoutManager.setRecycleChildrenOnDetach(true); setLayoutManager(layoutManager); setHasFixedSize(true); - addItemDecoration(new HorizontalDividerItemDecoration.Builder(getContext()).build()); + addItemDecoration(new DividerItemDecoration(getContext(), layoutManager.getOrientation())); setClipToPadding(false); } diff --git a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedIndicatorView.java b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedIndicatorView.java deleted file mode 100644 index d7f1eac1d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedIndicatorView.java +++ /dev/null @@ -1,113 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.View; -import androidx.annotation.Nullable; -import de.danoeh.antennapod.R; - -public class PlaybackSpeedIndicatorView extends View { - private static final float DEG_2_RAD = (float) (Math.PI / 180); - private static final float PADDING_ANGLE = 30; - private static final float VALUE_UNSET = -4242; - - private final Paint arcPaint = new Paint(); - private final Paint indicatorPaint = new Paint(); - private final Path trianglePath = new Path(); - private float angle = VALUE_UNSET; - private float targetAngle = VALUE_UNSET; - private float degreePerFrame = 2; - private float paddingArc = 20; - private float paddingIndicator = 10; - private RectF arcBounds = new RectF(); - - public PlaybackSpeedIndicatorView(Context context) { - super(context); - setup(); - } - - public PlaybackSpeedIndicatorView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - setup(); - } - - public PlaybackSpeedIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setup(); - } - - private void setup() { - setSpeed(1.0f); // Set default angle to 1.0 - targetAngle = VALUE_UNSET; // Do not move to first value that is set externally - - int[] colorAttrs = new int[] {R.attr.action_icon_color }; - TypedArray a = getContext().obtainStyledAttributes(colorAttrs); - arcPaint.setColor(a.getColor(0, 0xffffffff)); - indicatorPaint.setColor(a.getColor(0, 0xffffffff)); - a.recycle(); - - arcPaint.setAntiAlias(true); - arcPaint.setStyle(Paint.Style.STROKE); - arcPaint.setStrokeCap(Paint.Cap.ROUND); - - indicatorPaint.setAntiAlias(true); - indicatorPaint.setStyle(Paint.Style.FILL); - - trianglePath.setFillType(Path.FillType.EVEN_ODD); - } - - public void setSpeed(float value) { - float maxAnglePerDirection = 90 + 45 - 2 * paddingArc; - // Speed values above 3 are probably not too common. Cap at 3 for better differentiation - float normalizedValue = Math.min(2.5f, value - 0.5f) / 2.5f; // Linear between 0 and 1 - float target = -maxAnglePerDirection + 2 * maxAnglePerDirection * normalizedValue; - if (targetAngle == VALUE_UNSET) { - angle = target; - } - targetAngle = target; - degreePerFrame = Math.abs(targetAngle - angle) / 20; - invalidate(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - paddingArc = getMeasuredHeight() / 4.5f; - paddingIndicator = getMeasuredHeight() / 6f; - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - float radiusInnerCircle = getWidth() / 10f; - canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, radiusInnerCircle, indicatorPaint); - - trianglePath.rewind(); - float bigRadius = getHeight() / 2f - paddingIndicator; - trianglePath.moveTo(getWidth() / 2f + (float) (bigRadius * Math.sin((-angle + 180) * DEG_2_RAD)), - getHeight() / 2f + (float) (bigRadius * Math.cos((-angle + 180) * DEG_2_RAD))); - trianglePath.lineTo(getWidth() / 2f + (float) (radiusInnerCircle * Math.sin((-angle + 180 - 90) * DEG_2_RAD)), - getHeight() / 2f + (float) (radiusInnerCircle * Math.cos((-angle + 180 - 90) * DEG_2_RAD))); - trianglePath.lineTo(getWidth() / 2f + (float) (radiusInnerCircle * Math.sin((-angle + 180 + 90) * DEG_2_RAD)), - getHeight() / 2f + (float) (radiusInnerCircle * Math.cos((-angle + 180 + 90) * DEG_2_RAD))); - trianglePath.close(); - canvas.drawPath(trianglePath, indicatorPaint); - - arcPaint.setStrokeWidth(getHeight() / 15f); - arcBounds.set(paddingArc, paddingArc, getWidth() - paddingArc, getHeight() - paddingArc); - canvas.drawArc(arcBounds, -180 - 45, 90 + 45 + angle - PADDING_ANGLE, false, arcPaint); - canvas.drawArc(arcBounds, -90 + PADDING_ANGLE + angle, 90 + 45 - PADDING_ANGLE - angle, false, arcPaint); - - if (Math.abs(angle - targetAngle) > 0.5 && targetAngle != VALUE_UNSET) { - angle += Math.signum(targetAngle - angle) * Math.min(degreePerFrame, Math.abs(targetAngle - angle)); - invalidate(); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/view/RecursiveRadioGroup.java b/app/src/main/java/de/danoeh/antennapod/view/RecursiveRadioGroup.java deleted file mode 100644 index ee5e7c51d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/RecursiveRadioGroup.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import java.util.ArrayList; - -/** - * An alternative to {@link android.widget.RadioGroup} that allows to nest children. - * Basend on https://stackoverflow.com/a/14309274. - */ -public class RecursiveRadioGroup extends LinearLayout { - private final ArrayList<RadioButton> radioButtons = new ArrayList<>(); - private RadioButton checkedButton = null; - - public RecursiveRadioGroup(Context context) { - super(context); - } - - public RecursiveRadioGroup(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public RecursiveRadioGroup(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - super.addView(child, index, params); - parseChild(child); - } - - public void parseChild(final View child) { - if (child instanceof RadioButton) { - RadioButton button = (RadioButton) child; - radioButtons.add(button); - button.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (!isChecked) { - return; - } - checkedButton = (RadioButton) buttonView; - - for (RadioButton view : radioButtons) { - if (view != buttonView) { - view.setChecked(false); - } - } - }); - } else if (child instanceof ViewGroup) { - parseChildren((ViewGroup) child); - } - } - - public void parseChildren(final ViewGroup child) { - for (int i = 0; i < child.getChildCount(); i++) { - parseChild(child.getChildAt(i)); - } - } - - public RadioButton getCheckedButton() { - return checkedButton; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java deleted file mode 100644 index c256ede9e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.content.Context; -import android.content.res.TypedArray; -import androidx.appcompat.widget.AppCompatImageView; -import android.util.AttributeSet; -import de.danoeh.antennapod.core.R; - -/** - * From http://stackoverflow.com/a/19449488/6839 - */ -public class SquareImageView extends AppCompatImageView { - public static final int DIRECTION_WIDTH = 0; - public static final int DIRECTION_HEIGHT = 1; - public static final int DIRECTION_MINIMUM = 2; - - private int direction = DIRECTION_WIDTH; - - public SquareImageView(Context context) { - super(context); - } - - public SquareImageView(Context context, AttributeSet attrs) { - super(context, attrs); - loadAttrs(context, attrs); - } - - public SquareImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - loadAttrs(context, attrs); - } - - private void loadAttrs(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SquareImageView); - direction = a.getInt(R.styleable.SquareImageView_direction, DIRECTION_WIDTH); - a.recycle(); - } - - public void setDirection(int direction) { - this.direction = direction; - requestLayout(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - switch (direction) { - case DIRECTION_MINIMUM: - int size = Math.min(getMeasuredWidth(), getMeasuredHeight()); - setMeasuredDimension(size, size); - break; - case DIRECTION_HEIGHT: - setMeasuredDimension(getMeasuredHeight(), getMeasuredHeight()); - break; - default: - setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); - break; - } - } - -}
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java b/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java deleted file mode 100644 index 37792b4d1..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.GridView; - -/** - * Source: https://stackoverflow.com/a/46350213/ - */ -public class WrappingGridView extends GridView { - - public WrappingGridView(Context context) { - super(context); - } - - public WrappingGridView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public WrappingGridView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int heightSpec = heightMeasureSpec; - if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) { - // The great Android "hackatlon", the love, the magic. - // The two leftmost bits in the height measure spec have - // a special meaning, hence we can't use them to describe height. - heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); - } - super.onMeasure(widthMeasureSpec, heightSpec); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java index 274dd4ea8..0e446fb84 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java @@ -20,6 +20,7 @@ public class DownloadItemViewHolder extends RecyclerView.ViewHolder { public final TextView type; public final TextView date; public final TextView reason; + public final TextView tapForDetails; public DownloadItemViewHolder(Context context, ViewGroup parent) { super(LayoutInflater.from(context).inflate(R.layout.downloadlog_item, parent, false)); @@ -27,6 +28,7 @@ public class DownloadItemViewHolder extends RecyclerView.ViewHolder { type = itemView.findViewById(R.id.txtvType); icon = itemView.findViewById(R.id.txtvIcon); reason = itemView.findViewById(R.id.txtvReason); + tapForDetails = itemView.findViewById(R.id.txtvTapForDetails); secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton); secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon); title = itemView.findViewById(R.id.txtvTitle); 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 35744227f..8b46a781f 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 @@ -13,9 +13,7 @@ import android.widget.TextView; import androidx.cardview.widget.CardView; import androidx.recyclerview.widget.RecyclerView; - import com.joanzapata.iconify.Iconify; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.CoverLoader; @@ -25,13 +23,15 @@ import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadRequest; +import de.danoeh.antennapod.core.service.playback.PlaybackService; 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.NetworkUtils; -import de.danoeh.antennapod.core.util.ThemeUtils; -import de.danoeh.antennapod.view.CircularProgressBar; +import de.danoeh.antennapod.ui.common.ThemeUtils; +import de.danoeh.antennapod.ui.common.CircularProgressBar; /** * Holds the view which shows FeedItems. @@ -121,8 +121,8 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { if (coverHolder.getVisibility() == View.VISIBLE) { new CoverLoader(activity) - .withUri(ImageResourceUtils.getImageLocation(item)) - .withFallbackUri(item.getFeed().getImageLocation()) + .withUri(ImageResourceUtils.getEpisodeListImageLocation(item)) + .withFallbackUri(item.getFeed().getImageUrl()) .withPlaceholderView(placeholder) .withCoverView(cover) .load(); @@ -132,9 +132,6 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { private void bind(FeedMedia media) { isVideo.setVisibility(media.getMediaType() == MediaType.VIDEO ? View.VISIBLE : View.GONE); duration.setVisibility(media.getDuration() > 0 ? View.VISIBLE : View.GONE); - duration.setText(Converter.getDurationStringLong(media.getDuration())); - duration.setContentDescription(activity.getString(R.string.chapter_duration, - Converter.getDurationStringLocalized(activity, media.getDuration()))); if (media.isCurrentlyPlaying()) { itemView.setBackgroundColor(ThemeUtils.getColorFromAttr(activity, R.attr.currently_playing_background)); @@ -152,6 +149,9 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { secondaryActionProgress.setPercentage(0, item); // Animate X% -> 0% } + duration.setText(Converter.getDurationStringLong(media.getDuration())); + duration.setContentDescription(activity.getString(R.string.chapter_duration, + Converter.getDurationStringLocalized(activity, media.getDuration()))); if (item.getState() == FeedItem.State.PLAYING || item.getState() == FeedItem.State.IN_PROGRESS) { int progress = (int) (100.0 * media.getPosition() / media.getDuration()); progressBar.setProgress(progress); @@ -160,6 +160,11 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { Converter.getDurationStringLocalized(activity, media.getPosition()))); progressBar.setVisibility(View.VISIBLE); position.setVisibility(View.VISIBLE); + if (UserPreferences.shouldShowRemainingTime()) { + duration.setText("-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition())); + duration.setContentDescription(activity.getString(R.string.chapter_duration, + Converter.getDurationStringLocalized(activity, (media.getDuration() - media.getPosition())))); + } } else { progressBar.setVisibility(View.GONE); position.setVisibility(View.GONE); @@ -186,6 +191,22 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { } } + private void updateDuration(PlaybackPositionEvent event) { + int currentPosition = event.getPosition(); + int timeDuration = event.getDuration(); + int remainingTime = event.getDuration() - event.getPosition(); + Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition)); + if (currentPosition == PlaybackService.INVALID_TIME || timeDuration == PlaybackService.INVALID_TIME) { + Log.w(TAG, "Could not react to position observer update because of invalid time"); + return; + } + if (UserPreferences.shouldShowRemainingTime()) { + duration.setText("-" + Converter.getDurationStringLong(remainingTime)); + } else { + duration.setText(Converter.getDurationStringLong(timeDuration)); + } + } + public FeedItem getFeedItem() { return item; } @@ -197,7 +218,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) { progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); position.setText(Converter.getDurationStringLong(event.getPosition())); - duration.setText(Converter.getDurationStringLong(event.getDuration())); + updateDuration(event); duration.setVisibility(View.VISIBLE); // Even if the duration was previously unknown, it is now known } |