diff options
author | ByteHamster <info@bytehamster.com> | 2021-01-22 11:07:40 +0100 |
---|---|---|
committer | ByteHamster <info@bytehamster.com> | 2021-01-22 11:07:40 +0100 |
commit | cdf59a1c8e99720fd737fcbcc1f5ac1eb5252890 (patch) | |
tree | 119819aaec911b7510dc3f3bb1a5a4a6ddf82c1c /app/src/main/java/de/danoeh/antennapod | |
parent | 85b897c7d778ae70d837ae0c2a9e7b4252ea8945 (diff) | |
parent | 41d23fa671a3ad7d417f4791c63d0f25634d725f (diff) | |
download | AntennaPod-cdf59a1c8e99720fd737fcbcc1f5ac1eb5252890.zip |
Merge branch 'develop' into fix_episodes_list_item_loading_b
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod')
67 files changed, 820 insertions, 1208 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java index bff11fa5e..721291597 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java @@ -11,10 +11,15 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; import com.google.android.material.snackbar.Snackbar; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; +import android.view.Menu; +import android.view.MenuItem; import android.widget.TextView; @@ -67,42 +72,65 @@ public class BugReportActivity extends AppCompatActivity { clipboard.setPrimaryClip(clip); Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show(); }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.bug_report_options, menu); + return super.onCreateOptionsMenu(menu); + } - findViewById(R.id.btn_export_logcat).setOnClickListener(v -> { + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == R.id.export_logcat) { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this); + alertBuilder.setMessage(R.string.confirm_export_log_dialog_message); + alertBuilder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + exportLog(); + dialog.dismiss(); + }); + alertBuilder.setNegativeButton(R.string.cancel_label, null); + alertBuilder.show(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void exportLog() { + try { + File filename = new File(UserPreferences.getDataFolder(null), "full-logs.txt"); + filename.createNewFile(); + String cmd = "logcat -d -f " + filename.getAbsolutePath(); + Runtime.getRuntime().exec(cmd); + //share file try { - File filename = new File(UserPreferences.getDataFolder(null), "full-logs.txt"); - filename.createNewFile(); - String cmd = "logcat -d -f " + filename.getAbsolutePath(); - Runtime.getRuntime().exec(cmd); - //share file - try { - Intent i = new Intent(Intent.ACTION_SEND); - i.setType("text/*"); - String authString = getString(de.danoeh.antennapod.core.R.string.provider_authority); - Uri fileUri = FileProvider.getUriForFile(this, authString, filename); - i.putExtra(Intent.EXTRA_STREAM, fileUri); - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - PackageManager pm = getPackageManager(); - List<ResolveInfo> resInfos = pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfos) { - String packageName = resolveInfo.activityInfo.packageName; - grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } + Intent i = new Intent(Intent.ACTION_SEND); + i.setType("text/*"); + String authString = getString(de.danoeh.antennapod.core.R.string.provider_authority); + Uri fileUri = FileProvider.getUriForFile(this, authString, filename); + i.putExtra(Intent.EXTRA_STREAM, fileUri); + i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + PackageManager pm = getPackageManager(); + List<ResolveInfo> resInfos = pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfos) { + String packageName = resolveInfo.activityInfo.packageName; + grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } - String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label); - startActivity(Intent.createChooser(i, chooserTitle)); - } catch (Exception e) { - e.printStackTrace(); - int strResId = R.string.log_file_share_exception; - Snackbar.make(findViewById(android.R.id.content), strResId, Snackbar.LENGTH_LONG) - .show(); } - } catch (IOException e) { + String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label); + startActivity(Intent.createChooser(i, chooserTitle)); + } catch (Exception e) { e.printStackTrace(); - Snackbar.make(findViewById(android.R.id.content), e.getMessage(), Snackbar.LENGTH_LONG).show(); + int strResId = R.string.log_file_share_exception; + Snackbar.make(findViewById(android.R.id.content), strResId, Snackbar.LENGTH_LONG) + .show(); } - }); + } catch (IOException e) { + e.printStackTrace(); + Snackbar.make(findViewById(android.R.id.content), e.getMessage(), Snackbar.LENGTH_LONG).show(); + } } + } 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 1d3d9bf11..4900f0e27 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java @@ -1,62 +1,54 @@ package de.danoeh.antennapod.activity; import android.app.Activity; -import android.content.Intent; import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import android.widget.Button; +import android.text.TextUtils; import android.widget.EditText; import android.widget.TextView; - -import org.apache.commons.lang3.Validate; - +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; 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 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; + private DownloadRequest request; @Override protected void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getNoTitleTheme()); 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); + request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST); + TextView txtvDescription = findViewById(R.id.txtvDescription); String newDescription = txtvDescription.getText() + ":\n\n" + request.getTitle(); txtvDescription.setText(newDescription); @@ -65,28 +57,42 @@ public class DownloadAuthenticationActivity extends AppCompatActivity { 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); - } - finish(); - }); + findViewById(R.id.butConfirm).setOnClickListener(v -> + Completable.fromAction(this::updatePassword) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request); + finish(); + })); - butCancel.setOnClickListener(v -> { + findViewById(R.id.butCancel).setOnClickListener(v -> { setResult(Activity.RESULT_CANCELED); finish(); }); } + private void updatePassword() { + String username = etxtUsername.getText().toString(); + String password = etxtPassword.getText().toString(); + 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); + } + } + } + } + @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); 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 d39edb9b2..d1716e009 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -16,6 +16,7 @@ import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import android.widget.FrameLayout; import android.widget.Toast; import androidx.annotation.NonNull; @@ -542,6 +543,11 @@ public class MainActivity extends CastEnabledActivity { //Hardware keyboard support @Override public boolean onKeyUp(int keyCode, KeyEvent event) { + View currentFocus = getCurrentFocus(); + if (currentFocus instanceof EditText) { + return super.onKeyUp(keyCode, event); + } + AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); Integer customKeyCode = null; 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 b03d1e5cd..c1d921f8c 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,9 @@ 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 +16,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,18 +26,13 @@ import org.greenrobot.eventbus.ThreadMode; import java.text.NumberFormat; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityOptionsCompat; -import androidx.core.content.ContextCompat; 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; @@ -51,11 +44,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; @@ -74,8 +65,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements 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; @@ -664,50 +653,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 2fd537159..f53c629b9 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -4,6 +4,7 @@ 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; @@ -13,7 +14,6 @@ import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.UiThread; @@ -88,6 +88,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; @@ -446,6 +449,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 -> { @@ -512,10 +520,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()); + feedPreferences.setAutoDownload(autoDownload); feed1.savePreferences(); + + SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload); + editor.apply(); } openFeed(); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index 10292b892..d4e9ee5d9 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -17,9 +17,8 @@ import de.danoeh.antennapod.asynctask.OpmlFeedQueuer; import de.danoeh.antennapod.asynctask.OpmlImportWorker; import de.danoeh.antennapod.core.export.opml.OpmlElement; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.LangUtils; + import org.apache.commons.io.ByteOrderMark; -import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.BOMInputStream; import org.apache.commons.lang3.ArrayUtils; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java index d85235cf9..f0c76d545 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java @@ -13,9 +13,9 @@ import android.widget.ProgressBar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.danoeh.antennapod.error.CrashReportWriter; import io.reactivex.Completable; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; /** @@ -44,13 +44,15 @@ public class SplashActivity extends AppCompatActivity { }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - Intent intent = new Intent(SplashActivity.this, MainActivity.class); - startActivity(intent); - overridePendingTransition(0, 0); - finish(); - }, error -> { + .subscribe( + () -> { + Intent intent = new Intent(SplashActivity.this, MainActivity.class); + startActivity(intent); + overridePendingTransition(0, 0); + finish(); + }, error -> { error.printStackTrace(); + CrashReportWriter.write(error); Toast.makeText(this, error.getLocalizedMessage(), Toast.LENGTH_LONG).show(); finish(); }); 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 2d4510e8f..75198c016 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -12,12 +12,11 @@ import android.view.KeyEvent; import android.view.animation.AlphaAnimation; import android.view.animation.AnimationSet; import android.view.animation.ScaleAnimation; +import android.widget.EditText; import android.widget.ImageView; -import androidx.appcompat.view.menu.ActionMenuItem; 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,7 +36,6 @@ 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; @@ -88,9 +86,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; @@ -489,6 +485,11 @@ public class VideoplayerActivity extends MediaplayerActivity { //Hardware keyboard support @Override public boolean onKeyUp(int keyCode, KeyEvent event) { + View currentFocus = getCurrentFocus(); + if (currentFocus instanceof EditText) { + return super.onKeyUp(keyCode, event); + } + AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); switch (keyCode) { 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 474b96c38..1b4e8b81e 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java @@ -2,22 +2,19 @@ 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 android.widget.RemoteViews; -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; @@ -29,7 +26,10 @@ public class WidgetConfigActivity extends AppCompatActivity { 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) { @@ -74,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() { @@ -92,6 +118,9 @@ 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(); 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 002147071..4fa8acc43 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java @@ -22,12 +22,14 @@ 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.core.util.playback.Playable; +import de.danoeh.antennapod.view.CircularProgressBar; public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapter.ChapterHolder> { private Playable media; private final Callback callback; private final Context context; private int currentChapterIndex = -1; + private long currentChapterPosition = -1; private boolean hasImages = false; public ChaptersListAdapter(Context context, Callback callback) { @@ -48,7 +50,6 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte notifyDataSetChanged(); } - @Override public void onBindViewHolder(@NonNull ChapterHolder holder, int position) { Chapter sc = getItem(position); @@ -65,7 +66,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte holder.duration.setText(context.getString(R.string.chapter_duration, Converter.getDurationStringLocalized(context, (int) duration))); - if (sc.getLink() == null) { + if (TextUtils.isEmpty(sc.getLink())) { holder.link.setVisibility(View.GONE); } else { holder.link.setVisibility(View.VISIBLE); @@ -73,6 +74,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte holder.link.setOnClickListener(v -> IntentUtils.openInBrowser(context, sc.getLink())); } holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.av_play)); + holder.secondaryActionButton.setContentDescription(context.getString(R.string.play_chapter)); holder.secondaryActionButton.setOnClickListener(v -> { if (callback != null) { callback.onPlayChapterButtonClicked(position); @@ -82,8 +84,14 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte if (position == currentChapterIndex) { int playingBackGroundColor = ThemeUtils.getColorFromAttr(context, R.attr.currently_playing_background); holder.itemView.setBackgroundColor(playingBackGroundColor); + float progress = ((float) (currentChapterPosition - sc.getStart())) / duration; + progress = Math.max(progress, CircularProgressBar.MINIMUM_PERCENTAGE); + progress = Math.min(progress, CircularProgressBar.MAXIMUM_PERCENTAGE); + holder.progressBar.setPercentage(progress, position); + holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.av_replay)); } else { holder.itemView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent)); + holder.progressBar.setPercentage(0, null); } if (hasImages) { @@ -135,6 +143,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte final ImageView image; final View secondaryActionButton; final ImageView secondaryActionIcon; + final CircularProgressBar progressBar; public ChapterHolder(@NonNull View itemView) { super(itemView); @@ -145,14 +154,23 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte duration = itemView.findViewById(R.id.txtvDuration); secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton); secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon); + progressBar = itemView.findViewById(R.id.secondaryActionProgress); } } public void notifyChapterChanged(int newChapterIndex) { currentChapterIndex = newChapterIndex; + currentChapterPosition = getItem(newChapterIndex).getStart(); notifyDataSetChanged(); } + public void notifyTimeChanged(long timeMs) { + currentChapterPosition = timeMs; + // Passing an argument prevents flickering. + // See EpisodeItemListAdapter.notifyItemChangedCompat. + notifyItemChanged(currentChapterIndex, "foo"); + } + private boolean ignoreChapter(Chapter c) { return media.getDuration() > 0 && media.getDuration() < c.getStart(); } 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 7d195a9ad..0c4aaf6ed 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -1,23 +1,15 @@ package de.danoeh.antennapod.adapter; import android.app.Activity; -import android.content.Context; -import android.os.Build; -import android.text.Layout; import android.text.format.DateUtils; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import android.widget.TextView; import android.widget.Toast; import androidx.core.content.ContextCompat; -import com.joanzapata.iconify.widget.IconButton; -import com.joanzapata.iconify.widget.IconTextView; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; 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 540cd054c..268a21409 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java @@ -65,6 +65,7 @@ public class DownloadlistAdapter extends BaseAdapter { holder.title.setText(request.getTitle()); holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.navigation_cancel)); + holder.secondaryActionButton.setContentDescription(context.getString(R.string.cancel_download_label)); holder.secondaryActionButton.setTag(downloader); holder.secondaryActionButton.setOnClickListener(butSecondaryListener); holder.secondaryActionProgress.setPercentage(0, request); 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 6bfd34d5c..8cb0fd30a 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java @@ -13,8 +13,6 @@ import androidx.annotation.Nullable; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.playback.RemoteMedia; import de.danoeh.antennapod.core.feed.FeedItem; 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 92ed7b052..f8507ba74 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -75,7 +75,7 @@ public class NavListAdapter extends BaseAdapter } public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)) { + if (UserPreferences.PREF_HIDDEN_DRAWER_ITEMS.equals(key)) { loadItems(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java index 7ce086694..01712ea29 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -6,7 +6,6 @@ import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MotionEvent; import android.view.View; -import androidx.core.view.MotionEventCompat; import androidx.recyclerview.widget.ItemTouchHelper; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; 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 8c294a9c9..919a4e217 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java @@ -13,8 +13,6 @@ import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.bumptech.glide.Glide; - import java.lang.ref.WeakReference; import java.text.NumberFormat; import java.util.Locale; @@ -23,7 +21,6 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.LocalFeedUpdater; -import de.danoeh.antennapod.fragment.AddFeedFragment; import de.danoeh.antennapod.fragment.FeedItemlistFragment; import jp.shts.android.library.TriangleLabelView; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java index 31dfe15da..78ea3b93f 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java @@ -7,11 +7,8 @@ import de.danoeh.antennapod.R; 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.UsageStatistics; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; -import de.danoeh.antennapod.dialog.StreamingConfirmationDialog; public class PlayLocalActionButton extends ItemActionButton { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java index 7b8659968..e45280eed 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java @@ -1,8 +1,6 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; -import android.content.Intent; -import android.net.Uri; import android.view.View; import androidx.annotation.AttrRes; import androidx.annotation.StringRes; diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java index 3ac05e842..906d50c61 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java @@ -9,10 +9,10 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import de.danoeh.antennapod.core.export.ExportWriter; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.LangUtils; import io.reactivex.Observable; /** @@ -44,7 +44,7 @@ public class DocumentFileExportWorker { if (outputStream == null) { throw new IOException(); } - writer = new OutputStreamWriter(outputStream, LangUtils.UTF_8); + writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8")); exportWriter.writeDocument(DBReader.getFeedList(), writer, context); subscriber.onNext(output); } catch (IOException e) { diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java index f81a52402..0930b59eb 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java @@ -8,11 +8,11 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import de.danoeh.antennapod.core.export.ExportWriter; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.LangUtils; import io.reactivex.Observable; /** @@ -47,7 +47,7 @@ public class ExportWorker { return Observable.create(subscriber -> { OutputStreamWriter writer = null; try { - writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8); + writer = new OutputStreamWriter(new FileOutputStream(output), Charset.forName("UTF-8")); exportWriter.writeDocument(DBReader.getFeedList(), writer, context); subscriber.onNext(output); } catch (IOException e) { 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 6e584d34f..7a5cf431f 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -2,6 +2,7 @@ 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. @@ -15,7 +16,7 @@ class ClientConfigurator { ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl(); ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); - ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl(); + ClientConfig.automaticDownloadAlgorithm = new APDownloadAlgorithm(); ClientConfig.castCallbacks = new CastCallbackImpl(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java deleted file mode 100644 index c3f7ae9c8..000000000 --- a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.danoeh.antennapod.config; - -import de.danoeh.antennapod.core.DBTasksCallbacks; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.APDownloadAlgorithm; -import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm; -import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm; - -public class DBTasksCallbacksImpl implements DBTasksCallbacks { - - @Override - public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() { - return new APDownloadAlgorithm(); - } - - @Override - public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() { - return UserPreferences.getEpisodeCleanupAlgorithm(); - } -} 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 71442f50b..f509f6dd4 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java @@ -31,7 +31,6 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request) { final Intent activityIntent = new Intent(context.getApplicationContext(), DownloadAuthenticationActivity.class); 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); } @@ -54,9 +53,4 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - - @Override - public boolean shouldCreateReport() { - return true; - } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java index 44f18e9da..f70cb26ff 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java @@ -3,7 +3,6 @@ package de.danoeh.antennapod.config; import android.content.Context; import android.content.Intent; import android.os.Build; -import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.VideoplayerActivity; import de.danoeh.antennapod.core.PlaybackServiceCallbacks; @@ -22,10 +21,4 @@ public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks { return i; } } - - @Override - public boolean useQueue() { - return true; - } - } 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 5aee3f2be..efaff1da3 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -436,13 +436,18 @@ public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnM } private void deleteChecked() { + int countHasMedia = 0; + int countNoMedia = 0; for (long id : checkedIds.toArray()) { FeedItem episode = idMap.get(id); - if (episode.hasMedia()) { + if (episode.hasMedia() && episode.getMedia().isDownloaded()) { + countHasMedia++; DBWriter.deleteFeedMediaOfItem(getActivity(), episode.getMedia().getId()); + } else { + countNoMedia++; } } - close(R.plurals.deleted_episode_batch_label, checkedIds.size()); + closeMore(R.plurals.deleted_multi_episode_batch_label, countNoMedia, countHasMedia); } private void close(@PluralsRes int msgId, int numItems) { @@ -451,4 +456,12 @@ public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnM getActivity().getSupportFragmentManager().popBackStack(); } + private void closeMore(@PluralsRes int msgId, int countNoMedia, int countHasMedia) { + ((MainActivity) getActivity()).showSnackbarAbovePlayer( + getResources().getQuantityString(msgId, + (countHasMedia + countNoMedia), + (countHasMedia + countNoMedia), countHasMedia), + Snackbar.LENGTH_LONG); + getActivity().getSupportFragmentManager().popBackStack(); + } } 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 82010637f..80df87891 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.dialog; import android.content.Context; import android.text.TextUtils; import android.view.LayoutInflater; +import android.view.View; import android.widget.LinearLayout; import android.widget.RadioButton; @@ -34,18 +35,19 @@ public abstract class FilterDialog { builder.setTitle(R.string.filter); LayoutInflater inflater = LayoutInflater.from(this.context); - LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.filter_dialog, null, false); + View layout = inflater.inflate(R.layout.filter_dialog, null, false); + LinearLayout rows = layout.findViewById(R.id.filter_rows); builder.setView(layout); for (FeedItemFilterGroup item : FeedItemFilterGroup.values()) { - RecursiveRadioGroup row = (RecursiveRadioGroup) inflater.inflate(R.layout.filter_dialog_row, null); + RecursiveRadioGroup row = (RecursiveRadioGroup) inflater.inflate(R.layout.filter_dialog_row, null, false); RadioButton filter1 = row.findViewById(R.id.filter_dialog_radioButton1); RadioButton filter2 = row.findViewById(R.id.filter_dialog_radioButton2); filter1.setText(item.values[0].displayName); filter1.setTag(item.values[0].filterId); filter2.setText(item.values[1].displayName); filter2.setTag(item.values[1].filterId); - layout.addView(row); + rows.addView(row); } for (String filterId : filterValues) { @@ -56,11 +58,11 @@ public abstract class FilterDialog { builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { filterValues.clear(); - for (int i = 0; i < layout.getChildCount(); i++) { - if (!(layout.getChildAt(i) instanceof RecursiveRadioGroup)) { + for (int i = 0; i < rows.getChildCount(); i++) { + if (!(rows.getChildAt(i) instanceof RecursiveRadioGroup)) { continue; } - RecursiveRadioGroup group = (RecursiveRadioGroup) layout.getChildAt(i); + RecursiveRadioGroup group = (RecursiveRadioGroup) rows.getChildAt(i); if (group.getCheckedButton() != null) { String tag = (String) group.getCheckedButton().getTag(); if (tag != null) { // Clear buttons use no tag 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/RemoveFeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java new file mode 100644 index 000000000..156c1dba8 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java @@ -0,0 +1,52 @@ +package de.danoeh.antennapod.dialog; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.Log; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.storage.DBWriter; +import io.reactivex.Completable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class RemoveFeedDialog { + private static final String TAG = "RemoveFeedDialog"; + + public static void show(Context context, Feed feed, Runnable onSuccess) { + int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg + : R.string.feed_delete_confirmation_msg; + String message = context.getString(messageId, feed.getTitle()); + + ConfirmationDialog dialog = new ConfirmationDialog(context, R.string.remove_feed_label, message) { + @Override + public void onConfirmButtonPressed(DialogInterface clickedDialog) { + clickedDialog.dismiss(); + + ProgressDialog progressDialog = new ProgressDialog(context); + progressDialog.setMessage(context.getString(R.string.feed_remover_msg)); + progressDialog.setIndeterminate(true); + progressDialog.setCancelable(false); + progressDialog.show(); + + Completable.fromCallable(() -> DBWriter.deleteFeed(context, feed.getId()).get()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + () -> { + Log.d(TAG, "Feed was deleted"); + if (onSuccess != null) { + onSuccess.run(); + } + progressDialog.dismiss(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressDialog.dismiss(); + }); + } + }; + dialog.createNewDialog().show(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java index 699c6f492..a4e49ff96 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java @@ -1,16 +1,15 @@ package de.danoeh.antennapod.dialog; import android.app.Activity; -import android.text.InputType; import java.lang.ref.WeakReference; import android.view.View; -import android.widget.EditText; import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.databinding.EditTextDialogBinding; public class RenameFeedDialog { @@ -29,13 +28,14 @@ public class RenameFeedDialog { } View content = View.inflate(activity, R.layout.edit_text_dialog, null); - EditText editText = content.findViewById(R.id.text); - editText.setText(feed.getTitle()); + EditTextDialogBinding alertViewBinding = EditTextDialogBinding.bind(content); + + alertViewBinding.urlEditText.setText(feed.getTitle()); AlertDialog dialog = new AlertDialog.Builder(activity) .setView(content) .setTitle(de.danoeh.antennapod.core.R.string.rename_feed_label) .setPositiveButton(android.R.string.ok, (d, input) -> { - feed.setCustomTitle(editText.getText().toString()); + feed.setCustomTitle(alertViewBinding.urlEditText.getText().toString()); DBWriter.setFeedCustomTitle(feed); }) .setNeutralButton(de.danoeh.antennapod.core.R.string.reset, null) @@ -44,7 +44,7 @@ public class RenameFeedDialog { // To prevent cancelling the dialog on button click dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener( - (view) -> editText.setText(feed.getFeedTitle())); + (view) -> alertViewBinding.urlEditText.setText(feed.getFeedTitle())); } } 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 274c3b7bd..f1a41d753 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -3,7 +3,6 @@ package de.danoeh.antennapod.dialog; import android.app.Activity; import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; import android.os.Bundle; import android.view.View; import android.view.inputmethod.InputMethodManager; @@ -88,6 +87,27 @@ public class SleepTimerDialog extends DialogFragment { timeSetup = content.findViewById(R.id.timeSetup); timeDisplay = content.findViewById(R.id.timeDisplay); time = content.findViewById(R.id.time); + Button extendSleepFiveMinutesButton = content.findViewById(R.id.extendSleepFiveMinutesButton); + extendSleepFiveMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 5)); + Button extendSleepTenMinutesButton = content.findViewById(R.id.extendSleepTenMinutesButton); + extendSleepTenMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 10)); + Button extendSleepTwentyMinutesButton = content.findViewById(R.id.extendSleepTwentyMinutesButton); + extendSleepTwentyMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 20)); + extendSleepFiveMinutesButton.setOnClickListener(v -> { + if (controller != null) { + controller.extendSleepTimer(5 * 1000 * 60); + } + }); + extendSleepTenMinutesButton.setOnClickListener(v -> { + if (controller != null) { + controller.extendSleepTimer(10 * 1000 * 60); + } + }); + extendSleepTwentyMinutesButton.setOnClickListener(v -> { + if (controller != null) { + controller.extendSleepTimer(20 * 1000 * 60); + } + }); etxtTime.setText(SleepTimerPreferences.lastTimerValue()); etxtTime.postDelayed(() -> { 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 a8915480c..8a87fef25 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java @@ -30,7 +30,8 @@ public class SubscriptionsFilterDialog { builder.setTitle(context.getString(R.string.pref_filter_feed_title)); LayoutInflater inflater = LayoutInflater.from(context); - LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.filter_dialog, null, false); + View layout = inflater.inflate(R.layout.filter_dialog, null, false); + LinearLayout rows = layout.findViewById(R.id.filter_rows); builder.setView(layout); for (SubscriptionsFilterGroup item : SubscriptionsFilterGroup.values()) { @@ -45,7 +46,7 @@ public class SubscriptionsFilterDialog { } else { filter2.setVisibility(View.GONE); } - layout.addView(row); + rows.addView(row); } for (String filterId : filterValues) { @@ -56,11 +57,11 @@ public class SubscriptionsFilterDialog { builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { filterValues.clear(); - for (int i = 0; i < layout.getChildCount(); i++) { - if (!(layout.getChildAt(i) instanceof RecursiveRadioGroup)) { + for (int i = 0; i < rows.getChildCount(); i++) { + if (!(rows.getChildAt(i) instanceof RecursiveRadioGroup)) { continue; } - RecursiveRadioGroup group = (RecursiveRadioGroup) layout.getChildAt(i); + RecursiveRadioGroup group = (RecursiveRadioGroup) rows.getChildAt(i); if (group.getCheckedButton() != null) { String tag = (String) group.getCheckedButton().getTag(); if (tag != null) { // Clear buttons use no tag diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java index 796ec556f..6e894176f 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java @@ -1,7 +1,5 @@ package de.danoeh.antennapod.discovery; -import android.content.Context; -import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java index eeebd2ebf..8fbc8c76b 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java @@ -1,8 +1,6 @@ package de.danoeh.antennapod.discovery; import io.reactivex.Single; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; import java.util.List; public interface PodcastSearcher { 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 caf489569..06a974dfd 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -12,7 +12,6 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -30,6 +29,8 @@ import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.util.SortOrder; +import de.danoeh.antennapod.databinding.AddfeedBinding; +import de.danoeh.antennapod.databinding.EditTextDialogBinding; import de.danoeh.antennapod.discovery.CombinedSearcher; import de.danoeh.antennapod.discovery.FyydPodcastSearcher; import de.danoeh.antennapod.discovery.ItunesPodcastSearcher; @@ -50,7 +51,7 @@ public class AddFeedFragment extends Fragment { private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; - private EditText combinedFeedSearchBox; + private AddfeedBinding viewBinding; private MainActivity activity; @Override @@ -59,39 +60,43 @@ public class AddFeedFragment extends Fragment { @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); - View root = inflater.inflate(R.layout.addfeed, container, false); + viewBinding = AddfeedBinding.inflate(getLayoutInflater()); activity = (MainActivity) getActivity(); - Toolbar toolbar = root.findViewById(R.id.toolbar); + + Toolbar toolbar = viewBinding.toolbar; ((MainActivity) getActivity()).setupToolbarToggle(toolbar); - root.findViewById(R.id.btn_search_itunes).setOnClickListener(v + viewBinding.searchItunesButton.setOnClickListener(v -> activity.loadChildFragment(OnlineSearchFragment.newInstance(ItunesPodcastSearcher.class))); - root.findViewById(R.id.btn_search_fyyd).setOnClickListener(v + viewBinding.searchFyydButton.setOnClickListener(v -> activity.loadChildFragment(OnlineSearchFragment.newInstance(FyydPodcastSearcher.class))); - root.findViewById(R.id.btn_search_gpodder).setOnClickListener(v + viewBinding.searchGPodderButton.setOnClickListener(v -> activity.loadChildFragment(new GpodnetMainFragment())); - root.findViewById(R.id.btn_search_podcastindex).setOnClickListener(v + viewBinding.searchPodcastIndexButton.setOnClickListener(v -> activity.loadChildFragment(OnlineSearchFragment.newInstance(PodcastIndexPodcastSearcher.class))); - combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox); - combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> { + viewBinding.combinedFeedSearchEditText.setOnEditorActionListener((v, actionId, event) -> { performSearch(); return true; }); - root.findViewById(R.id.btn_add_via_url).setOnClickListener(v + + viewBinding.addViaUrlButton.setOnClickListener(v -> showAddViaUrlDialog()); - root.findViewById(R.id.btn_opml_import).setOnClickListener(v -> { + viewBinding.opmlImportButton.setOnClickListener(v -> { try { Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); intentGetContentAction.setType("*/*"); startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); + e.printStackTrace(); + ((MainActivity) getActivity()) + .showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG); } }); - root.findViewById(R.id.btn_add_local_folder).setOnClickListener(v -> { + + viewBinding.addLocalFolderButton.setOnClickListener(v -> { if (Build.VERSION.SDK_INT < 21) { return; } @@ -100,29 +105,35 @@ public class AddFeedFragment extends Fragment { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER); } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); + e.printStackTrace(); + ((MainActivity) getActivity()) + .showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG); } }); if (Build.VERSION.SDK_INT < 21) { - root.findViewById(R.id.btn_add_local_folder).setVisibility(View.GONE); + viewBinding.addLocalFolderButton.setVisibility(View.GONE); } - root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch()); - return root; + + viewBinding.searchButton.setOnClickListener(view -> performSearch()); + + return viewBinding.getRoot(); } private void showAddViaUrlDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(R.string.add_podcast_by_url); View content = View.inflate(getContext(), R.layout.edit_text_dialog, null); - EditText editText = content.findViewById(R.id.text); - editText.setHint(R.string.add_podcast_by_url_hint); + EditTextDialogBinding alertViewBinding = EditTextDialogBinding.bind(content); + alertViewBinding.urlEditText.setHint(R.string.add_podcast_by_url_hint); + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); String clipboardContent = clipboard.getText() != null ? clipboard.getText().toString() : ""; if (clipboardContent.trim().startsWith("http")) { - editText.setText(clipboardContent.trim()); + alertViewBinding.urlEditText.setText(clipboardContent.trim()); } - builder.setView(content); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> addUrl(editText.getText().toString())); + builder.setView(alertViewBinding.getRoot()); + builder.setPositiveButton(R.string.confirm_label, + (dialog, which) -> addUrl(alertViewBinding.urlEditText.getText().toString())); builder.setNegativeButton(R.string.cancel_label, null); builder.show(); } @@ -134,8 +145,7 @@ public class AddFeedFragment extends Fragment { } private void performSearch() { - String query = combinedFeedSearchBox.getText().toString(); - + String query = viewBinding.combinedFeedSearchEditText.getText().toString(); if (query.matches("http[s]?://.*")) { addUrl(query); return; @@ -187,7 +197,6 @@ public class AddFeedFragment extends Fragment { throw new IllegalArgumentException("Unable to retrieve document tree"); } Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, documentFile.getName()); - dirFeed.setDescription(getString(R.string.local_feed_description)); 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 08da1f8d9..77b272da7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import androidx.annotation.NonNull; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java index 5a65f956c..82e2b3a6a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java @@ -22,6 +22,8 @@ import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.CastEnabledActivity; import de.danoeh.antennapod.activity.MainActivity; @@ -43,7 +45,6 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog; import de.danoeh.antennapod.dialog.SleepTimerDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; -import de.danoeh.antennapod.view.PagerIndicatorView; import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -74,7 +75,6 @@ public class AudioPlayerFragment extends Fragment implements PlaybackSpeedIndicatorView butPlaybackSpeed; TextView txtvPlaybackSpeed; private ViewPager2 pager; - private PagerIndicatorView pageIndicator; private TextView txtvPosition; private TextView txtvLength; private SeekBar sbPosition; @@ -90,6 +90,8 @@ public class AudioPlayerFragment extends Fragment implements private PlaybackController controller; private Disposable disposable; private boolean showTimeLeft; + private boolean hasChapters = false; + private TabLayoutMediator tabLayoutMediator; @Override public View onCreateView(@NonNull LayoutInflater inflater, @@ -141,13 +143,37 @@ public class AudioPlayerFragment extends Fragment implements }); } }); - pageIndicator = root.findViewById(R.id.page_indicator); - pageIndicator.setViewPager(pager); - pageIndicator.setOnClickListener(v -> - pager.setCurrentItem((pager.getCurrentItem() + 1) % NUM_CONTENT_FRAGMENTS)); + + TabLayout tabLayout = root.findViewById(R.id.sliding_tabs); + tabLayoutMediator = new TabLayoutMediator(tabLayout, pager, (tab, position) -> { + tab.view.setAlpha(1.0f); + switch (position) { + case POS_COVER: + tab.setText(R.string.cover_label); + break; + case POS_DESCR: + tab.setText(R.string.description_label); + break; + case POS_CHAPTERS: + tab.setText(R.string.chapters_label); + if (!hasChapters) { + tab.view.setAlpha(0.5f); + } + break; + default: + break; + } + }); + tabLayoutMediator.attach(); return root; } + public void setHasChapters(boolean hasChapters) { + this.hasChapters = hasChapters; + tabLayoutMediator.detach(); + tabLayoutMediator.attach(); + } + public View getExternalPlayerHolder() { return getView().findViewById(R.id.playerFragment); } @@ -362,10 +388,6 @@ public class AudioPlayerFragment extends Fragment implements setupOptionsMenu(media); } - public void setHasChapters(boolean hasChapters) { - pageIndicator.setDisabledPage(hasChapters ? -1 : 2); - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java index 6f95d71da..d781d0774 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -106,6 +106,7 @@ public class ChaptersFragment extends Fragment { @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(PlaybackPositionEvent event) { updateChapterSelection(getCurrentChapter(media)); + adapter.notifyTimeChanged(event.getPosition()); } private int getCurrentChapter(Playable media) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index 59b2cd234..3519a34b4 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -4,7 +4,6 @@ import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index 98d6a946b..2e11ea4ec 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -4,7 +4,6 @@ import android.app.Dialog; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.content.res.TypedArray; import android.os.Bundle; import androidx.annotation.NonNull; @@ -13,7 +12,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.ListFragment; import android.util.Log; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ListView; @@ -176,7 +174,7 @@ public class DownloadLogFragment extends ListFragment { public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (!super.onOptionsItemSelected(item)) { switch (item.getItemId()) { - case R.id.clear_history_item: + case R.id.clear_logs_item: DBWriter.clearDownloadLog(); return true; case R.id.refresh_item: 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 4ca08cbdd..ddfcd06ad 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -130,6 +130,8 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic protected void doTint(Context themedContext) { toolbar.getMenu().findItem(R.id.visit_website_item) .setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.location_web_site)); + toolbar.getMenu().findItem(R.id.share_parent) + .setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.ic_share)); } }; iconTintManager.updateTint(); @@ -284,8 +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_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 681d3a88e..8e14214d2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.fragment; import android.content.Context; -import android.content.DialogInterface; import android.content.res.Configuration; import android.content.Intent; import android.graphics.LightingColorFilter; @@ -36,8 +35,6 @@ import com.joanzapata.iconify.widget.IconTextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; -import de.danoeh.antennapod.core.asynctask.FeedRemover; -import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; @@ -65,6 +62,7 @@ import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.dialog.FilterDialog; +import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.RenameFeedDialog; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; @@ -298,28 +296,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem new RenameFeedDialog(getActivity(), feed).show(); return true; case R.id.remove_item: - final FeedRemover remover = new FeedRemover( - getActivity(), feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); - } - }; - int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg - : R.string.feed_delete_confirmation_msg; - ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(), - R.string.remove_feed_label, - getString(messageId, feed.getTitle())) { - - @Override - public void onConfirmButtonPressed( - DialogInterface dialog) { - dialog.dismiss(); - remover.executeAsync(); - } - }; - conDialog.createNewDialog().show(); + RemoveFeedDialog.show(getContext(), feed, () -> + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null)); return true; default: return false; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index fc3052e20..18a61f1e6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -8,10 +8,8 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; import de.danoeh.antennapod.view.ShownotesWebView; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index 669dbdac2..07f59bb42 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -17,7 +17,6 @@ import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.core.text.TextUtilsCompat; import androidx.core.util.ObjectsCompat; import androidx.core.view.ViewCompat; @@ -71,7 +70,6 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import java.util.Date; import java.util.List; import java.util.Locale; @@ -120,6 +118,7 @@ public class ItemFragment extends Fragment { private View butAction2; private ItemActionButton actionButton1; private ItemActionButton actionButton2; + private View noMediaLabel; private Disposable disposable; private PlaybackController controller; @@ -169,6 +168,7 @@ public class ItemFragment extends Fragment { butAction2Icon = layout.findViewById(R.id.butAction2Icon); butAction1Text = layout.findViewById(R.id.butAction1Text); butAction2Text = layout.findViewById(R.id.butAction2Text); + noMediaLabel = layout.findViewById(R.id.noMediaLabel); butAction1.setOnClickListener(v -> { if (actionButton1 instanceof StreamActionButton && !UserPreferences.isStreamOverDownload() @@ -319,7 +319,9 @@ public class ItemFragment extends Fragment { if (media == null) { actionButton1 = new MarkAsPlayedActionButton(item); actionButton2 = new VisitWebsiteActionButton(item); + noMediaLabel.setVisibility(View.VISIBLE); } else { + noMediaLabel.setVisibility(View.GONE); if (media.getDuration() > 0) { txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); txtvDuration.setContentDescription( diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java index 4f99e8130..c69d46265 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -26,19 +26,15 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.NavListAdapter; -import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.FeedItemUtil; -import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.RenameFeedDialog; import io.reactivex.Observable; @@ -192,41 +188,15 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli new RenameFeedDialog(getActivity(), feed).show(); return true; case R.id.remove_item: - final FeedRemover remover = new FeedRemover(getContext(), feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (selectedNavListIndex == position) { - if (getActivity() instanceof MainActivity) { - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); - } else { - showMainActivity(EpisodesFragment.TAG); - } - } - } - }; - int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg - : R.string.feed_delete_confirmation_msg; - ConfirmationDialog conDialog = new ConfirmationDialog(getContext(), - R.string.remove_feed_label, - getString(messageId, feed.getTitle())) { - @Override - public void onConfirmButtonPressed(DialogInterface dialog) { - dialog.dismiss(); - long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); - if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { - Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); - remover.skipOnCompletion = true; - int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); - if (playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { - IntentUtils.sendLocalBroadcast(getContext(), - PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE); - } + RemoveFeedDialog.show(getContext(), feed, () -> { + if (selectedNavListIndex == position) { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); + } else { + showMainActivity(EpisodesFragment.TAG); } - remover.executeAsync(); } - }; - conDialog.createNewDialog().show(); + }); return true; default: return super.onContextItemSelected(item); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index d748d14c9..1aa66dcbb 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.view.MenuInflater; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.ItemTouchHelper; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java index ba817650b..992b6930c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java @@ -90,7 +90,6 @@ public class OnlineSearchFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_itunes_search, container, false); - setupToolbar(root.findViewById(R.id.toolbar)); root.findViewById(R.id.spinner_country).setVisibility(INVISIBLE); gridView = root.findViewById(R.id.gridView); adapter = new ItunesAdapter(getActivity(), new ArrayList<>()); @@ -110,6 +109,7 @@ public class OnlineSearchFragment extends Fragment { txtvEmpty = root.findViewById(android.R.id.empty); TextView txtvPoweredBy = root.findViewById(R.id.search_powered_by); txtvPoweredBy.setText(getString(R.string.search_powered_by, searchProvider.getName())); + setupToolbar(root.findViewById(R.id.toolbar)); return root; } 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 4549e2a09..973fcb978 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -20,6 +20,7 @@ import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.event.PlaybackHistoryEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; @@ -180,6 +181,12 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI refreshToolbarState(); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + loadItems(); + refreshToolbarState(); + } + private void onFragmentLoaded() { adapter.notifyDataSetChanged(); refreshToolbarState(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java index c994b4d8b..14f355b52 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java @@ -46,6 +46,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. private FeedDiscoverAdapter adapter; private GridView discoverGridLayout; private TextView errorTextView; + private TextView poweredByTextView; private LinearLayout errorView; private Button errorRetry; @@ -63,6 +64,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. errorTextView = root.findViewById(R.id.discover_error_txtV); errorRetry = root.findViewById(R.id.discover_error_retry_btn); errorRetry.setOnClickListener((listener) -> loadToplist()); + poweredByTextView = root.findViewById(R.id.discover_powered_by_itunes); adapter = new FeedDiscoverAdapter((MainActivity) getActivity()); discoverGridLayout.setAdapter(adapter); @@ -110,6 +112,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. discoverGridLayout.setVisibility(View.INVISIBLE); errorView.setVisibility(View.GONE); errorRetry.setVisibility(View.INVISIBLE); + poweredByTextView.setVisibility(View.VISIBLE); ItunesTopListLoader loader = new ItunesTopListLoader(getContext()); SharedPreferences prefs = getActivity().getSharedPreferences(ItunesTopListLoader.PREFS, MODE_PRIVATE); @@ -122,6 +125,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. progressBar.setVisibility(View.GONE); discoverGridLayout.setVisibility(View.INVISIBLE); errorRetry.setVisibility(View.INVISIBLE); + poweredByTextView.setVisibility(View.INVISIBLE); return; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java index 087abc327..fc500a223 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java @@ -6,7 +6,6 @@ import androidx.annotation.NonNull; import androidx.fragment.app.ListFragment; import android.util.Log; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ListView; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index 3b4246e3a..4735cab42 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -33,22 +33,18 @@ import java.util.concurrent.Callable; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.SubscriptionsAdapter; -import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; -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.DownloadRequester; -import de.danoeh.antennapod.core.util.FeedItemUtil; -import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.download.AutoUpdateManager; +import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.FeedSortDialog; import de.danoeh.antennapod.dialog.RenameFeedDialog; @@ -299,45 +295,13 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem new RenameFeedDialog(getActivity(), feed).show(); return true; case R.id.remove_item: - displayRemoveFeedDialog(feed); + RemoveFeedDialog.show(getContext(), feed, null); return true; default: return super.onContextItemSelected(item); } } - private void displayRemoveFeedDialog(Feed feed) { - final FeedRemover remover = new FeedRemover(getContext(), feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - loadSubscriptions(); - } - }; - - int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg - : R.string.feed_delete_confirmation_msg; - String message = getString(messageId, feed.getTitle()); - ConfirmationDialog dialog = new ConfirmationDialog(getContext(), R.string.remove_feed_label, message) { - @Override - public void onConfirmButtonPressed(DialogInterface clickedDialog) { - clickedDialog.dismiss(); - long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); - if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { - Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); - remover.skipOnCompletion = true; - int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); - if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { - IntentUtils.sendLocalBroadcast(getContext(), PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE); - - } - } - remover.executeAsync(); - } - }; - dialog.createNewDialog().show(); - } - private <T> void displayConfirmationDialog(@StringRes int title, @StringRes int message, Callable<? extends T> task) { ConfirmationDialog dialog = new ConfirmationDialog(getActivity(), title, message) { @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java index 641e82b5a..ad815c3c9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java @@ -1,11 +1,14 @@ package de.danoeh.antennapod.fragment.gpodnet; +import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.viewpager2.adapter.FragmentStateAdapter; @@ -15,6 +18,9 @@ import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.discovery.GpodnetPodcastSearcher; +import de.danoeh.antennapod.fragment.OnlineSearchFragment; /** * Main navigation hub for gpodder.net podcast directory @@ -30,9 +36,7 @@ public class GpodnetMainFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View root = inflater.inflate(R.layout.pager_fragment, container, false); - Toolbar toolbar = root.findViewById(R.id.toolbar); - toolbar.setTitle(R.string.gpodnet_main_label); - toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); + setupToolbar(root.findViewById(R.id.toolbar)); ViewPager2 viewPager = root.findViewById(R.id.viewpager); GpodnetPagerAdapter pagerAdapter = new GpodnetPagerAdapter(this); @@ -59,6 +63,33 @@ public class GpodnetMainFragment extends Fragment { return root; } + private void setupToolbar(Toolbar toolbar) { + toolbar.setTitle(R.string.gpodnet_main_label); + toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); + + toolbar.inflateMenu(R.menu.search); + MenuItem searchItem = toolbar.getMenu().findItem(R.id.action_search); + final SearchView sv = (SearchView) searchItem.getActionView(); + sv.setQueryHint(getString(R.string.gpodnet_search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + Activity activity = getActivity(); + if (activity != null) { + searchItem.collapseActionView(); + ((MainActivity) activity).loadChildFragment( + OnlineSearchFragment.newInstance(GpodnetPodcastSearcher.class, query)); + } + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + public static class GpodnetPagerAdapter extends FragmentStateAdapter { GpodnetPagerAdapter(@NonNull Fragment fragment) { @@ -68,20 +99,14 @@ public class GpodnetMainFragment extends Fragment { @NonNull @Override public Fragment createFragment(int position) { - Bundle arguments = new Bundle(); - arguments.putBoolean(PodcastListFragment.ARGUMENT_HIDE_TOOLBAR, true); switch (position) { case POS_TAGS: return new TagListFragment(); case POS_TOPLIST: - PodcastListFragment topListFragment = new PodcastTopListFragment(); - topListFragment.setArguments(arguments); - return topListFragment; + return new PodcastTopListFragment(); default: case POS_SUGGESTIONS: - PodcastListFragment suggestionsFragment = new SuggestionListFragment(); - suggestionsFragment.setArguments(arguments); - return suggestionsFragment; + return new SuggestionListFragment(); } } 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 b3f56941b..1f5434688 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -4,12 +4,9 @@ import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; -import androidx.appcompat.widget.SearchView; import android.util.Log; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -33,7 +30,6 @@ import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; * Displays a list of GPodnetPodcast-Objects in a GridView */ public abstract class PodcastListFragment extends Fragment { - public static final String ARGUMENT_HIDE_TOOLBAR = "hideToolbar"; private static final String TAG = "PodcastListFragment"; private GridView gridView; @@ -44,8 +40,6 @@ public abstract class PodcastListFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.gpodnet_podcast_list, container, false); - setupToolbar(root.findViewById(R.id.toolbar)); - gridView = root.findViewById(R.id.gridView); progressBar = root.findViewById(R.id.progressBar); txtvError = root.findViewById(R.id.txtvError); @@ -59,37 +53,6 @@ public abstract class PodcastListFragment extends Fragment { return root; } - private void setupToolbar(Toolbar toolbar) { - if (getArguments() != null && getArguments().getBoolean(ARGUMENT_HIDE_TOOLBAR, false)) { - toolbar.setVisibility(View.GONE); - return; - } - - toolbar.setTitle(R.string.gpodnet_main_label); - toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); - toolbar.inflateMenu(R.menu.gpodder_podcasts); - - MenuItem searchItem = toolbar.getMenu().findItem(R.id.action_search); - final SearchView sv = (SearchView) searchItem.getActionView(); - sv.setQueryHint(getString(R.string.gpodnet_search_hint)); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - sv.clearFocus(); - MainActivity activity = (MainActivity)getActivity(); - if (activity != null) { - activity.loadChildFragment(SearchListFragment.newInstance(s)); - } - return true; - } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - } - private void onPodcastSelected(GpodnetPodcast selection) { Log.d(TAG, "Selected podcast: " + selection.toString()); Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java deleted file mode 100644 index 72a752bf1..000000000 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java +++ /dev/null @@ -1,80 +0,0 @@ -package de.danoeh.antennapod.fragment.gpodnet; - -import android.os.Bundle; -import androidx.core.view.MenuItemCompat; -import androidx.appcompat.widget.SearchView; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -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 org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.R; - -import java.util.List; - -/** - * Performs a search on the gpodder.net directory and displays the results. - */ -public class SearchListFragment extends PodcastListFragment { - private static final String ARG_QUERY = "query"; - - private String query; - - public static SearchListFragment newInstance(String query) { - SearchListFragment fragment = new SearchListFragment(); - Bundle args = new Bundle(); - args.putString(ARG_QUERY, query); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (getArguments() != null && getArguments().containsKey(ARG_QUERY)) { - this.query = getArguments().getString(ARG_QUERY); - } else { - this.query = ""; - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - // parent already inflated menu - MenuItem searchItem = menu.findItem(R.id.action_search); - final SearchView sv = (SearchView) searchItem.getActionView(); - sv.setQueryHint(getString(R.string.gpodnet_search_hint)); - sv.setQuery(query, false); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - sv.clearFocus(); - changeQuery(s); - return true; - } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - } - - @Override - protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException { - return service.searchPodcasts(query, 0); - } - - private void changeQuery(String query) { - Validate.notNull(query); - - this.query = query; - loadData(); - } -} 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 53a31b68d..2c41ee070 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,19 +1,11 @@ package de.danoeh.antennapod.fragment.gpodnet; -import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; -import androidx.fragment.app.ListFragment; -import androidx.core.view.MenuItemCompat; -import androidx.appcompat.widget.SearchView; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.widget.TextView; - -import de.danoeh.antennapod.R; +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; @@ -28,37 +20,6 @@ public class TagListFragment extends ListFragment { private static final int COUNT = 50; @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.gpodder_podcasts, menu); - MenuItem searchItem = menu.findItem(R.id.action_search); - final SearchView sv = (SearchView) searchItem.getActionView(); - sv.setQueryHint(getString(R.string.gpodnet_search_hint)); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - Activity activity = getActivity(); - if (activity != null) { - sv.clearFocus(); - ((MainActivity) activity).loadChildFragment(SearchListFragment.newInstance(s)); - } - return true; - } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - } - - @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java new file mode 100644 index 000000000..187e8480b --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java @@ -0,0 +1,302 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Paint; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RadioGroup; +import android.widget.TextView; +import android.widget.ViewFlipper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.textfield.TextInputLayout; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice; +import de.danoeh.antennapod.core.util.FileNameGenerator; +import de.danoeh.antennapod.core.util.IntentUtils; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Guides the user through the authentication process. + */ +public class GpodderAuthenticationFragment extends DialogFragment { + public static final String TAG = "GpodnetAuthActivity"; + + private ViewFlipper viewFlipper; + + private static final int STEP_DEFAULT = -1; + private static final int STEP_HOSTNAME = 0; + private static final int STEP_LOGIN = 1; + private static final int STEP_DEVICE = 2; + private static final int STEP_FINISH = 3; + + private int currentStep = -1; + + private GpodnetService service; + private volatile String username; + private volatile String password; + private volatile GpodnetDevice selectedDevice; + private List<GpodnetDevice> devices; + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + dialog.setTitle(GpodnetService.DEFAULT_BASE_HOST); + dialog.setNegativeButton(R.string.cancel_label, null); + dialog.setCancelable(false); + this.setCancelable(false); + + View root = View.inflate(getContext(), R.layout.gpodnetauth_dialog, null); + viewFlipper = root.findViewById(R.id.viewflipper); + advance(); + dialog.setView(root); + + return dialog.create(); + } + + private void setupHostView(View view) { + final Button selectHost = view.findViewById(R.id.chooseHostButton); + final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup); + final EditText serverUrlText = view.findViewById(R.id.serverUrlText); + if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHostname())) { + serverUrlText.setText(GpodnetPreferences.getHostname()); + } + final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput); + serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { + serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE); + }); + selectHost.setOnClickListener(v -> { + if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) { + GpodnetPreferences.setHostname(serverUrlText.getText().toString()); + } else { + GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST); + } + service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname()); + getDialog().setTitle(GpodnetPreferences.getHostname()); + advance(); + }); + } + + private void setupLoginView(View view) { + final EditText username = view.findViewById(R.id.etxtUsername); + final EditText password = view.findViewById(R.id.etxtPassword); + final Button login = view.findViewById(R.id.butLogin); + final TextView txtvError = view.findViewById(R.id.credentialsError); + final ProgressBar progressBar = view.findViewById(R.id.progBarLogin); + final TextView createAccount = view.findViewById(R.id.createAccountButton); + + createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/")); + + password.setOnEditorActionListener((v, actionID, event) -> + actionID == EditorInfo.IME_ACTION_GO && login.performClick()); + + login.setOnClickListener(v -> { + final String usernameStr = username.getText().toString(); + final String passwordStr = password.getText().toString(); + + if (usernameHasUnwantedChars(usernameStr)) { + txtvError.setText(R.string.gpodnetsync_username_characters_error); + txtvError.setVisibility(View.VISIBLE); + return; + } + + login.setEnabled(false); + progressBar.setVisibility(View.VISIBLE); + txtvError.setVisibility(View.GONE); + InputMethodManager inputManager = (InputMethodManager) getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + + Completable.fromAction(() -> { + service.authenticate(usernameStr, passwordStr); + devices = service.getDevices(); + GpodderAuthenticationFragment.this.username = usernameStr; + GpodderAuthenticationFragment.this.password = passwordStr; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + login.setEnabled(true); + progressBar.setVisibility(View.GONE); + advance(); + }, error -> { + login.setEnabled(true); + progressBar.setVisibility(View.GONE); + txtvError.setText(error.getCause().getMessage()); + txtvError.setVisibility(View.VISIBLE); + }); + + }); + } + + private void setupDeviceView(View view) { + final EditText deviceName = view.findViewById(R.id.deviceName); + final LinearLayout devicesContainer = view.findViewById(R.id.devicesContainer); + deviceName.setText(generateDeviceName()); + + MaterialButton createDeviceButton = view.findViewById(R.id.createDeviceButton); + createDeviceButton.setOnClickListener(v -> createDevice(view)); + + for (GpodnetDevice device : devices) { + View row = View.inflate(getContext(), R.layout.gpodnetauth_device_row, null); + Button selectDeviceButton = row.findViewById(R.id.selectDeviceButton); + selectDeviceButton.setOnClickListener(v -> { + selectedDevice = device; + advance(); + }); + selectDeviceButton.setText(device.getCaption()); + devicesContainer.addView(row); + } + } + + private void createDevice(View view) { + final EditText deviceName = view.findViewById(R.id.deviceName); + final TextView txtvError = view.findViewById(R.id.deviceSelectError); + final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice); + + String deviceNameStr = deviceName.getText().toString(); + if (isDeviceInList(deviceNameStr)) { + return; + } + progBarCreateDevice.setVisibility(View.VISIBLE); + txtvError.setVisibility(View.GONE); + deviceName.setEnabled(false); + + Observable.fromCallable(() -> { + String deviceId = generateDeviceId(deviceNameStr); + service.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE); + return new GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(device -> { + progBarCreateDevice.setVisibility(View.GONE); + selectedDevice = device; + advance(); + }, error -> { + deviceName.setEnabled(true); + progBarCreateDevice.setVisibility(View.GONE); + txtvError.setText(error.getMessage()); + txtvError.setVisibility(View.VISIBLE); + }); + } + + private String generateDeviceName() { + String baseName = getString(R.string.gpodnetauth_device_name_default, Build.MODEL); + String name = baseName; + int num = 1; + while (isDeviceInList(name)) { + name = baseName + " (" + num + ")"; + num++; + } + return name; + } + + private String generateDeviceId(String name) { + // devices names must be of a certain form: + // https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices + return FileNameGenerator.generateFileName(name).replaceAll("\\W", "_").toLowerCase(Locale.US); + } + + private boolean isDeviceInList(String name) { + if (devices == null) { + return false; + } + String id = generateDeviceId(name); + for (GpodnetDevice device : devices) { + if (device.getId().equals(id) || device.getCaption().equals(name)) { + return true; + } + } + return false; + } + + private GpodnetDevice findDevice(String id) { + if (devices == null) { + return null; + } + for (GpodnetDevice device : devices) { + if (device.getId().equals(id)) { + return device; + } + } + return null; + } + + private void setupFinishView(View view) { + final Button sync = view.findViewById(R.id.butSyncNow); + + sync.setOnClickListener(v -> { + dismiss(); + SyncService.sync(getContext()); + }); + } + + private void writeLoginCredentials() { + GpodnetPreferences.setUsername(username); + GpodnetPreferences.setPassword(password); + GpodnetPreferences.setDeviceID(selectedDevice.getId()); + } + + private void advance() { + if (currentStep < STEP_FINISH) { + + View view = viewFlipper.getChildAt(currentStep + 1); + if (currentStep == STEP_DEFAULT) { + setupHostView(view); + } else if (currentStep == STEP_HOSTNAME) { + setupLoginView(view); + } else if (currentStep == STEP_LOGIN) { + if (username == null || password == null) { + throw new IllegalStateException("Username and password must not be null here"); + } else { + setupDeviceView(view); + } + } else if (currentStep == STEP_DEVICE) { + if (selectedDevice == null) { + throw new IllegalStateException("Device must not be null here"); + } else { + writeLoginCredentials(); + setupFinishView(view); + } + } + if (currentStep != STEP_DEFAULT) { + viewFlipper.showNext(); + } + currentStep++; + } else { + dismiss(); + } + } + + private boolean usernameHasUnwantedChars(String username) { + Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]"); + Matcher containsUnwantedChars = special.matcher(username); + return containsUnwantedChars.find(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java index bec73894c..4fb734e17 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java @@ -1,15 +1,12 @@ package de.danoeh.antennapod.fragment.preferences; import android.app.Activity; -import android.content.SharedPreferences; import android.os.Bundle; import androidx.core.text.HtmlCompat; -import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import android.text.Spanned; import android.text.format.DateUtils; -import android.widget.Toast; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; @@ -17,19 +14,16 @@ import de.danoeh.antennapod.core.event.SyncServiceEvent; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.sync.SyncService; import de.danoeh.antennapod.dialog.AuthenticationDialog; -import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; - public class GpodderPreferencesFragment extends PreferenceFragmentCompat { private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate"; private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; private static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync"; private static final String PREF_GPODNET_FORCE_FULL_SYNC = "pref_gpodnet_force_full_sync"; private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; - private static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname"; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -54,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) public void syncStatusChanged(SyncServiceEvent event) { + updateGpodnetPreferenceScreen(); if (!GpodnetPreferences.loggedIn()) { return; } @@ -69,6 +64,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { private void setupGpodderScreen() { final Activity activity = getActivity(); + findPreference(PREF_GPODNET_LOGIN).setOnPreferenceClickListener(preference -> { + new GpodderAuthenticationFragment().show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG); + return true; + }); findPreference(PREF_GPODNET_SETLOGIN_INFORMATION) .setOnPreferenceClickListener(preference -> { AuthenticationDialog dialog = new AuthenticationDialog(activity, @@ -97,11 +96,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { updateGpodnetPreferenceScreen(); return true; }); - findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> { - GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener( - dialog -> updateGpodnetPreferenceScreen()); - return true; - }); } private void updateGpodnetPreferenceScreen() { @@ -122,7 +116,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { } else { findPreference(PREF_GPODNET_LOGOUT).setSummary(null); } - findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname()); } private void updateLastGpodnetSyncReport(boolean successful, long lastTime) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 99fd12021..7bf602e35 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -1,8 +1,11 @@ package de.danoeh.antennapod.fragment.preferences; import android.content.Intent; +import android.os.Build; import android.os.Bundle; +import android.provider.Settings; import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import com.bytehamster.lib.preferencesearch.SearchConfiguration; import com.bytehamster.lib.preferencesearch.SearchPreference; @@ -20,7 +23,7 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork"; private static final String PREF_SCREEN_GPODDER = "prefScreenGpodder"; private static final String PREF_SCREEN_STORAGE = "prefScreenStorage"; - private static final String PREF_FAQ = "prefFaq"; + private static final String PREF_DOCUMENTATION = "prefDocumentation"; private static final String PREF_VIEW_FORUM = "prefViewForum"; private static final String PREF_SEND_BUG_REPORT = "prefSendBugReport"; private static final String PREF_CATEGORY_PROJECT = "project"; @@ -35,10 +38,18 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { setupSearch(); // If you are writing a spin-off, please update the details on screens like "About" and "Report bug" - // and afterwards remove the following lines. + // and afterwards remove the following lines. Please keep in mind that AntennaPod is licensed under the GPL. + // This means that your application needs to be open-source under the GPL, too. + // It must also include a prominent copyright notice. String packageName = getContext().getPackageName(); if (!"de.danoeh.antennapod".equals(packageName) && !"de.danoeh.antennapod.debug".equals(packageName)) { findPreference(PREF_CATEGORY_PROJECT).setVisible(false); + Preference copyrightNotice = new Preference(getContext()); + copyrightNotice.setSummary("This application is based on AntennaPod." + + " The AntennaPod team does NOT provide support for this unofficial version." + + " If you can read this message, the developers of this modification" + + " violate the GNU General Public License (GPL)."); + findPreference(PREF_CATEGORY_PROJECT).getParent().addPreference(copyrightNotice); } } @@ -70,10 +81,16 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { return true; }); findPreference(PREF_NOTIFICATION).setOnPreferenceClickListener(preference -> { - ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications); + if (Build.VERSION.SDK_INT >= 26) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, getActivity().getPackageName()); + startActivity(intent); + } else { + ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications); + } return true; }); - findPreference(PREF_ABOUT).setOnPreferenceClickListener( preference -> { getParentFragmentManager().beginTransaction().replace(R.id.content, new AboutFragment()) @@ -88,8 +105,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { return true; } ); - findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> { - IntentUtils.openInBrowser(getContext(), "https://antennapod.org/faq.html"); + findPreference(PREF_DOCUMENTATION).setOnPreferenceClickListener(preference -> { + IntentUtils.openInBrowser(getContext(), "https://antennapod.org/documentation/"); return true; }); findPreference(PREF_VIEW_FORUM).setOnPreferenceClickListener(preference -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java index fcc37f644..77f8063f2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java @@ -15,7 +15,6 @@ import org.apache.commons.lang3.ArrayUtils; import java.util.Calendar; import java.util.GregorianCalendar; -import java.util.Locale; import java.util.concurrent.TimeUnit; public class NetworkPreferencesFragment extends PreferenceFragmentCompat { @@ -97,8 +96,7 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { private void setParallelDownloadsText(int downloads) { final Resources res = getActivity().getResources(); - String s = String.format(Locale.getDefault(), "%d%s", - downloads, res.getString(R.string.parallel_downloads_suffix)); + String s = res.getString(R.string.parallel_downloads, downloads); findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java index b440d053b..0a64bbe71 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java @@ -33,7 +33,7 @@ public class AboutFragment extends PreferenceFragmentCompat { return true; }); findPreference("about_privacy_policy").setOnPreferenceClickListener((preference) -> { - IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy.html"); + IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy/"); return true; }); findPreference("about_licenses").setOnPreferenceClickListener((preference) -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java index 60d9f95dd..b844234b7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java @@ -6,8 +6,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.SimpleIconListAdapter; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java index 6db1389ea..d759a5ff2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java @@ -6,8 +6,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.SimpleIconListAdapter; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java index e8d8e113b..b77c74de6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java @@ -6,8 +6,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.SimpleIconListAdapter; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index 3d8f3fe09..1eecccb4c 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -48,65 +48,43 @@ public class FeedItemMenuHandler { if (menu == null || selectedItem == null) { return false; } - boolean hasMedia = selectedItem.getMedia() != null; - boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING; - - if (!isPlaying) { - setItemVisibility(menu, R.id.skip_episode_item, false); - } - boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE); - if (!isInQueue) { - setItemVisibility(menu, R.id.remove_from_queue_item, false); - } - if (!(!isInQueue && selectedItem.getMedia() != null)) { - setItemVisibility(menu, R.id.add_to_queue_item, false); - } - if (!ShareUtils.hasLinkToShare(selectedItem)) { - setItemVisibility(menu, R.id.visit_website_item, false); - } - - boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists(); + final boolean hasMedia = selectedItem.getMedia() != null; + final boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING; + final boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE); + final boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists(); + final boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE); + setItemVisibility(menu, R.id.skip_episode_item, isPlaying); + setItemVisibility(menu, R.id.remove_from_queue_item, isInQueue); + setItemVisibility(menu, R.id.add_to_queue_item, !isInQueue && selectedItem.getMedia() != null); + setItemVisibility(menu, R.id.visit_website_item, !selectedItem.getFeed().isLocalFeed() + && ShareUtils.hasLinkToShare(selectedItem)); + setItemVisibility(menu, R.id.share_item, !selectedItem.getFeed().isLocalFeed()); setItemVisibility(menu, R.id.remove_new_flag_item, selectedItem.isNew()); - if (selectedItem.isPlayed()) { - setItemVisibility(menu, R.id.mark_read_item, false); - } else { - setItemVisibility(menu, R.id.mark_unread_item, false); - } - - if (selectedItem.getMedia() == null || selectedItem.getMedia().getPosition() == 0) { - setItemVisibility(menu, R.id.reset_position, false); - } + setItemVisibility(menu, R.id.mark_read_item, !selectedItem.isPlayed()); + setItemVisibility(menu, R.id.mark_unread_item, selectedItem.isPlayed()); + setItemVisibility(menu, R.id.reset_position, hasMedia && selectedItem.getMedia().getPosition() != 0); if (!UserPreferences.isEnableAutodownload() || fileDownloaded || selectedItem.getFeed().isLocalFeed()) { setItemVisibility(menu, R.id.activate_auto_download, false); setItemVisibility(menu, R.id.deactivate_auto_download, false); - } else if (selectedItem.getAutoDownload()) { - setItemVisibility(menu, R.id.activate_auto_download, false); } else { - setItemVisibility(menu, R.id.deactivate_auto_download, false); + setItemVisibility(menu, R.id.activate_auto_download, !selectedItem.getAutoDownload()); + setItemVisibility(menu, R.id.deactivate_auto_download, selectedItem.getAutoDownload()); } // Display proper strings when item has no media - if (!hasMedia && !selectedItem.isPlayed()) { + if (hasMedia) { + setItemTitle(menu, R.id.mark_read_item, R.string.mark_read_label); + setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label); + } else { setItemTitle(menu, R.id.mark_read_item, R.string.mark_read_no_media_label); - } - - if (!hasMedia && selectedItem.isPlayed()) { setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label_no_media); } - boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE); setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite); setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite); - setItemVisibility(menu, R.id.remove_item, fileDownloaded); - - if (selectedItem.getFeed().isLocalFeed()) { - setItemVisibility(menu, R.id.visit_website_item, false); - setItemVisibility(menu, R.id.share_item, false); - } - return true; } @@ -134,7 +112,7 @@ public class FeedItemMenuHandler { * @param id The id of the string that is going to be replaced. * @param noMedia The id of the new String that is going to be used. * */ - public static void setItemTitle(Menu menu, int id, int noMedia){ + public static void setItemTitle(Menu menu, int id, int noMedia) { MenuItem item = menu.findItem(id); if (item != null) { item.setTitle(noMedia); diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java index 9ceed9369..0086a75ab 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.DialogInterface; import android.util.Log; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import androidx.annotation.NonNull; diff --git a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java index f755a4c84..2fd570ece 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java +++ b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java @@ -11,7 +11,8 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.util.ThemeUtils; public class CircularProgressBar extends View { - private static final float EPSILON = 0.005f; + 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(); @@ -74,11 +75,11 @@ public class CircularProgressBar extends View { bounds.set(padding, padding, getWidth() - padding, getHeight() - padding); canvas.drawArc(bounds, 0, 360, false, paintBackground); - if (percentage > EPSILON && 1 - percentage > EPSILON) { + if (MINIMUM_PERCENTAGE <= percentage && percentage <= MAXIMUM_PERCENTAGE) { canvas.drawArc(bounds, -90, percentage * 360, false, paintProgress); } - if (Math.abs(percentage - targetPercentage) > EPSILON) { + if (Math.abs(percentage - targetPercentage) > MINIMUM_PERCENTAGE) { float speed = 0.02f; if (Math.abs(targetPercentage - percentage) < 0.1 && targetPercentage > percentage) { speed = 0.006f; diff --git a/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java deleted file mode 100644 index 10ed98769..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java +++ /dev/null @@ -1,124 +0,0 @@ -package de.danoeh.antennapod.view; - -import android.animation.ArgbEvaluator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.Nullable; -import androidx.core.text.TextUtilsCompat; -import androidx.core.view.ViewCompat; -import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager2.widget.ViewPager2; - -import java.util.Locale; - -public class PagerIndicatorView extends View { - private final Paint paint = new Paint(); - private float position = 0; - private int numPages = 0; - private int disabledPage = -1; - private int circleColor = 0; - private int circleColorHighlight = -1; - private boolean isLocaleRtl = false; - - public PagerIndicatorView(Context context) { - super(context); - setup(); - } - - public PagerIndicatorView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - setup(); - } - - public PagerIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setup(); - } - - private void setup() { - isLocaleRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) - == ViewCompat.LAYOUT_DIRECTION_RTL; - - paint.setAntiAlias(true); - paint.setStyle(Paint.Style.FILL); - - int[] colorAttrs = new int[]{android.R.attr.textColorSecondary}; - TypedArray a = getContext().obtainStyledAttributes(colorAttrs); - circleColorHighlight = a.getColor(0, 0xffffffff); - circleColor = (Integer) new ArgbEvaluator().evaluate(0.8f, 0x00ffffff, circleColorHighlight); - a.recycle(); - } - - /** - * Visual and logical position distinction only happens in RTL locales (e.g. Persian) - * where pages positions are flipped thus it does nothing in LTR locales (e.g. English) - */ - private float logicalPositionToVisual(float position) { - return isLocaleRtl ? numPages - 1 - position : position; - } - - public void setViewPager(ViewPager2 pager) { - numPages = pager.getAdapter().getItemCount(); - pager.getAdapter().registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - numPages = pager.getAdapter().getItemCount(); - invalidate(); - } - }); - pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - PagerIndicatorView.this.position = logicalPositionToVisual( - position + positionOffset); - invalidate(); - } - }); - } - - public void setDisabledPage(int disabledPage) { - this.disabledPage = (int) logicalPositionToVisual(disabledPage); - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - for (int i = 0; i < numPages; i++) { - if ((int) Math.floor(position) == i) { - // This is the current dot - drawCircle(canvas, i, (float) (1 - (position - Math.floor(position)))); - } else if ((int) Math.ceil(position) == i) { - // This is the next dot - drawCircle(canvas, i, (float) (position - Math.floor(position))); - } else { - drawCircle(canvas, i, 0); - } - } - } - - private void drawCircle(Canvas canvas, int position, float frac) { - float availableHeight = canvas.getHeight() - getPaddingTop() - getPaddingBottom(); - float circleRadiusSmall = availableHeight * 0.26f; - float circleRadiusBig = availableHeight * 0.35f; - float circleRadiusDelta = (circleRadiusBig - circleRadiusSmall); - float start = 0.5f * (canvas.getWidth() - numPages * 1.5f * availableHeight); - paint.setStrokeWidth(availableHeight * 0.3f); - - if (position == disabledPage) { - paint.setStyle(Paint.Style.STROKE); - } else { - paint.setStyle(Paint.Style.FILL_AND_STROKE); - } - - paint.setColor((Integer) new ArgbEvaluator().evaluate(frac, circleColor, circleColorHighlight)); - canvas.drawCircle(start + (position * 1.5f + 0.75f) * availableHeight, 0.5f * availableHeight + getPaddingTop(), - circleRadiusSmall + frac * circleRadiusDelta, paint); - } -}
\ No newline at end of file |