diff options
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod')
122 files changed, 3511 insertions, 2517 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java b/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java index ea2166674..061ea9ae2 100644 --- a/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java +++ b/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java @@ -9,6 +9,9 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -32,13 +35,7 @@ public class CrashReportWriter implements Thread.UncaughtExceptionHandler { PrintWriter out = null; try { out = new PrintWriter(new FileWriter(path)); - out.println("[ Environment ]"); - out.println("Android version: " + Build.VERSION.RELEASE); - out.println("OS version: " + System.getProperty("os.version")); - out.println("AntennaPod version: " + BuildConfig.VERSION_NAME); - out.println("Model: " + Build.MODEL); - out.println("Device: " + Build.DEVICE); - out.println("Product: " + Build.PRODUCT); + out.println(getSystemInfo()); out.println(); out.println("[ StackTrace ]"); ex.printStackTrace(out); @@ -49,4 +46,15 @@ public class CrashReportWriter implements Thread.UncaughtExceptionHandler { } defaultHandler.uncaughtException(thread, ex); } + + public static String getSystemInfo() { + return "[ Environment ]" + + "\nAndroid version: " + Build.VERSION.RELEASE + + "\nOS version: " + System.getProperty("os.version") + + "\nAntennaPod version: " + BuildConfig.VERSION_NAME + + "\nModel: " + Build.MODEL + + "\nDevice: " + Build.DEVICE + + "\nProduct: " + Build.PRODUCT + + "\nTime: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()) + "\n"; + } } diff --git a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java index cb2f597d6..4e6b8fa29 100644 --- a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java +++ b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod; import android.app.Application; -import android.os.Build; import android.os.StrictMode; import com.joanzapata.iconify.Iconify; @@ -10,7 +9,6 @@ import com.joanzapata.iconify.fonts.MaterialModule; import de.danoeh.antennapod.core.ApCoreEventBusIndex; import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.spa.SPAUtil; import org.greenrobot.eventbus.EventBus; @@ -26,44 +24,43 @@ public class PodcastApp extends Application { } } - private static PodcastApp singleton; + private static PodcastApp singleton; - public static PodcastApp getInstance() { - return singleton; - } + public static PodcastApp getInstance() { + return singleton; + } - @Override - public void onCreate() { - super.onCreate(); + @Override + public void onCreate() { + super.onCreate(); - Thread.setDefaultUncaughtExceptionHandler(new CrashReportWriter()); + Thread.setDefaultUncaughtExceptionHandler(new CrashReportWriter()); - if(BuildConfig.DEBUG) { - StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder() - .detectLeakedSqlLiteObjects() - .penaltyLog() - .penaltyDropBox(); - builder.detectActivityLeaks(); - builder.detectLeakedClosableObjects(); - if(Build.VERSION.SDK_INT >= 16) { - builder.detectLeakedRegistrationObjects(); - } - StrictMode.setVmPolicy(builder.build()); - } + if (BuildConfig.DEBUG) { + StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder() + .detectLeakedSqlLiteObjects() + .penaltyLog() + .penaltyDropBox() + .detectActivityLeaks() + .detectLeakedClosableObjects() + .detectLeakedRegistrationObjects(); + StrictMode.setVmPolicy(builder.build()); + } - singleton = this; + singleton = this; - ClientConfig.initialize(this); + ClientConfig.initialize(this); - EventDistributor.getInstance(); - Iconify.with(new FontAwesomeModule()); - Iconify.with(new MaterialModule()); + Iconify.with(new FontAwesomeModule()); + Iconify.with(new MaterialModule()); SPAUtil.sendSPAppsQueryFeedsIntent(this); - EventBus.builder() - .addIndex(new ApEventBusIndex()) - .addIndex(new ApCoreEventBusIndex()) - .installDefaultEventBus(); + EventBus.builder() + .addIndex(new ApEventBusIndex()) + .addIndex(new ApCoreEventBusIndex()) + .logNoSubscriberMessages(false) + .sendNoSubscriberEvent(false) + .installDefaultEventBus(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java deleted file mode 100644 index 1bcdada44..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java +++ /dev/null @@ -1,160 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.LinearLayout; - -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -/** - * Displays the 'about' screen - */ -public class AboutActivity extends AppCompatActivity { - - private static final String TAG = AboutActivity.class.getSimpleName(); - - private WebView webView; - private LinearLayout webViewContainer; - private Disposable disposable; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayShowHomeEnabled(true); - setContentView(R.layout.about); - webViewContainer = findViewById(R.id.webViewContainer); - webView = findViewById(R.id.webViewAbout); - webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - webView.setBackgroundColor(Color.TRANSPARENT); - webView.setWebViewClient(new WebViewClient() { - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.startsWith("http")) { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent); - return true; - } else { - url = url.replace("file:///android_asset/", ""); - loadAsset(url); - return true; - } - } - - }); - loadAsset("about.html"); - } - - private void loadAsset(String filename) { - disposable = Single.create(subscriber -> { - InputStream input = null; - try { - TypedArray res = AboutActivity.this.getTheme().obtainStyledAttributes( - new int[] { R.attr.about_screen_font_color, R.attr.about_screen_background, - R.attr.about_screen_card_background, R.attr.about_screen_card_border}); - String fontColor = String.format("#%06X", 0xFFFFFF & res.getColor(0, 0)); - String backgroundColor = String.format("#%06X", 0xFFFFFF & res.getColor(1, 0)); - String cardBackground = String.format("#%06X", 0xFFFFFF & res.getColor(2, 0)); - String cardBorder = String.format("#%06X", 0xFFFFFF & res.getColor(3, 0)); - res.recycle(); - input = getAssets().open(filename); - String webViewData = IOUtils.toString(input, Charset.defaultCharset()); - if (!webViewData.startsWith("<!DOCTYPE html>")) { - webViewData = webViewData.replace("%", "%"); - webViewData = - "<!DOCTYPE html>" + - "<html>" + - "<head>" + - " <meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">" + - " <style type=\"text/css\">" + - " @font-face {" + - " font-family: 'Roboto-Light';" + - " src: url('file:///android_asset/Roboto-Light.ttf');" + - " }" + - " * {" + - " color: @fontcolor@;" + - " font-family: roboto-Light;" + - " font-size: 8pt;" + - " }" + - " </style>" + - "</head><body><p>" + webViewData + "</p></body></html>"; - webViewData = webViewData.replace("\n", "<br/>"); - } - webViewData = webViewData.replace("@fontcolor@", fontColor); - webViewData = webViewData.replace("@background@", backgroundColor); - webViewData = webViewData.replace("@card_background@", cardBackground); - webViewData = webViewData.replace("@card_border@", cardBorder); - subscriber.onSuccess(webViewData); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - subscriber.onError(e); - } finally { - IOUtils.closeQuietly(input); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - webViewData -> - webView.loadDataWithBaseURL("file:///android_asset/", webViewData.toString(), "text/html", "utf-8", "file:///android_asset/" + filename.toString()), - error -> Log.e(TAG, Log.getStackTraceString(error)) - ); - } - - @Override - public void onBackPressed() { - if (webView.canGoBack()) { - webView.goBack(); - } else { - super.onBackPressed(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (disposable != null) { - disposable.dispose(); - } - if (webViewContainer != null && webView != null) { - webViewContainer.removeAllViews(); - webView.destroy(); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java index 2321a3602..7f8c14b03 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.activity; import android.content.Intent; -import android.support.v4.view.ViewCompat; +import androidx.core.view.ViewCompat; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -12,6 +12,8 @@ import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.preferences.PlaybackPreferences; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.dialog.VariableSpeedDialog; @@ -60,11 +62,13 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { } if (controller == null) { butPlaybackSpeed.setVisibility(View.GONE); + txtvPlaybackSpeed.setVisibility(View.GONE); return; } updatePlaybackSpeedButtonText(); ViewCompat.setAlpha(butPlaybackSpeed, controller.canSetPlaybackSpeed() ? 1.0f : 0.5f); butPlaybackSpeed.setVisibility(View.VISIBLE); + txtvPlaybackSpeed.setVisibility(View.VISIBLE); } @Override @@ -74,14 +78,15 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { } if (controller == null) { butPlaybackSpeed.setVisibility(View.GONE); + txtvPlaybackSpeed.setVisibility(View.GONE); return; } float speed = 1.0f; if(controller.canSetPlaybackSpeed()) { - speed = UserPreferences.getPlaybackSpeed(); + speed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(controller.getMedia()); } - String speedStr = new DecimalFormat("0.00x").format(speed); - butPlaybackSpeed.setText(speedStr); + String speedStr = new DecimalFormat("0.00").format(speed); + txtvPlaybackSpeed.setText(speedStr); } @Override @@ -102,7 +107,9 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US); format.setDecimalSeparator('.'); - String currentSpeed = new DecimalFormat("0.00", format).format(UserPreferences.getPlaybackSpeed()); + + float currentSpeedValue = controller.getCurrentPlaybackSpeedMultiplier(); + String currentSpeed = new DecimalFormat("0.00", format).format(currentSpeedValue); // Provide initial value in case the speed list has changed // out from under us @@ -124,6 +131,12 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { break; } } + + try { + PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(Float.parseFloat(newSpeed)); + } catch (NumberFormatException e) { + // Well this was awkward... + } UserPreferences.setPlaybackSpeed(newSpeed); controller.setPlaybackSpeed(Float.parseFloat(newSpeed)); onPositionObserverUpdate(); @@ -136,6 +149,7 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { return true; }); butPlaybackSpeed.setVisibility(View.VISIBLE); + txtvPlaybackSpeed.setVisibility(View.VISIBLE); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java new file mode 100644 index 000000000..666eacfa8 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java @@ -0,0 +1,55 @@ +package de.danoeh.antennapod.activity; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Bundle; +import com.google.android.material.snackbar.Snackbar; +import androidx.appcompat.app.AppCompatActivity; +import android.widget.TextView; +import de.danoeh.antennapod.CrashReportWriter; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.IntentUtils; +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Displays the 'crash report' screen + */ +public class BugReportActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayShowHomeEnabled(true); + setContentView(R.layout.bug_report); + + TextView crashDetailsText = findViewById(R.id.crash_report_logs); + + try { + File crashFile = CrashReportWriter.getFile(); + String crashReportContent = IOUtils.toString(new FileInputStream(crashFile), Charset.forName("UTF-8")); + crashDetailsText.setText(crashReportContent); + } catch (IOException e) { + e.printStackTrace(); + crashDetailsText.setText("No crash report recorded\n" + CrashReportWriter.getSystemInfo()); + } + + findViewById(R.id.btn_open_bug_tracker).setOnClickListener(v -> { + IntentUtils.openInBrowser(BugReportActivity.this, "https://github.com/AntennaPod/AntennaPod/issues"); + }); + + findViewById(R.id.btn_copy_log).setOnClickListener(v -> { + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(getString(R.string.bug_report_title), crashDetailsText.getText()); + clipboard.setPrimaryClip(clip); + Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show(); + }); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/CastplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/CastplayerActivity.java index 871e9c279..c60c7b769 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/CastplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/CastplayerActivity.java @@ -49,6 +49,7 @@ public class CastplayerActivity extends MediaplayerInfoActivity { super.setupGUI(); if (butPlaybackSpeed != null) { butPlaybackSpeed.setVisibility(View.GONE); + txtvPlaybackSpeed.setVisibility(View.GONE); } // if (butCastDisconnect != null) { // butCastDisconnect.setOnClickListener(v -> castManager.disconnect()); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java index 33def125e..49ce954bc 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java @@ -5,9 +5,9 @@ import android.content.Intent; import android.os.Bundle; import android.os.Environment; import android.os.FileObserver; -import android.support.v4.app.NavUtils; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import androidx.core.app.NavUtils; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; 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 5e04d743d..08ebc6421 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java @@ -3,8 +3,8 @@ package de.danoeh.antennapod.activity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java deleted file mode 100644 index 26e360bd3..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ /dev/null @@ -1,225 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.ClipData; -import android.content.Context; -import android.content.Intent; -import android.graphics.LightingColorFilter; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.text.TextUtils; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.joanzapata.iconify.Iconify; - -import org.apache.commons.lang3.StringUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.glide.FastBlurTransformation; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.LangUtils; -import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; -import de.danoeh.antennapod.menuhandler.FeedMenuHandler; -import io.reactivex.Maybe; -import io.reactivex.MaybeOnSubscribe; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -/** - * Displays information about a feed. - */ -public class FeedInfoActivity extends AppCompatActivity { - - public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; - private static final String TAG = "FeedInfoActivity"; - private Feed feed; - - private ImageView imgvCover; - private TextView txtvTitle; - private TextView txtvDescription; - private TextView lblLanguage; - private TextView txtvLanguage; - private TextView lblAuthor; - private TextView txtvAuthor; - private TextView txtvUrl; - - private Disposable disposable; - - - private final View.OnClickListener copyUrlToClipboard = new View.OnClickListener() { - @Override - public void onClick(View v) { - if(feed != null && feed.getDownload_url() != null) { - String url = feed.getDownload_url(); - ClipData clipData = ClipData.newPlainText(url, url); - android.content.ClipboardManager cm = (android.content.ClipboardManager) FeedInfoActivity.this - .getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(clipData); - Toast t = Toast.makeText(FeedInfoActivity.this, R.string.copied_url_msg, Toast.LENGTH_SHORT); - t.show(); - } - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - setContentView(R.layout.feedinfo); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - long feedId = getIntent().getLongExtra(EXTRA_FEED_ID, -1); - - imgvCover = findViewById(R.id.imgvCover); - txtvTitle = findViewById(R.id.txtvTitle); - TextView txtvAuthorHeader = findViewById(R.id.txtvAuthor); - ImageView imgvBackground = findViewById(R.id.imgvBackground); - findViewById(R.id.butShowInfo).setVisibility(View.INVISIBLE); - findViewById(R.id.butShowSettings).setVisibility(View.INVISIBLE); - // https://github.com/bumptech/glide/issues/529 - imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); - - - txtvDescription = findViewById(R.id.txtvDescription); - lblLanguage = findViewById(R.id.lblLanguage); - txtvLanguage = findViewById(R.id.txtvLanguage); - lblAuthor = findViewById(R.id.lblAuthor); - txtvAuthor = findViewById(R.id.txtvDetailsAuthor); - txtvUrl = findViewById(R.id.txtvUrl); - - txtvUrl.setOnClickListener(copyUrlToClipboard); - - disposable = Maybe.create((MaybeOnSubscribe<Feed>) emitter -> { - Feed feed = DBReader.getFeed(feedId); - if (feed != null) { - emitter.onSuccess(feed); - } else { - emitter.onComplete(); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - feed = result; - Log.d(TAG, "Language is " + feed.getLanguage()); - Log.d(TAG, "Author is " + feed.getAuthor()); - Log.d(TAG, "URL is " + feed.getDownload_url()); - Glide.with(FeedInfoActivity.this) - .load(feed.getImageLocation()) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate()) - .into(imgvCover); - Glide.with(FeedInfoActivity.this) - .load(feed.getImageLocation()) - .apply(new RequestOptions() - .placeholder(R.color.image_readability_tint) - .error(R.color.image_readability_tint) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .transform(new FastBlurTransformation()) - .dontAnimate()) - .into(imgvBackground); - - txtvTitle.setText(feed.getTitle()); - - String description = feed.getDescription(); - if(description != null) { - if(Feed.TYPE_ATOM1.equals(feed.getType())) { - HtmlToPlainText formatter = new HtmlToPlainText(); - Document feedDescription = Jsoup.parse(feed.getDescription()); - description = StringUtils.trim(formatter.getPlainText(feedDescription)); - } - } else { - description = ""; - } - txtvDescription.setText(description); - - if (!TextUtils.isEmpty(feed.getAuthor())) { - txtvAuthor.setText(feed.getAuthor()); - txtvAuthorHeader.setText(feed.getAuthor()); - } else { - lblAuthor.setVisibility(View.GONE); - txtvAuthor.setVisibility(View.GONE); - } - if (!TextUtils.isEmpty(feed.getLanguage())) { - txtvLanguage.setText(LangUtils.getLanguageString(feed.getLanguage())); - } else { - lblLanguage.setVisibility(View.GONE); - txtvLanguage.setVisibility(View.GONE); - } - txtvUrl.setText(feed.getDownload_url() + " {fa-paperclip}"); - Iconify.addIcons(txtvUrl); - - supportInvalidateOptionsMenu(); - }, error -> { - Log.d(TAG, Log.getStackTraceString(error)); - finish(); - }, () -> { - Log.e(TAG, "Activity was started with invalid arguments"); - finish(); - }); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (disposable != null) { - disposable.dispose(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.feedinfo, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null); - menu.findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null && - IntentUtils.isCallable(this, new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink())))); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - try { - return FeedMenuHandler.onOptionsItemClicked(this, item, feed); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, - e.getMessage()); - } - return super.onOptionsItemSelected(item); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedSettingsActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedSettingsActivity.java deleted file mode 100644 index fbd19f88a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/FeedSettingsActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.arch.lifecycle.ViewModelProviders; -import android.graphics.LightingColorFilter; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; -import android.text.TextUtils; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.glide.FastBlurTransformation; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.fragment.FeedSettingsFragment; -import de.danoeh.antennapod.viewmodel.FeedSettingsViewModel; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -/** - * Displays information about a feed. - */ -public class FeedSettingsActivity extends AppCompatActivity { - public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; - private static final String TAG = "FeedSettingsActivity"; - private Feed feed; - private Disposable disposable; - private ImageView imgvCover; - private TextView txtvTitle; - private ImageView imgvBackground; - private TextView txtvAuthorHeader; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - setContentView(R.layout.feedsettings); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - imgvCover = findViewById(R.id.imgvCover); - txtvTitle = findViewById(R.id.txtvTitle); - txtvAuthorHeader = findViewById(R.id.txtvAuthor); - imgvBackground = findViewById(R.id.imgvBackground); - findViewById(R.id.butShowInfo).setVisibility(View.INVISIBLE); - findViewById(R.id.butShowSettings).setVisibility(View.INVISIBLE); - // https://github.com/bumptech/glide/issues/529 - imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); - - long feedId = getIntent().getLongExtra(EXTRA_FEED_ID, -1); - disposable = ViewModelProviders.of(this).get(FeedSettingsViewModel.class).getFeed(feedId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - feed = result; - showFragment(); - showHeader(); - }, error -> { - Log.d(TAG, Log.getStackTraceString(error)); - finish(); - }, () -> { - Log.e(TAG, "Activity was started with invalid arguments"); - finish(); - }); - } - - private void showFragment() { - FeedSettingsFragment fragment = new FeedSettingsFragment(); - fragment.setArguments(getIntent().getExtras()); - - FragmentManager fragmentManager = getSupportFragmentManager(); - FragmentTransaction fragmentTransaction = - fragmentManager.beginTransaction(); - fragmentTransaction.replace(R.id.settings_fragment_container, fragment); - fragmentTransaction.commit(); - } - - private void showHeader() { - txtvTitle.setText(feed.getTitle()); - - if (!TextUtils.isEmpty(feed.getAuthor())) { - txtvAuthorHeader.setText(feed.getAuthor()); - } - - Glide.with(FeedSettingsActivity.this) - .load(feed.getImageLocation()) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate()) - .into(imgvCover); - Glide.with(FeedSettingsActivity.this) - .load(feed.getImageLocation()) - .apply(new RequestOptions() - .placeholder(R.color.image_readability_tint) - .error(R.color.image_readability_tint) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .transform(new FastBlurTransformation()) - .dontAnimate()) - .into(imgvBackground); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (disposable != null) { - disposable.dispose(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java index 9795c1240..f85a1cd77 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java @@ -7,10 +7,10 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.support.design.widget.Snackbar; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import com.google.android.material.snackbar.Snackbar; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.MenuItem; 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 339ce01c2..0023e6d7f 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.activity; import android.annotation.TargetApi; import android.app.ProgressDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; @@ -10,15 +11,16 @@ import android.database.DataSetObserver; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.support.annotation.VisibleForTesting; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.Toolbar; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import com.google.android.material.snackbar.Snackbar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.util.TypedValue; import android.view.ContextMenu; @@ -32,9 +34,13 @@ import android.widget.Toast; import com.bumptech.glide.Glide; -import de.danoeh.antennapod.preferences.PreferenceUpgrader; +import de.danoeh.antennapod.core.event.FeedListUpdateEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.Validate; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import java.util.List; @@ -43,9 +49,7 @@ 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.MessageEvent; -import de.danoeh.antennapod.core.event.ProgressEvent; import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -66,14 +70,13 @@ import de.danoeh.antennapod.fragment.FeedItemlistFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; +import de.danoeh.antennapod.fragment.TransitionEffect; import de.danoeh.antennapod.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.preferences.PreferenceUpgrader; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; /** * The activity that is shown when the user launches the app. @@ -82,9 +85,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi private static final String TAG = "MainActivity"; - private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE - | EventDistributor.UNREAD_ITEMS_UPDATE; - public static final String PREF_NAME = "MainActivityPrefs"; public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch"; public static final String PREF_LAST_FRAGMENT_TAG = "prefMainActivityLastFragmentTag"; @@ -93,7 +93,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi public static final String EXTRA_NAV_INDEX = "nav_index"; public static final String EXTRA_FRAGMENT_TAG = "fragment_tag"; public static final String EXTRA_FRAGMENT_ARGS = "fragment_args"; - public static final String EXTRA_FEED_ID = "fragment_feed_id"; + private static final String EXTRA_FEED_ID = "fragment_feed_id"; private static final String SAVE_BACKSTACK_COUNT = "backstackCount"; private static final String SAVE_TITLE = "title"; @@ -127,6 +127,14 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi private long lastBackButtonPressTime = 0; + @NonNull + public static Intent getIntentToOpenFeed(@NonNull Context context, long feedId) { + Intent intent = new Intent(context.getApplicationContext(), MainActivity.class); + intent.putExtra(MainActivity.EXTRA_FEED_ID, feedId); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + return intent; + } + @Override public void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getNoTitleTheme()); @@ -240,7 +248,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi SharedPreferences.Editor edit = prefs.edit(); edit.putBoolean(PREF_IS_FIRST_LAUNCH, false); - edit.commit(); + edit.apply(); } } @@ -309,8 +317,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi fragment = new AddFeedFragment(); break; case SubscriptionFragment.TAG: - SubscriptionFragment subscriptionFragment = new SubscriptionFragment(); - fragment = subscriptionFragment; + fragment = new SubscriptionFragment(); break; default: // default to the queue @@ -368,15 +375,34 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi } } - public void loadChildFragment(Fragment fragment) { + public void loadChildFragment(Fragment fragment, TransitionEffect transition) { Validate.notNull(fragment); - FragmentManager fm = getSupportFragmentManager(); - fm.beginTransaction() - .replace(R.id.main_view, fragment, "main") + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + + switch (transition) { + case FADE: + transaction.setCustomAnimations(R.anim.fade_in, R.anim.fade_out); + break; + case FLIP: + transaction.setCustomAnimations( + R.anim.card_flip_left_in, + R.anim.card_flip_left_out, + R.anim.card_flip_right_in, + R.anim.card_flip_right_out); + break; + } + + transaction + .hide(getSupportFragmentManager().findFragmentByTag("main")) + .add(R.id.main_view, fragment, "main") .addToBackStack(null) .commit(); } + public void loadChildFragment(Fragment fragment) { + loadChildFragment(fragment, TransitionEffect.NONE); + } + public void dismissChildFragment() { getSupportFragmentManager().popBackStack(); } @@ -461,7 +487,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); RatingDialog.init(this); } @@ -489,7 +514,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi @Override protected void onStop() { super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); EventBus.getDefault().unregister(this); if (disposable != null) { disposable.dispose(); @@ -778,25 +802,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi } @Subscribe(threadMode = ThreadMode.MAIN) - public void onEventMainThread(ProgressEvent event) { - Log.d(TAG, "onEvent(" + event + ")"); - switch(event.action) { - case START: - pd = new ProgressDialog(this); - pd.setMessage(event.message); - pd.setIndeterminate(true); - pd.setCancelable(false); - pd.show(); - break; - case END: - if(pd != null) { - pd.dismiss(); - } - break; - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(MessageEvent event) { Log.d(TAG, "onEvent(" + event + ")"); View parentLayout = findViewById(R.id.drawer_layout); @@ -807,16 +812,16 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi snackbar.show(); } - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Subscribe + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + loadData(); + } - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - Log.d(TAG, "Received contentUpdate Intent."); - loadData(); - } - } - }; + + @Subscribe + public void onFeedListChanged(FeedListUpdateEvent event) { + loadData(); + } private void handleNavIntent() { Log.d(TAG, "handleNavIntent()"); 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 52497a27f..538ed1231 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -9,14 +9,14 @@ import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PixelFormat; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.ActivityOptionsCompat; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.app.ActivityOptionsCompat; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -28,12 +28,12 @@ import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import android.widget.Toast; -import com.afollestad.materialdialogs.MaterialDialog; import com.bumptech.glide.Glide; import com.joanzapata.iconify.IconDrawable; import com.joanzapata.iconify.fonts.FontAwesomeIcons; 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; @@ -63,6 +63,10 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; /** @@ -73,7 +77,8 @@ 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 = 42; + private static final int REQUEST_CODE_STORAGE_PLAY_VIDEO = 42; + private static final int REQUEST_CODE_STORAGE_PLAY_AUDIO = 43; PlaybackController controller; @@ -194,6 +199,11 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements }; } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + onPositionObserverUpdate(); + } + private static TextView getTxtvFFFromActivity(MediaplayerActivity activity) { return activity.txtvFF; } @@ -274,6 +284,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements controller.init(); loadMediaInfo(); onPositionObserverUpdate(); + EventBus.getDefault().register(this); } @Override @@ -286,6 +297,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements if (disposable != null) { disposable.dispose(); } + EventBus.getDefault().unregister(this); super.onStop(); } @@ -322,6 +334,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements Playable media = controller.getMedia(); boolean isFeedMedia = media != null && (media instanceof FeedMedia); + menu.findItem(R.id.open_feed_item).setVisible(isFeedMedia); // FeedMedia implies it belongs to a Feed + boolean hasWebsiteLink = ( getWebsiteLinkWithFallback(media) != null ); menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink); @@ -377,10 +391,9 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements | Intent.FLAG_ACTIVITY_NEW_TASK); View cover = findViewById(R.id.imgvCover); - if (cover != null && Build.VERSION.SDK_INT >= 16) { - ActivityOptionsCompat options = ActivityOptionsCompat. - makeSceneTransitionAnimation(MediaplayerActivity.this, - cover, "coverTransition"); + if (cover != null) { + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation(MediaplayerActivity.this, cover, "coverTransition"); startActivity(intent, options.toBundle()); } else { startActivity(intent); @@ -389,47 +402,38 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements return true; } else { if (media != null) { + final @Nullable FeedItem feedItem = getFeedItem(media); // some options option requires FeedItem switch (item.getItemId()) { case R.id.add_to_favorites_item: - if(media instanceof FeedMedia) { - FeedItem feedItem = ((FeedMedia)media).getItem(); - if(feedItem != null) { - DBWriter.addFavoriteItem(feedItem); - isFavorite = true; - invalidateOptionsMenu(); - Toast.makeText(this, R.string.added_to_favorites, Toast.LENGTH_SHORT) - .show(); - } + if (feedItem != null) { + DBWriter.addFavoriteItem(feedItem); + isFavorite = true; + invalidateOptionsMenu(); + Toast.makeText(this, R.string.added_to_favorites, Toast.LENGTH_SHORT) + .show(); } break; case R.id.remove_from_favorites_item: - if(media instanceof FeedMedia) { - FeedItem feedItem = ((FeedMedia)media).getItem(); - if(feedItem != null) { - DBWriter.removeFavoriteItem(feedItem); - isFavorite = false; - invalidateOptionsMenu(); - Toast.makeText(this, R.string.removed_from_favorites, Toast.LENGTH_SHORT) - .show(); - } + if (feedItem != null) { + DBWriter.removeFavoriteItem(feedItem); + isFavorite = false; + invalidateOptionsMenu(); + Toast.makeText(this, R.string.removed_from_favorites, Toast.LENGTH_SHORT) + .show(); } break; case R.id.disable_sleeptimer_item: if (controller.serviceAvailable()) { - MaterialDialog.Builder stDialog = new MaterialDialog.Builder(this); - stDialog.title(R.string.sleep_timer_label); - stDialog.content(getString(R.string.time_left_label) - + Converter.getDurationStringLong((int) controller - .getSleepTimerTimeLeft())); - stDialog.positiveText(R.string.disable_sleeptimer_label); - stDialog.negativeText(R.string.cancel_label); - stDialog.onPositive((dialog, which) -> { - dialog.dismiss(); - controller.disableSleepTimer(); - }); - stDialog.onNegative((dialog, which) -> dialog.dismiss()); - stDialog.build().show(); + new AlertDialog.Builder(this) + .setTitle(R.string.sleep_timer_label) + .setMessage(getString(R.string.time_left_label) + + Converter.getDurationStringLong((int) controller + .getSleepTimerTimeLeft())) + .setPositiveButton(R.string.disable_sleeptimer_label, (dialog, which) + -> controller.disableSleepTimer()) + .setNegativeButton(R.string.cancel_label, null) + .show(); } break; case R.id.set_sleeptimer_item: @@ -448,28 +452,33 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(isPlayingVideo); dialog.show(getSupportFragmentManager(), "playback_controls"); break; + case R.id.open_feed_item: + if (feedItem != null) { + Intent intent = MainActivity.getIntentToOpenFeed(this, feedItem.getFeedId()); + startActivity(intent); + } + break; case R.id.visit_website_item: - Uri uri = Uri.parse(getWebsiteLinkWithFallback(media)); - startActivity(new Intent(Intent.ACTION_VIEW, uri)); + IntentUtils.openInBrowser(MediaplayerActivity.this, getWebsiteLinkWithFallback(media)); break; case R.id.share_link_item: - if (media instanceof FeedMedia) { - ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem()); + if (feedItem != null) { + ShareUtils.shareFeedItemLink(this, feedItem); } break; case R.id.share_download_url_item: - if (media instanceof FeedMedia) { - ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem()); + if (feedItem != null) { + ShareUtils.shareFeedItemDownloadLink(this, feedItem); } break; case R.id.share_link_with_position_item: - if (media instanceof FeedMedia) { - ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem(), true); + if (feedItem != null) { + ShareUtils.shareFeedItemLink(this, feedItem, true); } break; case R.id.share_download_url_with_position_item: - if (media instanceof FeedMedia) { - ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem(), true); + if (feedItem != null) { + ShareUtils.shareFeedItemDownloadLink(this, feedItem, true); } break; case R.id.share_file: @@ -490,7 +499,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements private static String getWebsiteLinkWithFallback(Playable media) { if (media == null) { return null; - } else if (media.getWebsiteLink() != null) { + } else if (StringUtils.isNotBlank(media.getWebsiteLink())) { return media.getWebsiteLink(); } else if (media instanceof FeedMedia) { return FeedItemUtil.getLinkWithFallback(((FeedMedia)media).getItem()); @@ -635,7 +644,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements } } - static public void showSkipPreference(Activity activity, SkipDirection direction) { + public static void showSkipPreference(Activity activity, SkipDirection direction) { int checked = 0; int skipSecs = direction.getPrefSkipSeconds(); final int[] values = activity.getResources().getIntArray(R.array.seek_delta_values); @@ -813,11 +822,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements } private void checkFavorite() { - Playable playable = controller.getMedia(); - if (!(playable instanceof FeedMedia)) { - return; - } - FeedItem feedItem = ((FeedMedia) playable).getItem(); + FeedItem feedItem = getFeedItem(controller.getMedia()); if (feedItem == null) { return; } @@ -847,10 +852,13 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show(); - } else { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - REQUEST_CODE_STORAGE); } + + 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; } @@ -858,6 +866,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements ExternalMedia media = new ExternalMedia(intent.getData().getPath(), type); new PlaybackServiceStarter(this, media) + .callEvenIfRunning(true) .startWhenPrepared(true) .shouldStream(false) .prepareImmediately(true) @@ -865,11 +874,24 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements } @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == REQUEST_CODE_STORAGE) { - if (grantResults.length <= 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show(); + 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) { + return ((FeedMedia) playable).getItem(); + } else { + return null; } } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java index 4fec1cfc5..21f4ad5be 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java @@ -7,15 +7,15 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.widget.Toolbar; +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.snackbar.Snackbar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.ViewPager; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.util.TypedValue; import android.view.ContextMenu; @@ -23,9 +23,9 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; -import android.widget.Button; import android.widget.ImageButton; import android.widget.ListView; +import android.widget.TextView; import android.widget.Toast; import com.viewpagerindicator.CirclePageIndicator; @@ -36,8 +36,8 @@ import de.danoeh.antennapod.R; 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.MessageEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -63,7 +63,6 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -92,7 +91,8 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem NavListAdapter.SUBSCRIPTION_LIST_TAG }; - Button butPlaybackSpeed; + ImageButton butPlaybackSpeed; + TextView txtvPlaybackSpeed; ImageButton butCastDisconnect; private DrawerLayout drawerLayout; private NavListAdapter navAdapter; @@ -119,8 +119,6 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem if (disposable != null) { disposable.dispose(); } - EventDistributor.getInstance().unregister(contentUpdate); - EventBus.getDefault().unregister(this); saveCurrentFragment(); } @@ -172,8 +170,6 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem @Override protected void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().register(this); loadData(); } @@ -258,6 +254,7 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem }); butPlaybackSpeed = findViewById(R.id.butPlaybackSpeed); + txtvPlaybackSpeed = findViewById(R.id.txtvPlaybackSpeed); butCastDisconnect = findViewById(R.id.butCastDisconnect); pager = findViewById(R.id.pager); @@ -446,16 +443,10 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem snackbar.show(); } - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EventDistributor.FEED_LIST_UPDATE & arg) != 0) { - Log.d(TAG, "Received contentUpdate Intent."); - loadData(); - } - } - }; + @Subscribe + public void onFeedListChanged(FeedListUpdateEvent event) { + loadData(); + } private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { @Override 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 ea7687bc9..0c6c63e73 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -7,12 +7,11 @@ import android.content.Intent; import android.graphics.LightingColorFilter; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.UiThread; -import android.support.v4.app.NavUtils; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.NonNull; +import androidx.annotation.UiThread; +import androidx.core.app.NavUtils; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -22,23 +21,19 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; -import android.widget.RelativeLayout; import android.widget.Spinner; import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; -import de.danoeh.antennapod.core.glide.FastBlurTransformation; +import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import org.apache.commons.lang3.StringUtils; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; import java.io.File; import java.io.IOException; @@ -50,11 +45,11 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadStatus; @@ -94,60 +89,31 @@ public class OnlineFeedViewActivity extends AppCompatActivity { public static final String ARG_TITLE = "title"; private static final int RESULT_ERROR = 2; private static final String TAG = "OnlineFeedViewActivity"; - private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE; private volatile List<Feed> feeds; private Feed feed; private String selectedDownloadUrl; private Downloader downloader; private boolean isPaused; + private boolean didPressSubscribe = false; private Dialog dialog; - + private ListView listView; private Button subscribeButton; + private ProgressBar progressBar; private Disposable download; private Disposable parser; private Disposable updater; - private final EventDistributor.EventListener listener = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) { - updater = Observable.fromCallable(DBReader::getFeedList) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - feeds -> { - OnlineFeedViewActivity.this.feeds = feeds; - setSubscribeButtonState(feed); - }, error -> Log.e(TAG, Log.getStackTraceString(error)) - ); - } else if ((arg & EVENTS) != 0) { - setSubscribeButtonState(feed); - } - } - }; - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEventMainThread(DownloadEvent event) { - Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - setSubscribeButtonState(feed); - } @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); + setTheme(UserPreferences.getTranslucentTheme()); super.onCreate(savedInstanceState); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - } - - if (actionBar != null && getIntent() != null && getIntent().hasExtra(ARG_TITLE)) { - actionBar.setTitle(getIntent().getStringExtra(ARG_TITLE)); - } - StorageUtils.checkStorageAvailability(this); + setContentView(R.layout.onlinefeedview_activity); + listView = findViewById(R.id.listview); + progressBar = findViewById(R.id.progressBar); String feedUrl = null; if (getIntent().hasExtra(ARG_FEEDURL)) { @@ -156,9 +122,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { || TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { feedUrl = TextUtils.equals(getIntent().getAction(), Intent.ACTION_SEND) ? getIntent().getStringExtra(Intent.EXTRA_TEXT) : getIntent().getDataString(); - if (actionBar != null) { - actionBar.setTitle(R.string.add_feed_label); - } } if (feedUrl == null) { @@ -183,26 +146,14 @@ public class OnlineFeedViewActivity extends AppCompatActivity { * Displays a progress indicator. */ private void setLoadingLayout() { - RelativeLayout rl = new RelativeLayout(this); - RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT); - - ProgressBar pb = new ProgressBar(this); - pb.setIndeterminate(true); - RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); - rl.addView(pb, pbLayoutParams); - addContentView(rl, rlLayoutParams); + progressBar.setVisibility(View.VISIBLE); + findViewById(R.id.feedDisplay).setVisibility(View.GONE); } @Override protected void onStart() { super.onStart(); isPaused = false; - EventDistributor.getInstance().register(listener); EventBus.getDefault().register(this); } @@ -210,7 +161,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { protected void onStop() { super.onStop(); isPaused = true; - EventDistributor.getInstance().unregister(listener); EventBus.getDefault().unregister(this); if (downloader != null && !downloader.isFinished()) { downloader.cancel(); @@ -251,6 +201,12 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } @Override + public void finish() { + super.finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: @@ -312,6 +268,25 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } } + @Subscribe + public void onFeedListChanged(FeedListUpdateEvent event) { + updater = Observable.fromCallable(DBReader::getFeedList) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + feeds -> { + OnlineFeedViewActivity.this.feeds = feeds; + handleUpdatedFeedStatus(feed); + }, error -> Log.e(TAG, Log.getStackTraceString(error)) + ); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + handleUpdatedFeedStatus(feed); + } + private void parseFeed() { if (feed == null || (feed.getFile_url() == null && feed.isDownloaded())) { throw new IllegalStateException("feed must be non-null and downloaded when parseFeed is called"); @@ -367,20 +342,14 @@ public class OnlineFeedViewActivity extends AppCompatActivity { * This method is executed on a background thread */ private void beforeShowFeedInformation(Feed feed) { - final HtmlToPlainText formatter = new HtmlToPlainText(); - if(Feed.TYPE_ATOM1.equals(feed.getType()) && feed.getDescription() != null) { - // remove HTML tags from descriptions - Log.d(TAG, "Removing HTML from feed description"); - Document feedDescription = Jsoup.parse(feed.getDescription()); - feed.setDescription(StringUtils.trim(formatter.getPlainText(feedDescription))); - } + Log.d(TAG, "Removing HTML from feed description"); + + feed.setDescription(HtmlToPlainText.getPlainText(feed.getDescription())); + Log.d(TAG, "Removing HTML from shownotes"); if (feed.getItems() != null) { for (FeedItem item : feed.getItems()) { - if (item.getDescription() != null) { - Document itemDescription = Jsoup.parse(item.getDescription()); - item.setDescription(StringUtils.trim(formatter.getPlainText(itemDescription))); - } + item.setDescription(HtmlToPlainText.getPlainText(item.getDescription())); } } } @@ -390,30 +359,27 @@ public class OnlineFeedViewActivity extends AppCompatActivity { * This method is executed on the GUI thread. */ private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) { - setContentView(R.layout.listview_activity); - + progressBar.setVisibility(View.GONE); + findViewById(R.id.feedDisplay).setVisibility(View.VISIBLE); this.feed = feed; this.selectedDownloadUrl = feed.getDownload_url(); - EventDistributor.getInstance().register(listener); - ListView listView = findViewById(R.id.listview); listView.setSelector(android.R.color.transparent); - LayoutInflater inflater = LayoutInflater.from(this); - View header = inflater.inflate(R.layout.onlinefeedview_header, listView, false); - listView.addHeaderView(header); - listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems())); - ImageView cover = header.findViewById(R.id.imgvCover); - ImageView headerBackground = header.findViewById(R.id.imgvBackground); - header.findViewById(R.id.butShowInfo).setVisibility(View.INVISIBLE); - header.findViewById(R.id.butShowSettings).setVisibility(View.INVISIBLE); + ImageView cover = findViewById(R.id.imgvCover); + ImageView headerBackground = findViewById(R.id.imgvBackground); + findViewById(R.id.butShowInfo).setVisibility(View.INVISIBLE); + findViewById(R.id.butShowSettings).setVisibility(View.INVISIBLE); headerBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); - TextView title = header.findViewById(R.id.txtvTitle); - TextView author = header.findViewById(R.id.txtvAuthor); + TextView title = findViewById(R.id.txtvTitle); + TextView author = findViewById(R.id.txtvAuthor); + Spinner spAlternateUrls = findViewById(R.id.spinnerAlternateUrls); + + View header = View.inflate(this, R.layout.onlinefeedview_header, null); + listView.addHeaderView(header); TextView description = header.findViewById(R.id.txtvDescription); - Spinner spAlternateUrls = header.findViewById(R.id.spinnerAlternateUrls); - subscribeButton = header.findViewById(R.id.butSubscribe); + subscribeButton = findViewById(R.id.butSubscribe); if (StringUtils.isNotBlank(feed.getImageUrl())) { Glide.with(this) @@ -441,13 +407,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { description.setText(feed.getDescription()); subscribeButton.setOnClickListener(v -> { - if(feedInFeedlist(feed)) { - Intent intent = new Intent(OnlineFeedViewActivity.this, MainActivity.class); - // feed.getId() is always 0, we have to retrieve the id from the feed list from - // the database - intent.putExtra(MainActivity.EXTRA_FEED_ID, getFeedId(feed)); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); + if (feedInFeedlist(feed)) { + openFeed(); } else { Feed f = new Feed(selectedDownloadUrl, null, feed.getTitle()); f.setPreferences(feed.getPreferences()); @@ -458,7 +419,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { Log.e(TAG, Log.getStackTraceString(e)); DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, e.getMessage()); } - setSubscribeButtonState(feed); + didPressSubscribe = true; + handleUpdatedFeedStatus(feed); } }); @@ -504,17 +466,28 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } }); } - setSubscribeButtonState(feed); + handleUpdatedFeedStatus(feed); } - private void setSubscribeButtonState(Feed feed) { + private void openFeed() { + // feed.getId() is always 0, we have to retrieve the id from the feed list from + // the database + Intent intent = MainActivity.getIntentToOpenFeed(this, getFeedId(feed)); + finish(); + startActivity(intent); + } + + private void handleUpdatedFeedStatus(Feed feed) { if (subscribeButton != null && feed != null) { if (DownloadRequester.getInstance().isDownloadingFile(feed.getDownload_url())) { subscribeButton.setEnabled(false); - subscribeButton.setText(R.string.downloading_label); + subscribeButton.setText(R.string.subscribing_label); } else if (feedInFeedlist(feed)) { subscribeButton.setEnabled(true); subscribeButton.setText(R.string.open_podcast); + if (didPressSubscribe) { + openFeed(); + } } else { subscribeButton.setEnabled(true); subscribeButton.setText(R.string.subscribe_label); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java index 72759c59c..376074525 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.activity; import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.util.SparseBooleanArray; import android.view.Menu; import android.view.MenuInflater; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java index c04ae051e..d7a4b9517 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java @@ -5,13 +5,12 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Environment; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; -import com.afollestad.materialdialogs.MaterialDialog; - import org.apache.commons.lang3.ArrayUtils; import java.io.InputStreamReader; @@ -30,55 +29,55 @@ import de.danoeh.antennapod.core.util.LangUtils; public class OpmlImportBaseActivity extends AppCompatActivity { private static final String TAG = "OpmlImportBaseActivity"; - private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; + private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; private OpmlImportWorker importWorker; - @Nullable private Uri uri; - - /** - * Handles the choices made by the user in the OpmlFeedChooserActivity and - * starts the OpmlFeedQueuer if necessary. - */ - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - Log.d(TAG, "Received result"); - if (resultCode == RESULT_CANCELED) { - Log.d(TAG, "Activity was cancelled"); + @Nullable private Uri uri; + + /** + * Handles the choices made by the user in the OpmlFeedChooserActivity and + * starts the OpmlFeedQueuer if necessary. + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d(TAG, "Received result"); + if (resultCode == RESULT_CANCELED) { + Log.d(TAG, "Activity was cancelled"); if (finishWhenCanceled()) { - finish(); - } - } else { - int[] selected = data.getIntArrayExtra(OpmlFeedChooserActivity.EXTRA_SELECTED_ITEMS); - if (selected != null && selected.length > 0) { - OpmlFeedQueuer queuer = new OpmlFeedQueuer(this, selected) { - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - Intent intent = new Intent(OpmlImportBaseActivity.this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP - | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - - }; - queuer.executeAsync(); - } else { - Log.d(TAG, "No items were selected"); - } - } - } - - void importUri(@Nullable Uri uri) { + finish(); + } + } else { + int[] selected = data.getIntArrayExtra(OpmlFeedChooserActivity.EXTRA_SELECTED_ITEMS); + if (selected != null && selected.length > 0) { + OpmlFeedQueuer queuer = new OpmlFeedQueuer(this, selected) { + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + Intent intent = new Intent(OpmlImportBaseActivity.this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + }; + queuer.executeAsync(); + } else { + Log.d(TAG, "No items were selected"); + } + } + } + + void importUri(@Nullable Uri uri) { if(uri == null) { - new MaterialDialog.Builder(this) - .content(R.string.opml_import_error_no_file) - .positiveText(android.R.string.ok) + new AlertDialog.Builder(this) + .setMessage(R.string.opml_import_error_no_file) + .setPositiveButton(android.R.string.ok, null) .show(); return; } - this.uri = uri; + this.uri = uri; if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { + uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { int permission = ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE); if (permission != PackageManager.PERMISSION_GRANTED) { requestPermission(); @@ -88,30 +87,28 @@ public class OpmlImportBaseActivity extends AppCompatActivity { startImport(); } - private void requestPermission() { - String[] permissions = { android.Manifest.permission.READ_EXTERNAL_STORAGE }; - ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_READ_EXTERNAL_STORAGE); - } - - @Override - public void onRequestPermissionsResult(int requestCode, - String[] permissions, - int[] grantResults) { - if (requestCode != PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { - return; - } - if (grantResults.length > 0 && ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED)) { - startImport(); - } else { - new MaterialDialog.Builder(this) - .content(R.string.opml_import_ask_read_permission) - .positiveText(android.R.string.ok) - .negativeText(R.string.cancel_label) - .onPositive((dialog, which) -> requestPermission()) - .onNegative((dialog, which) -> finish()) + private void requestPermission() { + String[] permissions = { android.Manifest.permission.READ_EXTERNAL_STORAGE }; + ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_READ_EXTERNAL_STORAGE); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String[] permissions, + int[] grantResults) { + if (requestCode != PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { + return; + } + if (grantResults.length > 0 && ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED)) { + startImport(); + } else { + new AlertDialog.Builder(this) + .setMessage(R.string.opml_import_ask_read_permission) + .setPositiveButton(android.R.string.ok, (dialog, which) -> requestPermission()) + .setNegativeButton(R.string.cancel_label, (dialog, which) -> finish()) .show(); - } - } + } + } /** Starts the import process. */ private void startImport() { @@ -136,10 +133,10 @@ public class OpmlImportBaseActivity extends AppCompatActivity { importWorker.executeAsync(); } catch (Exception e) { Log.d(TAG, Log.getStackTraceString(e)); - String message = getString(R.string.opml_reader_error); - new MaterialDialog.Builder(this) - .content(message + " " + e.getMessage()) - .positiveText(android.R.string.ok) + String message = getString(R.string.opml_reader_error); + new AlertDialog.Builder(this) + .setMessage(message + " " + e.getMessage()) + .setPositiveButton(android.R.string.ok, null) .show(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index 7e0ae173f..a0f9bf6d8 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -1,9 +1,9 @@ package de.danoeh.antennapod.activity; import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.preference.PreferenceFragmentCompat; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.PreferenceFragmentCompat; import android.view.Menu; import android.view.MenuItem; import android.view.ViewGroup; 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 52102eee1..bd1ccaea4 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java @@ -5,9 +5,9 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.Nullable; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.appcompat.app.AppCompatActivity; import android.widget.ProgressBar; import de.danoeh.antennapod.R; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java deleted file mode 100644 index 37199ccf7..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java +++ /dev/null @@ -1,153 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.SharedPreferences; -import android.os.Bundle; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.RadioButton; -import android.widget.TextView; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.StatisticsListAdapter; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.Converter; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -/** - * Displays the 'statistics' screen - */ -public class StatisticsActivity extends AppCompatActivity - implements AdapterView.OnItemClickListener { - - private static final String TAG = StatisticsActivity.class.getSimpleName(); - private static final String PREF_NAME = "StatisticsActivityPrefs"; - private static final String PREF_COUNT_ALL = "countAll"; - - private Disposable disposable; - private TextView totalTimeTextView; - private ListView feedStatisticsList; - private ProgressBar progressBar; - private StatisticsListAdapter listAdapter; - private boolean countAll = false; - private SharedPreferences prefs; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayShowHomeEnabled(true); - setContentView(R.layout.statistics_activity); - - prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); - countAll = prefs.getBoolean(PREF_COUNT_ALL, false); - - totalTimeTextView = findViewById(R.id.total_time); - feedStatisticsList = findViewById(R.id.statistics_list); - progressBar = findViewById(R.id.progressBar); - listAdapter = new StatisticsListAdapter(this); - listAdapter.setCountAll(countAll); - feedStatisticsList.setAdapter(listAdapter); - feedStatisticsList.setOnItemClickListener(this); - } - - @Override - public void onResume() { - super.onResume(); - refreshStatistics(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.statistics, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } else if (item.getItemId() == R.id.statistics_mode) { - selectStatisticsMode(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void selectStatisticsMode() { - View contentView = View.inflate(this, R.layout.statistics_mode_select_dialog, null); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setView(contentView); - builder.setTitle(R.string.statistics_mode); - - if (countAll) { - ((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).setChecked(true); - } else { - ((RadioButton) contentView.findViewById(R.id.statistics_mode_normal)).setChecked(true); - } - - builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { - countAll = ((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).isChecked(); - listAdapter.setCountAll(countAll); - prefs.edit().putBoolean(PREF_COUNT_ALL, countAll).apply(); - refreshStatistics(); - }); - - builder.show(); - } - - private void refreshStatistics() { - progressBar.setVisibility(View.VISIBLE); - totalTimeTextView.setVisibility(View.GONE); - feedStatisticsList.setVisibility(View.GONE); - loadStatistics(); - } - - private void loadStatistics() { - if (disposable != null) { - disposable.dispose(); - } - disposable = Observable.fromCallable(() -> DBReader.getStatistics(countAll)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - totalTimeTextView.setText(Converter - .shortLocalizedDuration(this, countAll ? result.totalTimeCountAll : result.totalTime)); - listAdapter.update(result.feedTime); - progressBar.setVisibility(View.GONE); - totalTimeTextView.setVisibility(View.VISIBLE); - feedStatisticsList.setVisibility(View.VISIBLE); - }, error -> Log.e(TAG, Log.getStackTraceString(error))); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - DBReader.StatisticsItem stats = listAdapter.getItem(position); - - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(stats.feed.getTitle()); - dialog.setMessage(getString(R.string.statistics_details_dialog, - countAll ? stats.episodesStartedIncludingMarked : stats.episodesStarted, - stats.episodes, - Converter.shortLocalizedDuration(this, countAll ? - stats.timePlayedCountAll : stats.timePlayed), - Converter.shortLocalizedDuration(this, stats.time))); - dialog.setPositiveButton(android.R.string.ok, null); - dialog.show(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java index 20e34cc52..8527949b0 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java @@ -9,15 +9,13 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.widget.Button; -import com.afollestad.materialdialogs.MaterialDialog; - import java.io.File; import de.danoeh.antennapod.R; @@ -28,22 +26,22 @@ import de.danoeh.antennapod.dialog.ChooseDataFolderDialog; /** Is show if there is now external storage available. */ public class StorageErrorActivity extends AppCompatActivity { - private static final String TAG = "StorageErrorActivity"; + private static final String TAG = "StorageErrorActivity"; private static final String[] EXTERNAL_STORAGE_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 42; - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); - setContentView(R.layout.storage_error); + setContentView(R.layout.storage_error); - Button btnChooseDataFolder = findViewById(R.id.btnChooseDataFolder); - btnChooseDataFolder.setOnClickListener(v -> { + Button btnChooseDataFolder = findViewById(R.id.btnChooseDataFolder); + btnChooseDataFolder.setOnClickListener(v -> { if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { showChooseDataFolderDialog(); @@ -82,11 +80,10 @@ public class StorageErrorActivity extends AppCompatActivity { } if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) { - new MaterialDialog.Builder(this) - .content(R.string.choose_data_directory_permission_rationale) - .positiveText(android.R.string.ok) - .onPositive((dialog, which) -> requestPermission()) - .onNegative((dialog, which) -> finish()) + new AlertDialog.Builder(this) + .setMessage(R.string.choose_data_directory_permission_rationale) + .setPositiveButton(android.R.string.ok, (dialog, which) -> requestPermission()) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> finish()) .show(); } } @@ -101,15 +98,15 @@ public class StorageErrorActivity extends AppCompatActivity { } } - @Override - protected void onPause() { - super.onPause(); - try { - unregisterReceiver(mediaUpdate); - } catch (IllegalArgumentException e) { + @Override + protected void onPause() { + super.onPause(); + try { + unregisterReceiver(mediaUpdate); + } catch (IllegalArgumentException e) { Log.e(TAG, Log.getStackTraceString(e)); - } - } + } + } // see PreferenceController.showChooseDataFolderDialog() private void showChooseDataFolderDialog() { @@ -123,9 +120,9 @@ public class StorageErrorActivity extends AppCompatActivity { }); } - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && - requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && + requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); File path; @@ -138,46 +135,46 @@ public class StorageErrorActivity extends AppCompatActivity { return; } String message = null; - if(!path.exists()) { - message = String.format(getString(R.string.folder_does_not_exist_error), dir); - } else if(!path.canRead()) { - message = String.format(getString(R.string.folder_not_readable_error), dir); - } else if(!path.canWrite()) { - message = String.format(getString(R.string.folder_not_writable_error), dir); - } - - if(message == null) { - Log.d(TAG, "Setting data folder: " + dir); - UserPreferences.setDataFolder(dir); - leaveErrorState(); - } else { - AlertDialog.Builder ab = new AlertDialog.Builder(this); - ab.setMessage(message); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - } - - private void leaveErrorState() { - finish(); - startActivity(new Intent(this, MainActivity.class)); - } - - private final BroadcastReceiver mediaUpdate = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), Intent.ACTION_MEDIA_MOUNTED)) { - if (intent.getBooleanExtra("read-only", true)) { - Log.d(TAG, "Media was mounted; Finishing activity"); - leaveErrorState(); - } else { - Log.d(TAG, "Media seemed to have been mounted read only"); - } - } - } - - }; + if(!path.exists()) { + message = String.format(getString(R.string.folder_does_not_exist_error), dir); + } else if(!path.canRead()) { + message = String.format(getString(R.string.folder_not_readable_error), dir); + } else if(!path.canWrite()) { + message = String.format(getString(R.string.folder_not_writable_error), dir); + } + + if(message == null) { + Log.d(TAG, "Setting data folder: " + dir); + UserPreferences.setDataFolder(dir); + leaveErrorState(); + } else { + AlertDialog.Builder ab = new AlertDialog.Builder(this); + ab.setMessage(message); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + } + + private void leaveErrorState() { + finish(); + startActivity(new Intent(this, MainActivity.class)); + } + + private final BroadcastReceiver mediaUpdate = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (TextUtils.equals(intent.getAction(), Intent.ACTION_MEDIA_MOUNTED)) { + if (intent.getBooleanExtra("read-only", true)) { + Log.d(TAG, "Media was mounted; Finishing activity"); + leaveErrorState(); + } else { + Log.d(TAG, "Media seemed to have been mounted read only"); + } + } + } + + }; } 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 78cc15b2c..0c6ae2645 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -6,8 +6,8 @@ import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.support.v4.view.WindowCompat; -import android.support.v7.app.ActionBar; +import androidx.core.view.WindowCompat; +import androidx.appcompat.app.ActionBar; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -151,10 +151,7 @@ public class VideoplayerActivity extends MediaplayerActivity { videoview.getHolder().addCallback(surfaceHolderCallback); videoframe.setOnTouchListener(onVideoviewTouched); videoOverlay.setOnTouchListener((view, motionEvent) -> true); // To suppress touches directly below the slider - - if (Build.VERSION.SDK_INT >= 16) { - videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - } + videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); videoOverlay.setFitsSystemWindows(true); setupVideoControlsToggler(); @@ -351,9 +348,9 @@ public class VideoplayerActivity extends MediaplayerActivity { controls.startAnimation(animation); } } - int videoviewFlag = (Build.VERSION.SDK_INT >= 16) ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0; - getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | videoviewFlag); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); videoOverlay.setFitsSystemWindows(true); videoOverlay.setVisibility(View.GONE); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java new file mode 100644 index 000000000..474b96c38 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java @@ -0,0 +1,107 @@ +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.os.Bundle; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.receiver.PlayerWidget; +import de.danoeh.antennapod.core.service.PlayerWidgetJobService; + +public class WidgetConfigActivity extends AppCompatActivity { + private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + + private SeekBar opacitySeekBar; + private TextView opacityTextView; + private RelativeLayout widgetPreview; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_widget_config); + + Intent configIntent = getIntent(); + Bundle extras = configIntent.getExtras(); + if (extras != null) { + appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + } + + Intent resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + setResult(RESULT_CANCELED, resultValue); + if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + finish(); + } + + displayDeviceBackground(); + opacityTextView = findViewById(R.id.widget_opacity_textView); + opacitySeekBar = findViewById(R.id.widget_opacity_seekBar); + widgetPreview = findViewById(R.id.widgetLayout); + findViewById(R.id.butConfirm).setOnClickListener(this::confirmCreateWidget); + opacitySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + opacityTextView.setText(seekBar.getProgress() + "%"); + int color = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.getProgress()); + widgetPreview.setBackgroundColor(color); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + }); + } + + private void displayDeviceBackground() { + int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); + if (Build.VERSION.SDK_INT < 27 || permission == PackageManager.PERMISSION_GRANTED) { + final WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); + final Drawable wallpaperDrawable = wallpaperManager.getDrawable(); + ImageView background = findViewById(R.id.widget_config_background); + background.setImageDrawable(wallpaperDrawable); + } + } + + private void confirmCreateWidget(View v) { + int backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.getProgress()); + + SharedPreferences prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor); + editor.apply(); + + Intent resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + setResult(RESULT_OK, resultValue); + finish(); + PlayerWidgetJobService.updateWidget(this); + } + + private int getColorWithAlpha(int color, int opacity) { + return (int) Math.round(0xFF * (0.01 * opacity)) * 0x1000000 + color; + } +} 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 index 2d7898d5b..c79c611ce 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java @@ -2,11 +2,10 @@ package de.danoeh.antennapod.activity.gpoddernet; import android.content.Context; import android.content.Intent; -import android.content.res.Configuration; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java index 2eff33339..9cd5cc3ab 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -1,10 +1,11 @@ package de.danoeh.antennapod.adapter; import android.os.Build; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.ItemTouchHelper; import android.text.Layout; import android.util.Log; import android.view.ContextMenu; @@ -23,19 +24,23 @@ import android.widget.TextView; import com.joanzapata.iconify.Iconify; import java.lang.ref.WeakReference; +import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; +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.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.fragment.ItemFragment; +import de.danoeh.antennapod.fragment.ItemPagerFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; /** @@ -165,7 +170,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR holder.progress.setVisibility(View.INVISIBLE); } - if(media.isCurrentlyPlaying()) { + if (media.isCurrentlyPlaying()) { holder.container.setBackgroundColor(playingBackGroundColor); } else { holder.container.setBackgroundColor(normalBackGroundColor); @@ -189,7 +194,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR holder.butSecondary.setTag(item); new CoverLoader(mainActivityRef.get()) - .withUri(item.getImageLocation()) + .withUri(ImageResourceUtils.getImageLocation(item)) .withFallbackUri(item.getFeed().getImageLocation()) .withPlaceholderView(holder.placeholder) .withCoverView(holder.cover) @@ -241,7 +246,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR MainActivity mainActivity = mainActivityRef.get(); if (mainActivity != null) { long[] ids = itemAccess.getItemsIds().toArray(); - mainActivity.loadChildFragment(ItemFragment.newInstance(ids, getAdapterPosition())); + mainActivity.loadChildFragment(ItemPagerFragment.newInstance(ids, getAdapterPosition())); } } @@ -262,7 +267,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR FeedItem item = itemAccess.getItem(getAdapterPosition()); MenuInflater inflater = mainActivityRef.get().getMenuInflater(); - inflater.inflate(R.menu.allepisodes_context, menu); + inflater.inflate(R.menu.feeditemlist_context, menu); if (item != null) { menu.setHeaderTitle(item.getTitle()); @@ -277,9 +282,15 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR item1.setVisible(visible); } }; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null); + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item); + } + + public boolean isCurrentlyPlayingItem() { + return item.getMedia() != null && item.getMedia().isCurrentlyPlaying(); + } - contextMenuInterface.setItemVisibility(R.id.remove_new_flag_item, item.isNew()); + public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) { + progress.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); } } 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 c3fac7e18..f6e6da8b4 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.adapter; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; import android.text.Layout; import android.text.Selection; import android.text.Spannable; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java b/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java index 33f925e3f..098e9a616 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java @@ -1,9 +1,8 @@ package de.danoeh.antennapod.adapter; import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.view.View; import android.widget.ImageView; import android.widget.TextView; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java index 74bc84878..e3ca5b5a5 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java @@ -2,9 +2,10 @@ package de.danoeh.antennapod.adapter; import android.app.Dialog; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; +import android.widget.ProgressBar; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,7 +21,6 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.dialog.ChooseDataFolderDialog; -import me.zhanghai.android.materialprogressbar.MaterialProgressBar; public class DataFolderAdapter extends RecyclerView.Adapter<DataFolderAdapter.ViewHolder> { @@ -105,7 +105,7 @@ public class DataFolderAdapter extends RecyclerView.Adapter<DataFolderAdapter.Vi private TextView path; private TextView size; private RadioButton radioButton; - private MaterialProgressBar progressBar; + private ProgressBar progressBar; ViewHolder(View itemView) { super(itemView); 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 789c01a26..4d66fd486 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -2,7 +2,6 @@ package de.danoeh.antennapod.adapter; import android.content.Context; import android.os.Build; -import android.support.v4.content.ContextCompat; import android.text.Layout; import android.text.format.DateUtils; import android.util.Log; @@ -13,6 +12,8 @@ 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; @@ -24,6 +25,7 @@ import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; /** Displays a list of DownloadStatus entries. */ public class DownloadLogAdapter extends BaseAdapter { @@ -132,7 +134,7 @@ public class DownloadLogAdapter extends BaseAdapter { FeedMedia media = DBReader.getFeedMedia(holder.id); if (media != null) { try { - DBTasks.downloadFeedItems(context, media.getItem()); + DownloadRequester.getInstance().downloadMedia(context, media.getItem()); Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); } catch (DownloadRequestException e) { e.printStackTrace(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java index 98d55dd97..b083908a8 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -17,9 +17,9 @@ import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; /** * Shows a list of downloaded episodes @@ -79,7 +79,7 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { } Glide.with(context) - .load(item.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(item)) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedDiscoverAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedDiscoverAdapter.java index df7ec46e0..66fa79a4e 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedDiscoverAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedDiscoverAdapter.java @@ -59,6 +59,8 @@ public class FeedDiscoverAdapter extends BaseAdapter { final PodcastSearchResult podcast = getItem(position); + holder.imageView.setContentDescription(podcast.title); + Glide.with(mainActivityRef.get()) .load(podcast.imageUrl) .apply(new RequestOptions() diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java index d090bc4b1..aec0f0c91 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java @@ -3,7 +3,7 @@ package de.danoeh.antennapod.adapter; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; import android.text.Layout; import android.view.LayoutInflater; import android.view.View; @@ -13,15 +13,18 @@ import android.widget.BaseAdapter; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; +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.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.ThemeUtils; @@ -39,6 +42,8 @@ public class FeedItemlistAdapter extends BaseAdapter { private final int playingBackGroundColor; private final int normalBackGroundColor; + private int currentlyPlayingItem = -1; + public FeedItemlistAdapter(Context context, ItemAccess itemAccess, boolean showFeedtitle, @@ -176,8 +181,9 @@ public class FeedItemlistAdapter extends BaseAdapter { } typeDrawables.recycle(); - if(media.isCurrentlyPlaying()) { + if (media.isCurrentlyPlaying()) { holder.container.setBackgroundColor(playingBackGroundColor); + currentlyPlayingItem = position; } else { holder.container.setBackgroundColor(normalBackGroundColor); } @@ -195,6 +201,20 @@ public class FeedItemlistAdapter extends BaseAdapter { return convertView; } + public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event, ListView listView) { + if (currentlyPlayingItem != -1 && currentlyPlayingItem < getCount()) { + View view = listView.getChildAt(currentlyPlayingItem + - listView.getFirstVisiblePosition() + listView.getHeaderViewsCount()); + if (view == null) { + return; + } + Holder holder = (Holder) view.getTag(); + holder.episodeProgress.setVisibility(View.VISIBLE); + holder.episodeProgress.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); + holder.lenSize.setText(Converter.getDurationStringLong(event.getDuration() - event.getPosition())); + } + } + static class Holder { LinearLayout container; TextView title; 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 c10bb7638..8d469c7a6 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.adapter; import android.content.Context; +import android.os.Build; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -51,6 +52,17 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { .replaceAll("\\s+", " ") .trim(); holder.description.setText(description); + + final int MAX_LINES_COLLAPSED = 3; + holder.description.setMaxLines(MAX_LINES_COLLAPSED); + holder.description.setOnClickListener(v -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN + && holder.description.getMaxLines() > MAX_LINES_COLLAPSED) { + holder.description.setMaxLines(MAX_LINES_COLLAPSED); + } else { + holder.description.setMaxLines(2000); + } + }); } return convertView; } 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 be8e52cfc..50b11a15b 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -7,7 +7,7 @@ import android.content.res.TypedArray; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.preference.PreferenceManager; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; 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 382abfb32..5ccec0ade 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -1,12 +1,11 @@ package de.danoeh.antennapod.adapter; -import android.content.Context; import android.os.Build; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.MotionEventCompat; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.core.view.MotionEventCompat; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.ItemTouchHelper; import android.text.Layout; import android.text.TextUtils; import android.util.Log; @@ -25,6 +24,8 @@ import android.widget.TextView; import com.joanzapata.iconify.Iconify; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.fragment.ItemPagerFragment; import org.apache.commons.lang3.ArrayUtils; import java.lang.ref.WeakReference; @@ -38,10 +39,10 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.ThemeUtils; -import de.danoeh.antennapod.fragment.ItemFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; /** @@ -80,11 +81,13 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap notifyDataSetChanged(); } + @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.queue_listitem, parent, false); return new ViewHolder(view); } + @Override public void onBindViewHolder(ViewHolder holder, int pos) { FeedItem item = itemAccess.getItem(pos); holder.bind(item); @@ -160,7 +163,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap if (activity != null) { long[] ids = itemAccess.getQueueIds().toArray(); int position = ArrayUtils.indexOf(ids, item.getId()); - activity.loadChildFragment(ItemFragment.newInstance(ids, position)); + activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position)); } } @@ -169,7 +172,8 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap FeedItem item = itemAccess.getItem(getAdapterPosition()); MenuInflater inflater = mainActivity.get().getMenuInflater(); - inflater.inflate(R.menu.queue_context, menu); + inflater.inflate(R.menu.queue_context, menu); // queue-specific menu items + inflater.inflate(R.menu.feeditemlist_context, menu); // generic menu items for item feeds if (item != null) { menu.setHeaderTitle(item.getTitle()); @@ -184,7 +188,18 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap item1.setVisible(visible); } }; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, itemAccess.getQueueIds()); + + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, + R.id.skip_episode_item); // Skip Episode is not useful in Queue, so hide it. + // Queue-specific menu preparation + final boolean keepSorted = UserPreferences.isQueueKeepSorted(); + final LongList queueAccess = itemAccess.getQueueIds(); + if (queueAccess.size() == 0 || queueAccess.get(0) == item.getId() || keepSorted) { + contextMenuInterface.setItemVisibility(R.id.move_to_top_item, false); + } + if (queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == item.getId() || keepSorted) { + contextMenuInterface.setItemVisibility(R.id.move_to_bottom_item, false); + } } @Override @@ -288,13 +303,22 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap butSecondary.setTag(item); new CoverLoader(mainActivity.get()) - .withUri(item.getImageLocation()) + .withUri(ImageResourceUtils.getImageLocation(item)) .withFallbackUri(item.getFeed().getImageLocation()) .withPlaceholderView(placeholder) .withCoverView(cover) .load(); } + public boolean isCurrentlyPlayingItem() { + return item.getMedia() != null && item.getMedia().isCurrentlyPlaying(); + } + + public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) { + progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); + progressLeft.setText(Converter.getDurationStringLong(event.getPosition())); + progressRight.setText(Converter.getDurationStringLong(event.getDuration())); + } } public interface ItemAccess { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SimpleIconListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SimpleIconListAdapter.java new file mode 100644 index 000000000..10bda4efa --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SimpleIconListAdapter.java @@ -0,0 +1,59 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.RequestOptions; +import de.danoeh.antennapod.R; + +import java.util.List; + +/** + * Displays a list of items that have a subtitle and an icon. + */ +public class SimpleIconListAdapter<T extends SimpleIconListAdapter.ListItem> extends ArrayAdapter<T> { + private final Context context; + private final List<T> listItems; + + public SimpleIconListAdapter(Context context, List<T> listItems) { + super(context, R.layout.simple_icon_list_item, listItems); + this.context = context; + this.listItems = listItems; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + if (view == null) { + view = View.inflate(context, R.layout.simple_icon_list_item, null); + } + + ListItem item = listItems.get(position); + ((TextView) view.findViewById(R.id.title)).setText(item.title); + ((TextView) view.findViewById(R.id.subtitle)).setText(item.subtitle); + Glide.with(context) + .load(item.imageUrl) + .apply(new RequestOptions() + .diskCacheStrategy(DiskCacheStrategy.NONE) + .fitCenter() + .dontAnimate()) + .into(((ImageView) view.findViewById(R.id.icon))); + return view; + } + + public static class ListItem { + public final String title; + public final String subtitle; + public final String imageUrl; + + public ListItem(String title, String subtitle, String imageUrl) { + this.title = title; + this.subtitle = subtitle; + this.imageUrl = imageUrl; + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java index 31e82dbe0..f013f2a49 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java @@ -1,31 +1,30 @@ package de.danoeh.antennapod.adapter; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; - import com.bumptech.glide.Glide; - -import java.util.ArrayList; -import java.util.List; - import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.view.PieChartView; /** * Adapter for the statistics list */ -public class StatisticsListAdapter extends BaseAdapter { +public class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { + private static final int TYPE_HEADER = 0; + private static final int TYPE_FEED = 1; private final Context context; - private List<DBReader.StatisticsItem> feedTime = new ArrayList<>(); + private DBReader.StatisticsData statisticsData; private boolean countAll = true; public StatisticsListAdapter(Context context) { @@ -37,66 +36,102 @@ public class StatisticsListAdapter extends BaseAdapter { } @Override - public int getCount() { - return feedTime.size(); + public int getItemCount() { + return statisticsData.feedTime.size() + 1; } - @Override public DBReader.StatisticsItem getItem(int position) { - return feedTime.get(position); + if (position == 0) { + return null; + } + return statisticsData.feedTime.get(position - 1); } @Override - public long getItemId(int position) { - return feedTime.get(position).feed.getId(); + public int getItemViewType(int position) { + return position == 0 ? TYPE_HEADER : TYPE_FEED; } + @NonNull @Override - public View getView(int position, View convertView, ViewGroup parent) { - StatisticsHolder holder; - Feed feed = feedTime.get(position).feed; - - if (convertView == null) { - holder = new StatisticsHolder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.statistics_listitem, parent, false); + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + if (viewType == TYPE_HEADER) { + return new HeaderHolder(inflater.inflate(R.layout.statistics_listitem_total_time, parent, false)); + } + return new StatisticsHolder(inflater.inflate(R.layout.statistics_listitem, parent, false)); + } - holder.image = convertView.findViewById(R.id.imgvCover); - holder.title = convertView.findViewById(R.id.txtvTitle); - holder.time = convertView.findViewById(R.id.txtvTime); - convertView.setTag(holder); + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder h, int position) { + if (getItemViewType(position) == TYPE_HEADER) { + HeaderHolder holder = (HeaderHolder) h; + long time = countAll ? statisticsData.totalTimeCountAll : statisticsData.totalTime; + holder.totalTime.setText(Converter.shortLocalizedDuration(context, time)); + float[] dataValues = new float[statisticsData.feedTime.size()]; + for (int i = 0; i < statisticsData.feedTime.size(); i++) { + DBReader.StatisticsItem item = statisticsData.feedTime.get(i); + dataValues[i] = countAll ? item.timePlayedCountAll : item.timePlayed; + } + holder.pieChart.setData(dataValues); } else { - holder = (StatisticsHolder) convertView.getTag(); + StatisticsHolder holder = (StatisticsHolder) h; + DBReader.StatisticsItem statsItem = statisticsData.feedTime.get(position - 1); + Glide.with(context) + .load(statsItem.feed.getImageLocation()) + .apply(new RequestOptions() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate()) + .into(holder.image); + + holder.title.setText(statsItem.feed.getTitle()); + long time = countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed; + holder.time.setText(Converter.shortLocalizedDuration(context, time)); + + holder.itemView.setOnClickListener(v -> { + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + dialog.setTitle(statsItem.feed.getTitle()); + dialog.setMessage(context.getString(R.string.statistics_details_dialog, + countAll ? statsItem.episodesStartedIncludingMarked : statsItem.episodesStarted, + statsItem.episodes, Converter.shortLocalizedDuration(context, + countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed), + Converter.shortLocalizedDuration(context, statsItem.time))); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.show(); + }); } - - Glide.with(context) - .load(feed.getImageLocation()) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate()) - .into(holder.image); - - holder.title.setText(feed.getTitle()); - holder.time.setText(Converter.shortLocalizedDuration(context, - countAll ? feedTime.get(position).timePlayedCountAll - : feedTime.get(position).timePlayed)); - return convertView; } - public void update(List<DBReader.StatisticsItem> feedTime) { - this.feedTime = feedTime; + public void update(DBReader.StatisticsData statistics) { + this.statisticsData = statistics; notifyDataSetChanged(); } - static class StatisticsHolder { + static class HeaderHolder extends RecyclerView.ViewHolder { + TextView totalTime; + PieChartView pieChart; + + HeaderHolder(View itemView) { + super(itemView); + totalTime = itemView.findViewById(R.id.total_time); + pieChart = itemView.findViewById(R.id.pie_chart); + } + } + + static class StatisticsHolder extends RecyclerView.ViewHolder { ImageView image; TextView title; TextView time; + + StatisticsHolder(View itemView) { + super(itemView); + image = itemView.findViewById(R.id.imgvCover); + title = itemView.findViewById(R.id.txtvTitle); + time = itemView.findViewById(R.id.txtvTime); + } } } 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 e0fb65c61..3141c6046 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.adapter; import android.content.Context; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -30,7 +30,6 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI public static final Object ADD_ITEM_OBJ = new Object(); /** the position in the view that holds the add item; 0 is the first, -1 is the last position */ - private static final int ADD_POSITION = -1; private static final String TAG = "SubscriptionsAdapter"; private final WeakReference<MainActivity> mainActivityRef; @@ -41,28 +40,14 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI this.itemAccess = itemAccess; } - private int getAddTilePosition() { - if(ADD_POSITION < 0) { - return ADD_POSITION + getCount(); - } - return ADD_POSITION; - } - - private int getAdjustedPosition(int origPosition) { - return origPosition < getAddTilePosition() ? origPosition : origPosition - 1; - } - @Override public int getCount() { - return 1 + itemAccess.getCount(); + return itemAccess.getCount(); } @Override public Object getItem(int position) { - if (position == getAddTilePosition()) { - return ADD_ITEM_OBJ; - } - return itemAccess.getItem(getAdjustedPosition(position)); + return itemAccess.getItem(position); } @Override @@ -72,10 +57,7 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI @Override public long getItemId(int position) { - if (position == getAddTilePosition()) { - return 0; - } - return itemAccess.getItem(getAdjustedPosition(position)).getId(); + return itemAccess.getItem(position).getId(); } @Override @@ -98,24 +80,11 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI holder = (Holder) convertView.getTag(); } - if (position == getAddTilePosition()) { - holder.feedTitle.setText("{md-add 500%}\n\n" + mainActivityRef.get().getString(R.string.add_feed_label)); - holder.feedTitle.setVisibility(View.VISIBLE); - // prevent any accidental re-use of old values (not sure how that would happen...) - holder.count.setPrimaryText(""); - // make it go away, we don't need it for add feed - holder.count.setVisibility(View.INVISIBLE); - - // when this holder is reused, we could else end up with a cover image - Glide.with(mainActivityRef.get()).clear(holder.imageView); - - return convertView; - } - final Feed feed = (Feed) getItem(position); if (feed == null) return null; holder.feedTitle.setText(feed.getTitle()); + holder.imageView.setContentDescription(feed.getTitle()); holder.feedTitle.setVisibility(View.VISIBLE); int count = itemAccess.getFeedCounter(feed.getId()); if(count > 0) { @@ -137,12 +106,8 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (position == getAddTilePosition()) { - mainActivityRef.get().loadChildFragment(new AddFeedFragment()); - } else { - Fragment fragment = FeedItemlistFragment.newInstance(getItemId(position)); - mainActivityRef.get().loadChildFragment(fragment); - } + Fragment fragment = FeedItemlistFragment.newInstance(getItemId(position)); + mainActivityRef.get().loadChildFragment(fragment); } static class Holder { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java index 3299db3ab..a8001eeb1 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; -import android.support.annotation.AttrRes; -import android.support.annotation.StringRes; +import androidx.annotation.AttrRes; +import androidx.annotation.StringRes; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java index 1275a799b..10458ed46 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; -import android.support.annotation.AttrRes; -import android.support.annotation.StringRes; +import androidx.annotation.AttrRes; +import androidx.annotation.StringRes; import android.widget.Toast; import de.danoeh.antennapod.R; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java index c1559528e..026386cf9 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java @@ -1,16 +1,16 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; -import android.support.annotation.AttrRes; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; import android.widget.Toast; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; @@ -64,7 +64,7 @@ class DownloadActionButton extends ItemActionButton { private void downloadEpisode(Context context) { try { - DBTasks.downloadFeedItems(context, item); + DownloadRequester.getInstance().downloadMedia(context, item); Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); } catch (DownloadRequestException e) { e.printStackTrace(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java index da5ebf6e1..861c6a4be 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java @@ -2,14 +2,15 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; import android.content.res.TypedArray; -import android.support.annotation.AttrRes; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import android.view.View; import android.widget.ImageButton; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DownloadRequester; public abstract class ItemActionButton { @@ -20,12 +21,12 @@ public abstract class ItemActionButton { } @StringRes - abstract public int getLabel(); + public abstract int getLabel(); @AttrRes - abstract public int getDrawable(); + public abstract int getDrawable(); - abstract public void onClick(Context context); + public abstract void onClick(Context context); public int getVisibility() { return View.VISIBLE; @@ -43,6 +44,8 @@ public abstract class ItemActionButton { return new PlayActionButton(item); } else if (isDownloadingMedia) { return new CancelDownloadActionButton(item); + } else if (UserPreferences.streamOverDownload()) { + return new StreamActionButton(item); } else if (MobileDownloadHelper.userAllowedMobileDownloads() || !MobileDownloadHelper.userChoseAddToQueue() || isInQueue) { return new DownloadActionButton(item, isInQueue); } else { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MarkAsPlayedActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MarkAsPlayedActionButton.java index 4d906cee5..354ded73d 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MarkAsPlayedActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MarkAsPlayedActionButton.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; -import android.support.annotation.AttrRes; -import android.support.annotation.StringRes; +import androidx.annotation.AttrRes; +import androidx.annotation.StringRes; import android.view.View; import de.danoeh.antennapod.R; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MobileDownloadHelper.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MobileDownloadHelper.java index f8d2a139e..77efd9023 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MobileDownloadHelper.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/MobileDownloadHelper.java @@ -3,15 +3,14 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; import android.widget.Toast; -import com.afollestad.materialdialogs.MaterialDialog; - +import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; class MobileDownloadHelper { private static long addToQueueTimestamp; @@ -27,16 +26,15 @@ class MobileDownloadHelper { } static void confirmMobileDownload(final Context context, final FeedItem item) { - MaterialDialog.Builder builder = new MaterialDialog.Builder(context) - .title(R.string.confirm_mobile_download_dialog_title) - .content(R.string.confirm_mobile_download_dialog_message) - .positiveText(context.getText(R.string.confirm_mobile_download_dialog_enable_temporarily)) - .onPositive((dialog, which) -> downloadFeedItems(context, item)); + AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setTitle(R.string.confirm_mobile_download_dialog_title) + .setMessage(R.string.confirm_mobile_download_dialog_message) + .setPositiveButton(context.getText(R.string.confirm_mobile_download_dialog_enable_temporarily), + (dialog, which) -> downloadFeedItems(context, item)); if (!DBReader.getQueueIDList().contains(item.getId())) { - builder - .content(R.string.confirm_mobile_download_dialog_message_not_in_queue) - .neutralText(R.string.confirm_mobile_download_dialog_only_add_to_queue) - .onNeutral((dialog, which) -> addToQueue(context, item)); + builder.setMessage(R.string.confirm_mobile_download_dialog_message_not_in_queue) + .setNeutralButton(R.string.confirm_mobile_download_dialog_only_add_to_queue, + (dialog, which) -> addToQueue(context, item)); } builder.show(); } @@ -50,7 +48,7 @@ class MobileDownloadHelper { private static void downloadFeedItems(Context context, FeedItem item) { allowMobileDownloadTimestamp = System.currentTimeMillis(); try { - DBTasks.downloadFeedItems(context, item); + DownloadRequester.getInstance().downloadMedia(context, item); Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); } catch (DownloadRequestException e) { e.printStackTrace(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java index 3992c7240..0d314b5eb 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; -import android.support.annotation.AttrRes; -import android.support.annotation.StringRes; +import androidx.annotation.AttrRes; +import androidx.annotation.StringRes; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; @@ -12,7 +12,6 @@ import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; import static de.danoeh.antennapod.core.service.playback.PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE; -import static de.danoeh.antennapod.core.service.playback.PlaybackService.ACTION_RESUME_PLAY_CURRENT_EPISODE; class PlayActionButton extends ItemActionButton { @@ -52,12 +51,14 @@ class PlayActionButton extends ItemActionButton { } private void togglePlayPause(Context context, FeedMedia media) { - new PlaybackServiceStarter(context, media) - .startWhenPrepared(true) - .shouldStream(false) - .start(); - - String pauseOrResume = media.isCurrentlyPlaying() ? ACTION_PAUSE_PLAY_CURRENT_EPISODE : ACTION_RESUME_PLAY_CURRENT_EPISODE; - IntentUtils.sendLocalBroadcast(context, pauseOrResume); + if (media.isCurrentlyPlaying()) { + IntentUtils.sendLocalBroadcast(context, ACTION_PAUSE_PLAY_CURRENT_EPISODE); + } else { + new PlaybackServiceStarter(context, media) + .callEvenIfRunning(true) + .startWhenPrepared(true) + .shouldStream(false) + .start(); + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/StreamActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/StreamActionButton.java new file mode 100644 index 000000000..c1e619fdf --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/StreamActionButton.java @@ -0,0 +1,64 @@ +package de.danoeh.antennapod.adapter.actionbutton; + +import android.content.Context; + +import androidx.annotation.AttrRes; +import androidx.annotation.StringRes; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; + +import static de.danoeh.antennapod.core.service.playback.PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE; + +public class StreamActionButton extends ItemActionButton { + + StreamActionButton(FeedItem item) { + super(item); + } + + @Override + @StringRes + public int getLabel() { + return R.string.stream_label; + } + + @Override + @AttrRes + public int getDrawable() { + FeedMedia media = item.getMedia(); + if (media != null && media.isCurrentlyPlaying()) { + return R.attr.av_pause; + } + return R.attr.action_stream; + } + + @Override + public void onClick(Context context) { + final FeedMedia media = item.getMedia(); + if (media == null) { + return; + } + + if (media.isPlaying()) { + togglePlayPause(context, media); + } else { + DBTasks.playMedia(context, media, false, true, true); + } + } + + private void togglePlayPause(Context context, FeedMedia media) { + if (media.isCurrentlyPlaying()) { + IntentUtils.sendLocalBroadcast(context, ACTION_PAUSE_PLAY_CURRENT_EPISODE); + } else { + new PlaybackServiceStarter(context, media) + .callEvenIfRunning(true) + .startWhenPrepared(true) + .shouldStream(true) + .start(); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java index f5213e4ab..cc3b6fba0 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.adapter.itunes; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java new file mode 100644 index 000000000..339a98dfa --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java @@ -0,0 +1,72 @@ +package de.danoeh.antennapod.asynctask; + +import android.content.Context; +import android.net.Uri; +import androidx.annotation.NonNull; +import androidx.documentfile.provider.DocumentFile; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +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; + +/** + * Writes an OPML file into the user selected export directory in the background. + */ +public class DocumentFileExportWorker { + + private final @NonNull ExportWriter exportWriter; + private @NonNull Context context; + private @NonNull Uri outputFileUri; + + public DocumentFileExportWorker(@NonNull ExportWriter exportWriter, @NonNull Context context, @NonNull Uri outputFileUri) { + this.exportWriter = exportWriter; + this.context = context; + this.outputFileUri = outputFileUri; + } + + public Observable<DocumentFile> exportObservable() { + DocumentFile output = DocumentFile.fromSingleUri(context, outputFileUri); + return Observable.create(subscriber -> { + OutputStream outputStream = null; + OutputStreamWriter writer = null; + try { + Uri uri = output.getUri(); + if (uri == null) { + throw new FileNotFoundException("Export file not found."); + } + outputStream = context.getContentResolver().openOutputStream(uri); + if (outputStream == null) { + throw new IOException(); + } + writer = new OutputStreamWriter(outputStream, LangUtils.UTF_8); + exportWriter.writeDocument(DBReader.getFeedList(), writer); + subscriber.onNext(output); + } catch (IOException e) { + subscriber.onError(e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + subscriber.onError(e); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + subscriber.onError(e); + } + } + subscriber.onComplete(); + } + }); + } + +} 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 219725b01..40b101ddf 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.asynctask; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import java.io.File; diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java index 13b95907f..b88b58537 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -3,7 +3,7 @@ package de.danoeh.antennapod.asynctask; import android.app.ProgressDialog; import android.content.Context; import android.os.AsyncTask; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.util.Log; import org.xmlpull.v1.XmlPullParserException; 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 eb70d8e0b..c3f5d898c 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java @@ -35,6 +35,6 @@ public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks { @Override public int getNotificationIconResource(Context context) { - return R.drawable.ic_stat_antenna_default; + return R.drawable.ic_antenna; } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ChooseDataFolderDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ChooseDataFolderDialog.java index c185a5557..ec285a8f6 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/ChooseDataFolderDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ChooseDataFolderDialog.java @@ -2,14 +2,16 @@ package de.danoeh.antennapod.dialog; import android.content.Context; -import com.afollestad.materialdialogs.MaterialDialog; - +import android.view.View; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DataFolderAdapter; public class ChooseDataFolderDialog { - public static abstract class RunnableWithString implements Runnable { + public abstract static class RunnableWithString implements Runnable { public RunnableWithString() { super(); } @@ -25,21 +27,23 @@ public class ChooseDataFolderDialog { DataFolderAdapter adapter = new DataFolderAdapter(context, handlerFunc); if (adapter.getItemCount() == 0) { - new MaterialDialog.Builder(context) - .title(R.string.error_label) - .content(R.string.external_storage_error_msg) - .neutralText(android.R.string.ok) + new AlertDialog.Builder(context) + .setTitle(R.string.error_label) + .setMessage(R.string.external_storage_error_msg) + .setPositiveButton(android.R.string.ok, null) .show(); return; } - MaterialDialog dialog = new MaterialDialog.Builder(context) - .title(R.string.choose_data_directory) - .content(R.string.choose_data_directory_message) - .adapter(adapter, null) - .negativeText(R.string.cancel_label) - .cancelable(true) - .build(); + View content = View.inflate(context, R.layout.choose_data_folder_dialog, null); + AlertDialog dialog = new AlertDialog.Builder(context) + .setView(content) + .setTitle(R.string.choose_data_directory) + .setMessage(R.string.choose_data_directory_message) + .setNegativeButton(R.string.cancel_label, null) + .create(); + ((RecyclerView) content.findViewById(R.id.recyclerView)).setLayoutManager(new LinearLayoutManager(context)); + ((RecyclerView) content.findViewById(R.id.recyclerView)).setAdapter(adapter); adapter.setDialog(dialog); dialog.show(); } 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 ed35495fa..ff131aeba 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -4,16 +4,6 @@ import android.app.AlertDialog; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.support.annotation.IdRes; -import android.support.annotation.NonNull; -import android.support.annotation.PluralsRes; -import android.support.annotation.StringRes; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.Fragment; -import android.support.v4.util.ArrayMap; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -24,6 +14,17 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.PluralsRes; +import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.collection.ArrayMap; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; + +import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.SpeedDialView; import java.util.ArrayList; @@ -35,10 +36,12 @@ import java.util.Map; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.FeedItemPermutors; import de.danoeh.antennapod.core.util.LongList; +import de.danoeh.antennapod.core.util.SortOrder; public class EpisodesApplyActionFragment extends Fragment { @@ -263,6 +266,18 @@ public class EpisodesApplyActionFragment extends Fragment { mSelectToggle.setTitle(titleResId); } + private static final Map<Integer, SortOrder> menuItemIdToSortOrder; + static { + Map<Integer, SortOrder> map = new ArrayMap<>(); + map.put(R.id.sort_title_a_z, SortOrder.EPISODE_TITLE_A_Z); + map.put(R.id.sort_title_z_a, SortOrder.EPISODE_TITLE_Z_A); + map.put(R.id.sort_date_new_old, SortOrder.DATE_NEW_OLD); + map.put(R.id.sort_date_old_new, SortOrder.DATE_OLD_NEW); + map.put(R.id.sort_duration_long_short, SortOrder.DURATION_LONG_SHORT); + map.put(R.id.sort_duration_short_long, SortOrder.DURATION_SHORT_LONG); + menuItemIdToSortOrder = Collections.unmodifiableMap(map); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { @StringRes int resId = 0; @@ -305,24 +320,12 @@ public class EpisodesApplyActionFragment extends Fragment { checkWithMedia(); resId = R.string.selected_has_media_label; break; - case R.id.sort_title_a_z: - sortByTitle(false); - return true; - case R.id.sort_title_z_a: - sortByTitle(true); - return true; - case R.id.sort_date_new_old: - sortByDate(true); - return true; - case R.id.sort_date_old_new: - sortByDate(false); - return true; - case R.id.sort_duration_long_short: - sortByDuration(true); - return true; - case R.id.sort_duration_short_long: - sortByDuration(false); - return true; + default: // handle various sort options + SortOrder sortOrder = menuItemIdToSortOrder.get(item.getItemId()); + if (sortOrder != null) { + sort(sortOrder); + return true; + } } if(resId != 0) { Snackbar.make(getActivity().findViewById(R.id.content), resId, Snackbar.LENGTH_SHORT) @@ -333,52 +336,9 @@ public class EpisodesApplyActionFragment extends Fragment { } } - private void sortByTitle(final boolean reverse) { - Collections.sort(episodes, (lhs, rhs) -> { - if (reverse) { - return -1 * lhs.getTitle().compareTo(rhs.getTitle()); - } else { - return lhs.getTitle().compareTo(rhs.getTitle()); - } - }); - refreshTitles(); - refreshCheckboxes(); - } - - private void sortByDate(final boolean reverse) { - Collections.sort(episodes, (lhs, rhs) -> { - if (lhs.getPubDate() == null) { - return -1; - } else if (rhs.getPubDate() == null) { - return 1; - } - int code = lhs.getPubDate().compareTo(rhs.getPubDate()); - if (reverse) { - return -1 * code; - } else { - return code; - } - }); - refreshTitles(); - refreshCheckboxes(); - } - - private void sortByDuration(final boolean reverse) { - Collections.sort(episodes, (lhs, rhs) -> { - int ordering; - if (!lhs.hasMedia()) { - ordering = 1; - } else if (!rhs.hasMedia()) { - ordering = -1; - } else { - ordering = lhs.getMedia().getDuration() - rhs.getMedia().getDuration(); - } - if(reverse) { - return -1 * ordering; - } else { - return ordering; - } - }); + private void sort(@NonNull SortOrder sortOrder) { + FeedItemPermutors.getPermutor(sortOrder) + .reorder(episodes); refreshTitles(); refreshCheckboxes(); } @@ -525,7 +485,7 @@ public class EpisodesApplyActionFragment extends Fragment { } } try { - DBTasks.downloadFeedItems(getActivity(), toDownload.toArray(new FeedItem[toDownload.size()])); + DownloadRequester.getInstance().downloadMedia(getActivity(), toDownload.toArray(new FeedItem[toDownload.size()])); } catch (DownloadRequestException e) { e.printStackTrace(); DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); 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 607084c42..d2912f90f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.dialog; import android.content.Context; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.text.TextUtils; import java.util.Arrays; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java index 933ced0f9..17668586b 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.dialog; import android.content.Context; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.InputType; import android.view.View; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/IntraFeedSortDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/IntraFeedSortDialog.java new file mode 100644 index 000000000..2ee716c7c --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/IntraFeedSortDialog.java @@ -0,0 +1,51 @@ +package de.danoeh.antennapod.dialog; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.util.SortOrder; + +public abstract class IntraFeedSortDialog { + + @Nullable + protected SortOrder currentSortOrder; + @NonNull + protected Context context; + + public IntraFeedSortDialog(@NonNull Context context, @Nullable SortOrder sortOrder) { + this.context = context; + this.currentSortOrder = sortOrder; + } + + public void openDialog() { + final String[] items = context.getResources().getStringArray(R.array.feed_episodes_sort_options); + final String[] valueStrs = context.getResources().getStringArray(R.array.feed_episodes_sort_values); + final SortOrder[] values = new SortOrder[valueStrs.length]; + for (int i = 0; i < valueStrs.length; i++) { + values[i] = SortOrder.valueOf(valueStrs[i]); + } + + int idxCurrentSort = -1; + for (int i = 0; i < values.length; i++) { + if (currentSortOrder == values[i]) { + idxCurrentSort = i; + break; + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.sort); + builder.setSingleChoiceItems(items, idxCurrentSort, (dialog, idxNewSort) -> { + updateSort(values[idxNewSort]); + dialog.dismiss(); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); + } + + protected abstract void updateSort(@NonNull SortOrder sortOrder); +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index e8c7520b7..3e4e40a5b 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -2,20 +2,22 @@ package de.danoeh.antennapod.dialog; import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import android.widget.Button; import android.widget.CheckBox; import android.widget.SeekBar; import android.widget.TextView; -import com.afollestad.materialdialogs.MaterialDialog; +import de.danoeh.antennapod.core.preferences.PlaybackPreferences; +import java.util.Locale; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; -import java.util.Locale; - public class PlaybackControlsDialog extends DialogFragment { private static final float PLAYBACK_SPEED_STEP = 0.05f; private static final float DEFAULT_MIN_PLAYBACK_SPEED = 0.5f; @@ -23,7 +25,7 @@ public class PlaybackControlsDialog extends DialogFragment { private static final String ARGUMENT_IS_PLAYING_VIDEO = "isPlayingVideo"; private PlaybackController controller; - private MaterialDialog dialog; + private AlertDialog dialog; private boolean isPlayingVideo; public static PlaybackControlsDialog newInstance(boolean isPlayingVideo) { @@ -41,7 +43,12 @@ public class PlaybackControlsDialog extends DialogFragment { @Override public void onStart() { super.onStart(); - controller = new PlaybackController(getActivity(), false); + controller = new PlaybackController(getActivity(), false) { + @Override + public void setupGUI() { + setupUi(); + } + }; controller.init(); setupUi(); } @@ -58,15 +65,14 @@ public class PlaybackControlsDialog extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { isPlayingVideo = getArguments() != null && getArguments().getBoolean(ARGUMENT_IS_PLAYING_VIDEO); - dialog = new MaterialDialog.Builder(getContext()) - .title(R.string.audio_controls) - .customView(R.layout.audio_controls, true) - .neutralText(R.string.close_label) - .onNeutral((dialog1, which) -> { - final SeekBar left = (SeekBar) dialog1.findViewById(R.id.volume_left); - final SeekBar right = (SeekBar) dialog1.findViewById(R.id.volume_right); + dialog = new AlertDialog.Builder(getContext()) + .setTitle(R.string.audio_controls) + .setView(R.layout.audio_controls) + .setPositiveButton(R.string.close_label, (dialog1, which) -> { + final SeekBar left = dialog.findViewById(R.id.volume_left); + final SeekBar right = dialog.findViewById(R.id.volume_right); UserPreferences.setVolume(left.getProgress(), right.getProgress()); - }).build(); + }).create(); return dialog; } @@ -109,6 +115,7 @@ public class PlaybackControlsDialog extends DialogFragment { controller.setPlaybackSpeed(playbackSpeed); String speedPref = String.format(Locale.US, "%.2f", playbackSpeed); + PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(playbackSpeed); if (isPlayingVideo) { UserPreferences.setVideoPlaybackSpeed(speedPref); } else { @@ -135,7 +142,7 @@ public class PlaybackControlsDialog extends DialogFragment { public void onStopTrackingTouch(SeekBar seekBar) { } }); - barPlaybackSpeed.setProgress((int) ((currentSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP)); + barPlaybackSpeed.setProgress(Math.round((currentSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP)); final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left); barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage()); @@ -206,9 +213,11 @@ public class PlaybackControlsDialog extends DialogFragment { } private float getCurrentSpeed() { - if (isPlayingVideo) { - return UserPreferences.getVideoPlaybackSpeed(); + Playable media = null; + if (controller != null) { + media = controller.getMedia(); } - return UserPreferences.getPlaybackSpeed(); + + return PlaybackSpeedUtils.getCurrentPlaybackSpeed(media); } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java index c1008a380..11256f2de 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java @@ -4,7 +4,8 @@ import android.app.Dialog; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; -import android.support.v4.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -16,10 +17,6 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; -import com.afollestad.materialdialogs.internal.MDButton; - import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; @@ -48,7 +45,7 @@ public class ProxyDialog { private final Context context; - private MaterialDialog dialog; + private AlertDialog dialog; private Spinner spType; private EditText etHost; @@ -64,54 +61,53 @@ public class ProxyDialog { this.context = context; } - public Dialog createDialog() { - dialog = new MaterialDialog.Builder(context) - .title(R.string.pref_proxy_title) - .customView(R.layout.proxy_settings, true) - .positiveText(R.string.proxy_test_label) - .negativeText(R.string.cancel_label) - .onPositive((dialog1, which) -> { - if(!testSuccessful) { - dialog.getActionButton(DialogAction.POSITIVE).setEnabled(false); - test(); - return; - } - String type = (String) ((Spinner) dialog1.findViewById(R.id.spType)).getSelectedItem(); - ProxyConfig proxy; - if(Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { - proxy = ProxyConfig.direct(); - } else { - String host = etHost.getText().toString(); - String port = etPort.getText().toString(); - String username = etUsername.getText().toString(); - if(TextUtils.isEmpty(username)) { - username = null; - } - String password = etPassword.getText().toString(); - if(TextUtils.isEmpty(password)) { - password = null; - } - int portValue = 0; - if(!TextUtils.isEmpty(port)) { - portValue = Integer.valueOf(port); - } - if (Proxy.Type.valueOf(type) == Proxy.Type.SOCKS) { - proxy = ProxyConfig.socks(host, portValue, username, password); - } else { - proxy = ProxyConfig.http(host, portValue, username, password); - } - } - UserPreferences.setProxyConfig(proxy); - AntennapodHttpClient.reinit(); - dialog.dismiss(); - }) - .onNegative((dialog1, which) -> dialog1.dismiss()) - .autoDismiss(false) - .build(); - View view = dialog.getCustomView(); - spType = view.findViewById(R.id.spType); + public Dialog show() { + View content = View.inflate(context, R.layout.proxy_settings, null); + dialog = new AlertDialog.Builder(context) + .setTitle(R.string.pref_proxy_title) + .setView(content) + .setNegativeButton(R.string.cancel_label, null) + .setPositiveButton(R.string.proxy_test_label, null) + .show(); + // To prevent cancelling the dialog on button click + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener((view) -> { + if (!testSuccessful) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + test(); + return; + } + String type = (String) ((Spinner) content.findViewById(R.id.spType)).getSelectedItem(); + ProxyConfig proxy; + if (Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { + proxy = ProxyConfig.direct(); + } else { + String host = etHost.getText().toString(); + String port = etPort.getText().toString(); + String username = etUsername.getText().toString(); + if(TextUtils.isEmpty(username)) { + username = null; + } + String password = etPassword.getText().toString(); + if(TextUtils.isEmpty(password)) { + password = null; + } + int portValue = 0; + if(!TextUtils.isEmpty(port)) { + portValue = Integer.valueOf(port); + } + if (Proxy.Type.valueOf(type) == Proxy.Type.SOCKS) { + proxy = ProxyConfig.socks(host, portValue, username, password); + } else { + proxy = ProxyConfig.http(host, portValue, username, password); + } + } + UserPreferences.setProxyConfig(proxy); + AntennapodHttpClient.reinit(); + dialog.dismiss(); + }); - List<String> types= new ArrayList<>(); + spType = content.findViewById(R.id.spType); + List<String> types = new ArrayList<>(); types.add(Proxy.Type.DIRECT.name()); types.add(Proxy.Type.HTTP.name()); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -123,22 +119,22 @@ public class ProxyDialog { spType.setAdapter(adapter); ProxyConfig proxyConfig = UserPreferences.getProxyConfig(); spType.setSelection(adapter.getPosition(proxyConfig.type.name())); - etHost = view.findViewById(R.id.etHost); + etHost = content.findViewById(R.id.etHost); if(!TextUtils.isEmpty(proxyConfig.host)) { etHost.setText(proxyConfig.host); } etHost.addTextChangedListener(requireTestOnChange); - etPort = view.findViewById(R.id.etPort); + etPort = content.findViewById(R.id.etPort); if(proxyConfig.port > 0) { etPort.setText(String.valueOf(proxyConfig.port)); } etPort.addTextChangedListener(requireTestOnChange); - etUsername = view.findViewById(R.id.etUsername); + etUsername = content.findViewById(R.id.etUsername); if(!TextUtils.isEmpty(proxyConfig.username)) { etUsername.setText(proxyConfig.username); } etUsername.addTextChangedListener(requireTestOnChange); - etPassword = view.findViewById(R.id.etPassword); + etPassword = content.findViewById(R.id.etPassword); if(!TextUtils.isEmpty(proxyConfig.password)) { etPassword.setText(proxyConfig.username); } @@ -159,7 +155,7 @@ public class ProxyDialog { enableSettings(false); } }); - txtvMessage = view.findViewById(R.id.txtvMessage); + txtvMessage = content.findViewById(R.id.txtvMessage); checkValidity(); return dialog; } @@ -230,14 +226,12 @@ public class ProxyDialog { private void setTestRequired(boolean required) { if(required) { testSuccessful = false; - MDButton button = dialog.getActionButton(DialogAction.POSITIVE); - button.setText(context.getText(R.string.proxy_test_label)); - button.setEnabled(true); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.proxy_test_label); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); } else { testSuccessful = true; - MDButton button = dialog.getActionButton(DialogAction.POSITIVE); - button.setText(context.getText(android.R.string.ok)); - button.setEnabled(true); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(android.R.string.ok); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java index 24656ed29..7cb274708 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java @@ -2,19 +2,17 @@ package de.danoeh.antennapod.dialog; import android.app.Dialog; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import android.util.Log; -import com.afollestad.materialdialogs.MaterialDialog; - import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; +import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.util.IntentUtils; public class RatingDialog { @@ -59,14 +57,10 @@ public class RatingDialog { private static void rateNow() { Context context = mContext.get(); - if(context == null) { + if (context == null) { return; } - final String appPackage = "de.danoeh.antennapod"; - final Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=" + appPackage); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + IntentUtils.openInBrowser(context, "https://play.google.com/store/apps/details?id=de.danoeh.antennapod"); saveRated(); } @@ -102,21 +96,18 @@ public class RatingDialog { } @Nullable - private static MaterialDialog createDialog() { + private static AlertDialog createDialog() { Context context = mContext.get(); - if(context == null) { + if (context == null) { return null; } - return new MaterialDialog.Builder(context) - .title(R.string.rating_title) - .content(R.string.rating_message) - .positiveText(R.string.rating_now_label) - .negativeText(R.string.rating_never_label) - .neutralText(R.string.rating_later_label) - .onPositive((dialog, which) -> rateNow()) - .onNegative((dialog, which) -> saveRated()) - .onNeutral((dialog, which) -> resetStartDate()) - .cancelListener(dialog1 -> resetStartDate()) - .build(); + return new AlertDialog.Builder(context) + .setTitle(R.string.rating_title) + .setMessage(R.string.rating_message) + .setPositiveButton(R.string.rating_now_label, (dialog, which) -> rateNow()) + .setNegativeButton(R.string.rating_never_label, (dialog, which) -> saveRated()) + .setNeutralButton(R.string.rating_later_label, (dialog, which) -> resetStartDate()) + .setOnCancelListener(dialog1 -> resetStartDate()) + .create(); } } 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 31a544582..699c6f492 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java @@ -3,10 +3,12 @@ package de.danoeh.antennapod.dialog; import android.app.Activity; import android.text.InputType; -import com.afollestad.materialdialogs.MaterialDialog; - 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; @@ -25,20 +27,24 @@ public class RenameFeedDialog { if(activity == null) { return; } - new MaterialDialog.Builder(activity) - .title(de.danoeh.antennapod.core.R.string.rename_feed_label) - .inputType(InputType.TYPE_CLASS_TEXT) - .input(feed.getTitle(), feed.getTitle(), true, (dialog, input) -> { - feed.setCustomTitle(input.toString()); + + View content = View.inflate(activity, R.layout.edit_text_dialog, null); + EditText editText = content.findViewById(R.id.text); + editText.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()); DBWriter.setFeedCustomTitle(feed); - dialog.dismiss(); }) - .neutralText(de.danoeh.antennapod.core.R.string.reset) - .onNeutral((dialog, which) -> dialog.getInputEditText().setText(feed.getFeedTitle())) - .negativeText(de.danoeh.antennapod.core.R.string.cancel_label) - .onNegative((dialog, which) -> dialog.dismiss()) - .autoDismiss(false) + .setNeutralButton(de.danoeh.antennapod.core.R.string.reset, null) + .setNegativeButton(de.danoeh.antennapod.core.R.string.cancel_label, null) .show(); + + // To prevent cancelling the dialog on button click + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener( + (view) -> editText.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 dc056a3f9..8d176c708 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.content.Context; import android.text.Editable; import android.text.TextWatcher; -import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; @@ -12,9 +11,7 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; - +import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; @@ -26,7 +23,7 @@ public abstract class SleepTimerDialog { private final Context context; - private MaterialDialog dialog; + private AlertDialog dialog; private EditText etxtTime; private Spinner spTimeUnit; private CheckBox cbShakeToReset; @@ -38,40 +35,38 @@ public abstract class SleepTimerDialog { this.context = context; } - public MaterialDialog createNewDialog() { - MaterialDialog.Builder builder = new MaterialDialog.Builder(context); - builder.title(R.string.set_sleeptimer_label); - builder.customView(R.layout.time_dialog, false); - builder.positiveText(R.string.set_sleeptimer_label); - builder.negativeText(R.string.cancel_label); - builder.onNegative((dialog, which) -> dialog.dismiss()); - builder.onPositive((dialog, which) -> { - try { - savePreferences(); - long input = SleepTimerPreferences.timerMillis(); - onTimerSet(input, cbShakeToReset.isChecked(), cbVibrate.isChecked()); - dialog.dismiss(); - } catch (NumberFormatException e) { - e.printStackTrace(); - Toast toast = Toast.makeText(context, R.string.time_dialog_invalid_input, - Toast.LENGTH_LONG); - toast.show(); - } - }); - dialog = builder.build(); - - View view = dialog.getView(); - etxtTime = view.findViewById(R.id.etxtTime); - spTimeUnit = view.findViewById(R.id.spTimeUnit); - cbShakeToReset = view.findViewById(R.id.cbShakeToReset); - cbVibrate = view.findViewById(R.id.cbVibrate); - chAutoEnable = view.findViewById(R.id.chAutoEnable); + public AlertDialog createNewDialog() { + View content = View.inflate(context, R.layout.time_dialog, null); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.set_sleeptimer_label); + builder.setView(content); + builder.setNegativeButton(R.string.cancel_label, (dialog, which) -> dialog.dismiss()); + builder.setPositiveButton(R.string.set_sleeptimer_label, (dialog, which) -> { + try { + savePreferences(); + long input = SleepTimerPreferences.timerMillis(); + onTimerSet(input, cbShakeToReset.isChecked(), cbVibrate.isChecked()); + dialog.dismiss(); + } catch (NumberFormatException e) { + e.printStackTrace(); + Toast toast = Toast.makeText(context, R.string.time_dialog_invalid_input, + Toast.LENGTH_LONG); + toast.show(); + } + }); + dialog = builder.create(); + + etxtTime = content.findViewById(R.id.etxtTime); + spTimeUnit = content.findViewById(R.id.spTimeUnit); + cbShakeToReset = content.findViewById(R.id.cbShakeToReset); + cbVibrate = content.findViewById(R.id.cbVibrate); + chAutoEnable = content.findViewById(R.id.chAutoEnable); etxtTime.setText(SleepTimerPreferences.lastTimerValue()); etxtTime.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { - checkInputLength(s.length()); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0); } @Override @@ -109,16 +104,6 @@ public abstract class SleepTimerDialog { return dialog; } - private void checkInputLength(int length) { - if (length > 0) { - Log.d(TAG, "Length is larger than 0, enabling confirm button"); - dialog.getActionButton(DialogAction.POSITIVE).setEnabled(true); - } else { - Log.d(TAG, "Length is smaller than 0, disabling confirm button"); - dialog.getActionButton(DialogAction.POSITIVE).setEnabled(false); - } - } - public abstract void onTimerSet(long millis, boolean shakeToReset, boolean vibrate); private void savePreferences() { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java index bf3faf89a..ef624ebe6 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -1,37 +1,21 @@ package de.danoeh.antennapod.dialog; -import android.content.ActivityNotFoundException; import android.content.Context; -import android.content.Intent; -import android.net.Uri; import android.os.Build; -import android.support.v7.app.AlertDialog; -import android.util.Log; -import android.view.View; - -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; +import androidx.appcompat.app.AlertDialog; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; import java.util.Arrays; import java.util.List; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.IntentUtils; - public class VariableSpeedDialog { - private static final String TAG = VariableSpeedDialog.class.getSimpleName(); - - private static final Intent playStoreIntent = new Intent(Intent.ACTION_VIEW, - Uri.parse("market://details?id=com.falconware.prestissimo")); - private VariableSpeedDialog() { } public static void showDialog(final Context context) { - if (org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(context) - || UserPreferences.useSonic() + if (UserPreferences.useSonic() || UserPreferences.useExoplayer() || Build.VERSION.SDK_INT >= 23) { showSpeedSelectorDialog(context); @@ -45,38 +29,17 @@ public class VariableSpeedDialog { } private static void showGetPluginDialog(final Context context, boolean showSpeedSelector) { - MaterialDialog.Builder builder = new MaterialDialog.Builder(context); - builder.title(R.string.no_playback_plugin_title); - builder.content(R.string.no_playback_plugin_or_sonic_msg); - builder.positiveText(R.string.enable_sonic); - builder.negativeText(R.string.download_plugin_label); - builder.neutralText(R.string.close_label); - builder.onPositive((dialog, which) -> { - if (Build.VERSION.SDK_INT >= 16) { // just to be safe - UserPreferences.enableSonic(); - if(showSpeedSelector) { - showSpeedSelectorDialog(context); - } - } - }); - builder.onNegative((dialog, which) -> { - try { - context.startActivity(playStoreIntent); - } catch (ActivityNotFoundException e) { - // this is usually thrown on an emulator if the Android market is not installed - Log.e(TAG, Log.getStackTraceString(e)); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.no_playback_plugin_title); + builder.setMessage(R.string.no_playback_plugin_or_sonic_msg); + builder.setPositiveButton(R.string.enable_sonic, (dialog, which) -> { + UserPreferences.enableSonic(); + if (showSpeedSelector) { + showSpeedSelectorDialog(context); } }); - builder.forceStacking(true); - MaterialDialog dialog = builder.show(); - if (Build.VERSION.SDK_INT < 16) { - View pos = dialog.getActionButton(DialogAction.POSITIVE); - pos.setEnabled(false); - } - if(!IntentUtils.isCallable(context.getApplicationContext(), playStoreIntent)) { - View pos = dialog.getActionButton(DialogAction.NEGATIVE); - pos.setEnabled(false); - } + builder.setNeutralButton(R.string.close_label, null); + builder.show(); } private static void showSpeedSelectorDialog(final Context context) { diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java index ca9ed83d7..6535df5ef 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.discovery; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; import de.mfietz.fyydlin.SearchHit; import org.json.JSONArray; 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 3ef010f88..2cfe7c1e8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -2,20 +2,15 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Bundle; -import android.provider.MediaStore; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.ContextMenu; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.EditText; -import android.widget.ImageView; -import android.widget.TextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; @@ -51,7 +46,7 @@ public class AddFeedFragment extends Fragment { View butOpmlImport = root.findViewById(R.id.btn_opml_import); butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(), OpmlImportFromPathActivity.class))); - + root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch()); return root; } 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 bb52b26b7..3949a03a9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -1,9 +1,9 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; 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 bb8f4df9a..2df28b262 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.support.v4.app.ListFragment; +import androidx.fragment.app.ListFragment; import android.util.Log; import android.view.View; import android.widget.ListView; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java index 1d9020f0d..47d7a0b86 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java @@ -2,9 +2,9 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; +import androidx.fragment.app.Fragment; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.widget.SearchView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -101,7 +101,6 @@ public class CombinedSearchFragment extends Fragment { inflater.inflate(R.menu.itunes_search, menu); MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.search_label)); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override 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 705151062..7f70daaec 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.ListFragment; +import androidx.annotation.NonNull; +import androidx.fragment.app.ListFragment; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -16,7 +16,8 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.DownloadedEpisodesListAdapter; -import de.danoeh.antennapod.core.feed.EventDistributor; +import de.danoeh.antennapod.core.event.DownloadLogEvent; +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; @@ -27,6 +28,8 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE; @@ -38,10 +41,6 @@ public class CompletedDownloadsFragment extends ListFragment { private static final String TAG = CompletedDownloadsFragment.class.getSimpleName(); - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | - EventDistributor.DOWNLOADLOG_UPDATE | - EventDistributor.UNREAD_ITEMS_UPDATE; - private List<FeedItem> items = new ArrayList<>(); private DownloadedEpisodesListAdapter listAdapter; private Disposable disposable; @@ -56,19 +55,24 @@ public class CompletedDownloadsFragment extends ListFragment { listAdapter = new DownloadedEpisodesListAdapter(getActivity(), itemAccess); setListAdapter(listAdapter); setListShown(false); + EventBus.getDefault().register(this); + } + + @Override + public void onDestroyView() { + EventBus.getDefault().unregister(this); + super.onDestroyView(); } @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); loadItems(); } @Override public void onStop() { super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); if (disposable != null) { disposable.dispose(); } @@ -79,7 +83,7 @@ public class CompletedDownloadsFragment extends ListFragment { super.onListItemClick(l, v, position, id); position -= l.getHeaderViewsCount(); long[] ids = FeedItemUtil.getIds(items); - ((MainActivity) requireActivity()).loadChildFragment(ItemFragment.newInstance(ids, position)); + ((MainActivity) requireActivity()).loadChildFragment(ItemPagerFragment.newInstance(ids, position)); } @Override @@ -135,14 +139,15 @@ public class CompletedDownloadsFragment extends ListFragment { } }; - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EVENTS) != 0) { - loadItems(); - } - } - }; + @Subscribe + public void onDownloadLogChanged(DownloadLogEvent event) { + loadItems(); + } + + @Subscribe + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + loadItems(); + } private void loadItems() { if (disposable != null) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index db9dd9530..5467d71a8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -15,6 +15,7 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import io.reactivex.Maybe; @@ -70,7 +71,7 @@ public class CoverFragment extends Fragment { txtvPodcastTitle.setText(media.getFeedTitle()); txtvEpisodeTitle.setText(media.getEpisodeTitle()); Glide.with(this) - .load(media.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(media)) .apply(new RequestOptions() .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) .dontAnimate() 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 26b115b4b..16337b00d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -4,8 +4,8 @@ import android.app.AlertDialog; import android.app.Dialog; import android.content.res.TypedArray; import android.os.Bundle; -import android.support.v4.app.ListFragment; -import android.support.v4.view.MenuItemCompat; +import androidx.fragment.app.ListFragment; +import androidx.core.view.MenuItemCompat; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -19,7 +19,7 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DownloadLogAdapter; -import de.danoeh.antennapod.core.feed.EventDistributor; +import de.danoeh.antennapod.core.event.DownloadLogEvent; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.service.download.DownloadStatus; @@ -30,6 +30,8 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; /** * Shows the download log @@ -45,14 +47,12 @@ public class DownloadLogFragment extends ListFragment { @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); loadItems(); } @Override public void onStop() { super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); if (disposable != null) { disposable.dispose(); } @@ -77,6 +77,13 @@ public class DownloadLogFragment extends ListFragment { adapter = new DownloadLogAdapter(getActivity(), itemAccess); setListAdapter(adapter); + EventBus.getDefault().register(this); + } + + @Override + public void onDestroyView() { + EventBus.getDefault().unregister(this); + super.onDestroyView(); } private void onFragmentLoaded() { @@ -133,15 +140,10 @@ public class DownloadLogFragment extends ListFragment { } }; - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) { - loadItems(); - } - } - }; + @Subscribe + public void onDownloadLogChanged(DownloadLogEvent event) { + loadItems(); + } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java index aa6029c84..b1bcdf404 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java @@ -4,11 +4,11 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; +import com.google.android.material.tabs.TabLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java index ca21df661..8cdec9f38 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java @@ -4,11 +4,11 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; +import com.google.android.material.tabs.TabLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 1cedb5a91..5dbb703b7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -4,15 +4,13 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.SimpleItemAnimator; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.core.view.MenuItemCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.SearchView; +import androidx.recyclerview.widget.SimpleItemAnimator; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -23,8 +21,20 @@ import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.joanzapata.iconify.Iconify; + import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; + +import de.danoeh.antennapod.core.event.FeedListUpdateEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.ArrayList; +import java.util.List; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; @@ -32,22 +42,16 @@ import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; -import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedItemFilter; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; 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.LongList; -import de.danoeh.antennapod.dialog.FilterDialog; +import de.danoeh.antennapod.core.util.download.AutoUpdateManager; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; import de.danoeh.antennapod.view.EmptyViewHandler; @@ -55,13 +59,6 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; /** * Shows unread or recently published episodes @@ -69,11 +66,6 @@ import java.util.Set; public abstract class EpisodesListFragment extends Fragment { public static final String TAG = "EpisodesListFragment"; - - private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE | - EventDistributor.UNREAD_ITEMS_UPDATE | - EventDistributor.PLAYER_STATUS_UPDATE; - private static final String DEFAULT_PREF_NAME = "PrefAllEpisodesFragment"; private static final String PREF_SCROLL_POSITION = "scroll_position"; private static final String PREF_SCROLL_OFFSET = "scroll_offset"; @@ -107,7 +99,6 @@ public abstract class EpisodesListFragment extends Fragment { public void onStart() { super.onStart(); setHasOptionsMenu(true); - EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); loadItems(); } @@ -129,7 +120,6 @@ public abstract class EpisodesListFragment extends Fragment { public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); - EventDistributor.getInstance().unregister(contentUpdate); if (disposable != null) { disposable.dispose(); } @@ -149,7 +139,7 @@ public abstract class EpisodesListFragment extends Fragment { SharedPreferences.Editor editor = prefs.edit(); editor.putInt(PREF_SCROLL_POSITION, firstItem); editor.putFloat(PREF_SCROLL_OFFSET, topOffset); - editor.commit(); + editor.apply(); } private void restoreScrollPosition() { @@ -162,7 +152,7 @@ public abstract class EpisodesListFragment extends Fragment { SharedPreferences.Editor editor = prefs.edit(); editor.putInt(PREF_SCROLL_POSITION, 0); editor.putFloat(PREF_SCROLL_OFFSET, 0.0f); - editor.commit(); + editor.apply(); } } @@ -179,7 +169,6 @@ public abstract class EpisodesListFragment extends Fragment { MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.search_hint)); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override @@ -202,10 +191,7 @@ public abstract class EpisodesListFragment extends Fragment { if (!super.onOptionsItemSelected(item)) { switch (item.getItemId()) { case R.id.refresh_item: - List<Feed> feeds = ((MainActivity) getActivity()).getFeeds(); - if (feeds != null) { - DBTasks.refreshAllFeeds(getActivity(), feeds); - } + AutoUpdateManager.runImmediate(requireContext()); return true; case R.id.mark_all_read_item: ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(), @@ -262,17 +248,7 @@ public abstract class EpisodesListFragment extends Fragment { } FeedItem selectedItem = listAdapter.getSelectedItem(); - // Remove new flag contains UI logic specific to All/New/FavoriteSegments, - // e.g., Undo with Snackbar, - // and is handled by this class rather than the generic FeedItemMenuHandler - // Undo is useful for Remove new flag, given there is no UI to undo it otherwise, - // i.e., there is context menu item for Mark as new - if (R.id.remove_new_flag_item == item.getItemId()) { - removeNewFlagWithUndo(selectedItem); - return true; - } - - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); + return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem); } @NonNull @@ -403,6 +379,20 @@ public abstract class EpisodesListFragment extends Fragment { } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (listAdapter != null) { + for (int i = 0; i < listAdapter.getItemCount(); i++) { + AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) + recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; + } + } + } + } + protected boolean shouldUpdatedItemRemainInList(FeedItem item) { return true; } @@ -412,7 +402,7 @@ public abstract class EpisodesListFragment extends Fragment { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; downloaderList = update.downloaders; - if (isMenuInvalidationAllowed && isUpdatingFeeds != update.feedIds.length > 0) { + if (isMenuInvalidationAllowed && event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) { requireActivity().invalidateOptionsMenu(); } if (update.mediaIds.length > 0) { @@ -425,17 +415,27 @@ public abstract class EpisodesListFragment extends Fragment { } } - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EVENTS) != 0) { - loadItems(); - if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { - requireActivity().invalidateOptionsMenu(); - } - } + private void updateUi() { + loadItems(); + if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { + requireActivity().invalidateOptionsMenu(); } - }; + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerStatusChanged(PlayerStatusEvent event) { + updateUi(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + updateUi(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onFeedListChanged(FeedListUpdateEvent event) { + updateUi(); + } void loadItems() { if (disposable != null) { @@ -453,36 +453,4 @@ public abstract class EpisodesListFragment extends Fragment { @NonNull protected abstract List<FeedItem> loadData(); - - void removeNewFlagWithUndo(FeedItem item) { - if (item == null) { - return; - } - - Log.d(TAG, "removeNewFlagWithUndo(" + item.getId() + ")"); - if (disposable != null) { - disposable.dispose(); - } - // we're marking it as unplayed since the user didn't actually play it - // but they don't want it considered 'NEW' anymore - DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId()); - - final Handler h = new Handler(getActivity().getMainLooper()); - final Runnable r = () -> { - FeedMedia media = item.getMedia(); - if (media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) { - DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId()); - } - }; - - Snackbar snackbar = Snackbar.make(getView(), getString(R.string.removed_new_flag_label), - Snackbar.LENGTH_LONG); - snackbar.setAction(getString(R.string.undo), v -> { - DBWriter.markItemPlayed(FeedItem.NEW, item.getId()); - // don't forget to cancel the thing that's going to remove the media - h.removeCallbacks(r); - }); - snackbar.show(); - h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f)); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 348c73b92..ce2232a55 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -3,8 +3,8 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.ActivityOptionsCompat; -import android.support.v4.app.Fragment; +import androidx.core.app.ActivityOptionsCompat; +import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -18,16 +18,20 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; /** * Fragment which is supposed to be displayed outside of the MediaplayerActivity @@ -67,9 +71,9 @@ public class ExternalPlayerFragment extends Fragment { if (controller != null && controller.getMedia() != null) { Intent intent = PlaybackService.getPlayerActivityIntent(getActivity(), controller.getMedia()); - if (Build.VERSION.SDK_INT >= 16 && controller.getMedia().getMediaType() == MediaType.AUDIO) { - ActivityOptionsCompat options = ActivityOptionsCompat. - makeSceneTransitionAnimation(getActivity(), imgvCover, "coverTransition"); + if (controller.getMedia().getMediaType() == MediaType.AUDIO) { + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation(getActivity(), imgvCover, "coverTransition"); startActivity(intent, options.toBundle()); } else { startActivity(intent); @@ -138,6 +142,7 @@ public class ExternalPlayerFragment extends Fragment { controller = setupPlaybackController(); controller.init(); loadMediaInfo(); + EventBus.getDefault().register(this); } @Override @@ -147,6 +152,12 @@ public class ExternalPlayerFragment extends Fragment { controller.release(); controller = null; } + EventBus.getDefault().unregister(this); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + onPositionObserverUpdate(); } @Override @@ -217,7 +228,7 @@ public class ExternalPlayerFragment extends Fragment { onPositionObserverUpdate(); Glide.with(getActivity()) - .load(media.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(media)) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java index 536ebd468..f73735658 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -1,13 +1,12 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; +import androidx.annotation.NonNull; +import com.google.android.material.snackbar.Snackbar; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.ItemTouchHelper; import android.util.Log; import android.view.LayoutInflater; -import android.view.Menu; import android.view.View; import android.view.ViewGroup; @@ -55,7 +54,8 @@ public class FavoriteEpisodesFragment extends EpisodesListFragment { emptyView.setTitle(R.string.no_fav_episodes_head_label); emptyView.setMessage(R.string.no_fav_episodes_label); - ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { + ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, + ItemTouchHelper.LEFT) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java new file mode 100644 index 000000000..3b843e150 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -0,0 +1,226 @@ +package de.danoeh.antennapod.fragment; + +import android.content.ClipData; +import android.content.Context; +import android.content.Intent; +import android.graphics.LightingColorFilter; +import android.net.Uri; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import android.text.TextUtils; +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; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.joanzapata.iconify.Iconify; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.glide.FastBlurTransformation; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.LangUtils; +import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; +import de.danoeh.antennapod.menuhandler.FeedMenuHandler; +import io.reactivex.Maybe; +import io.reactivex.MaybeOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +/** + * Displays information about a feed. + */ +public class FeedInfoFragment extends Fragment { + + private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; + private static final String TAG = "FeedInfoActivity"; + + private Feed feed; + private Disposable disposable; + private ImageView imgvCover; + private TextView txtvTitle; + private TextView txtvDescription; + private TextView lblLanguage; + private TextView txtvLanguage; + private TextView lblAuthor; + private TextView txtvAuthor; + private TextView txtvUrl; + private TextView txtvAuthorHeader; + private ImageView imgvBackground; + + public static FeedInfoFragment newInstance(Feed feed) { + FeedInfoFragment fragment = new FeedInfoFragment(); + Bundle arguments = new Bundle(); + arguments.putLong(EXTRA_FEED_ID, feed.getId()); + fragment.setArguments(arguments); + return fragment; + } + + private final View.OnClickListener copyUrlToClipboard = new View.OnClickListener() { + @Override + public void onClick(View v) { + if(feed != null && feed.getDownload_url() != null) { + String url = feed.getDownload_url(); + ClipData clipData = ClipData.newPlainText(url, url); + android.content.ClipboardManager cm = (android.content.ClipboardManager) getContext() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(clipData); + Toast t = Toast.makeText(getContext(), R.string.copied_url_msg, Toast.LENGTH_SHORT); + t.show(); + } + } + }; + + @Override + public void onResume() { + super.onResume(); + ((MainActivity)getActivity()).getSupportActionBar().setTitle(R.string.feed_info_label); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.feedinfo, null); + setHasOptionsMenu(true); + + imgvCover = root.findViewById(R.id.imgvCover); + txtvTitle = root.findViewById(R.id.txtvTitle); + txtvAuthorHeader = root.findViewById(R.id.txtvAuthor); + imgvBackground = root.findViewById(R.id.imgvBackground); + root.findViewById(R.id.butShowInfo).setVisibility(View.INVISIBLE); + root.findViewById(R.id.butShowSettings).setVisibility(View.INVISIBLE); + // https://github.com/bumptech/glide/issues/529 + imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); + + + txtvDescription = root.findViewById(R.id.txtvDescription); + lblLanguage = root.findViewById(R.id.lblLanguage); + txtvLanguage = root.findViewById(R.id.txtvLanguage); + lblAuthor = root.findViewById(R.id.lblAuthor); + txtvAuthor = root.findViewById(R.id.txtvDetailsAuthor); + txtvUrl = root.findViewById(R.id.txtvUrl); + + txtvUrl.setOnClickListener(copyUrlToClipboard); + postponeEnterTransition(); + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + long feedId = getArguments().getLong(EXTRA_FEED_ID); + disposable = Maybe.create((MaybeOnSubscribe<Feed>) emitter -> { + Feed feed = DBReader.getFeed(feedId); + if (feed != null) { + emitter.onSuccess(feed); + } else { + emitter.onComplete(); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + feed = result; + showFeed(); + }, error -> Log.d(TAG, Log.getStackTraceString(error)), + this::startPostponedEnterTransition); + } + + private void showFeed() { + Log.d(TAG, "Language is " + feed.getLanguage()); + Log.d(TAG, "Author is " + feed.getAuthor()); + Log.d(TAG, "URL is " + feed.getDownload_url()); + Glide.with(getContext()) + .load(feed.getImageLocation()) + .apply(new RequestOptions() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate()) + .into(imgvCover); + Glide.with(getContext()) + .load(feed.getImageLocation()) + .apply(new RequestOptions() + .placeholder(R.color.image_readability_tint) + .error(R.color.image_readability_tint) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .transform(new FastBlurTransformation()) + .dontAnimate()) + .into(imgvBackground); + + txtvTitle.setText(feed.getTitle()); + + String description = HtmlToPlainText.getPlainText(feed.getDescription()); + + txtvDescription.setText(description); + + if (!TextUtils.isEmpty(feed.getAuthor())) { + txtvAuthor.setText(feed.getAuthor()); + txtvAuthorHeader.setText(feed.getAuthor()); + } else { + lblAuthor.setVisibility(View.GONE); + txtvAuthor.setVisibility(View.GONE); + } + if (!TextUtils.isEmpty(feed.getLanguage())) { + txtvLanguage.setText(LangUtils.getLanguageString(feed.getLanguage())); + } else { + lblLanguage.setVisibility(View.GONE); + txtvLanguage.setVisibility(View.GONE); + } + txtvUrl.setText(feed.getDownload_url() + " {fa-paperclip}"); + Iconify.addIcons(txtvUrl); + + getActivity().invalidateOptionsMenu(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposable != null) { + disposable.dispose(); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.feedinfo, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null); + menu.findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null && + IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink())))); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + boolean handled = false; + try { + handled = FeedMenuHandler.onOptionsItemClicked(getContext(), item, feed); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(getContext(), e.getMessage()); + } + return handled || super.onOptionsItemSelected(item); + } +} 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 4e4b40096..f03aef207 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -3,13 +3,8 @@ package de.danoeh.antennapod.fragment; import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; import android.graphics.LightingColorFilter; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.ListFragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -24,6 +19,11 @@ import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.SearchView; +import androidx.core.view.MenuItemCompat; +import androidx.fragment.app.ListFragment; + import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.joanzapata.iconify.Iconify; @@ -37,8 +37,6 @@ import org.greenrobot.eventbus.ThreadMode; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.FeedInfoActivity; -import de.danoeh.antennapod.activity.FeedSettingsActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.FeedItemlistAdapter; import de.danoeh.antennapod.core.asynctask.FeedRemover; @@ -47,7 +45,12 @@ import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; + +import de.danoeh.antennapod.core.event.FeedListUpdateEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; + import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedEvent; import de.danoeh.antennapod.core.feed.FeedItem; @@ -61,6 +64,7 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.FeedItemPermutors; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.Optional; @@ -81,12 +85,6 @@ import io.reactivex.schedulers.Schedulers; @SuppressLint("ValidFragment") public class FeedItemlistFragment extends ListFragment { private static final String TAG = "ItemlistFragment"; - - private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE - | EventDistributor.FEED_LIST_UPDATE - | EventDistributor.PLAYER_STATUS_UPDATE; - - public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem"; private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; private FeedItemlistAdapter adapter; @@ -140,37 +138,31 @@ public class FeedItemlistFragment extends ListFragment { } @Override - public void onStart() { - super.onStart(); - EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().register(this); - loadItems(); + public void onHiddenChanged(boolean hidden) { + super.onHiddenChanged(hidden); + if (!hidden && getActivity() != null) { + ((MainActivity) getActivity()).getSupportActionBar().setTitle(""); + } } @Override - public void onResume() { - super.onResume(); - ((MainActivity)getActivity()).getSupportActionBar().setTitle(""); - updateProgressBarVisibility(); - } + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - @Override - public void onStop() { - super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); - EventBus.getDefault().unregister(this); - if(disposable != null) { - disposable.dispose(); - } + registerForContextMenu(getListView()); + + EventBus.getDefault().register(this); + loadItems(); } @Override public void onDestroyView() { super.onDestroyView(); - resetViewState(); - } - private void resetViewState() { + EventBus.getDefault().unregister(this); + if (disposable != null) { + disposable.dispose(); + } adapter = null; listFooter = null; } @@ -193,11 +185,11 @@ public class FeedItemlistFragment extends ListFragment { MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.search_hint)); searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { + menu.findItem(R.id.sort_items).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.findItem(R.id.filter_items).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.findItem(R.id.episode_actions).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.findItem(R.id.refresh_item).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); @@ -323,7 +315,7 @@ public class FeedItemlistFragment extends ListFragment { contextMenu = menu; lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null); + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item); } @Override @@ -340,14 +332,7 @@ public class FeedItemlistFragment extends ListFragment { return super.onContextItemSelected(item); } - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - registerForContextMenu(getListView()); + return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem); } @Override @@ -358,7 +343,7 @@ public class FeedItemlistFragment extends ListFragment { position -= l.getHeaderViewsCount(); MainActivity activity = (MainActivity) getActivity(); long[] ids = FeedItemUtil.getIds(feed.getItems()); - activity.loadChildFragment(ItemFragment.newInstance(ids, position)); + activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position)); activity.getSupportActionBar().setTitle(feed.getTitle()); } @@ -390,26 +375,43 @@ public class FeedItemlistFragment extends ListFragment { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; downloaderList = update.downloaders; - if (isUpdatingFeed != event.update.feedIds.length > 0) { + if (event.hasChangedFeedUpdateStatus(isUpdatingFeed)) { updateProgressBarVisibility(); } - if(adapter != null && update.mediaIds.length > 0) { + if (adapter != null && update.mediaIds.length > 0) { adapter.notifyDataSetChanged(); } } - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (adapter != null) { + adapter.notifyCurrentlyPlayingItemChanged(event, getListView()); + } + } - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - Log.d(TAG, "Received contentUpdate Intent. arg " + arg); - refreshHeaderView(); - loadItems(); - updateProgressBarVisibility(); - } + private void updateUi() { + refreshHeaderView(); + loadItems(); + updateProgressBarVisibility(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerStatusChanged(PlayerStatusEvent event) { + updateUi(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + updateUi(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onFeedListChanged(FeedListUpdateEvent event) { + if (event.contains(feed)) { + updateUi(); } - }; + } private void updateProgressBarVisibility() { if (isUpdatingFeed != updateRefreshMenuItemChecker.isRefreshing()) { @@ -421,8 +423,9 @@ public class FeedItemlistFragment extends ListFragment { } - private void onFragmentLoaded() { - if(!isVisible()) { + private void displayList() { + if (getView() == null) { + Log.e(TAG, "Required root view is not yet created. Stop binding data to UI."); return; } if (adapter == null) { @@ -506,10 +509,8 @@ public class FeedItemlistFragment extends ListFragment { imgvCover.setOnClickListener(v -> showFeedInfo()); butShowSettings.setOnClickListener(v -> { if (feed != null) { - Intent startIntent = new Intent(getActivity(), FeedSettingsActivity.class); - startIntent.putExtra(FeedSettingsActivity.EXTRA_FEED_ID, - feed.getId()); - startActivity(startIntent); + FeedSettingsFragment fragment = FeedSettingsFragment.newInstance(feed); + ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.FLIP); } }); headerCreated = true; @@ -517,10 +518,8 @@ public class FeedItemlistFragment extends ListFragment { private void showFeedInfo() { if (feed != null) { - Intent startIntent = new Intent(getActivity(), FeedInfoActivity.class); - startIntent.putExtra(FeedInfoActivity.EXTRA_FEED_ID, - feed.getId()); - startActivity(startIntent); + FeedInfoFragment fragment = FeedInfoFragment.newInstance(feed); + ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.FLIP); } } @@ -626,7 +625,7 @@ public class FeedItemlistFragment extends ListFragment { .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { feed = result.orElse(null); - onFragmentLoaded(); + displayList(); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } @@ -638,6 +637,11 @@ public class FeedItemlistFragment extends ListFragment { FeedItemFilter filter = feed.getItemFilter(); feed.setItems(filter.filter(feed.getItems())); } + if (feed != null && feed.getSortOrder() != null) { + List<FeedItem> feedItems = feed.getItems(); + FeedItemPermutors.getPermutor(feed.getSortOrder()).reorder(feedItems); + feed.setItems(feedItems); + } return Optional.ofNullable(feed); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index 4fb3d90f5..b745313aa 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -1,49 +1,134 @@ package de.danoeh.antennapod.fragment; -import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; -import android.support.v14.preference.SwitchPreference; -import android.support.v7.preference.ListPreference; -import android.support.v7.preference.PreferenceFragmentCompat; +import androidx.preference.SwitchPreference; +import androidx.preference.ListPreference; +import androidx.preference.PreferenceFragmentCompat; +import android.util.Log; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedFilter; import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.EpisodeFilterDialog; -import de.danoeh.antennapod.viewmodel.FeedSettingsViewModel; +import io.reactivex.Maybe; +import io.reactivex.MaybeOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; -import static de.danoeh.antennapod.activity.FeedSettingsActivity.EXTRA_FEED_ID; +import static de.danoeh.antennapod.core.feed.FeedPreferences.SPEED_USE_GLOBAL; public class FeedSettingsFragment extends PreferenceFragmentCompat { private static final CharSequence PREF_EPISODE_FILTER = "episodeFilter"; + private static final String PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"; + private static final DecimalFormat decimalFormat = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); + private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; + private static final String TAG = "FeedSettingsFragment"; + private Feed feed; + private Disposable disposable; private FeedPreferences feedPreferences; + public static FeedSettingsFragment newInstance(Feed feed) { + FeedSettingsFragment fragment = new FeedSettingsFragment(); + Bundle arguments = new Bundle(); + arguments.putLong(EXTRA_FEED_ID, feed.getId()); + fragment.setArguments(arguments); + return fragment; + } + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.feed_settings); + postponeEnterTransition(); long feedId = getArguments().getLong(EXTRA_FEED_ID); - ViewModelProviders.of(getActivity()).get(FeedSettingsViewModel.class).getFeed(feedId) + disposable = Maybe.create((MaybeOnSubscribe<Feed>) emitter -> { + Feed feed = DBReader.getFeed(feedId); + if (feed != null) { + emitter.onSuccess(feed); + } else { + emitter.onComplete(); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { feed = result; feedPreferences = feed.getPreferences(); + ((MainActivity) getActivity()).getSupportActionBar().setSubtitle(feed.getTitle()); setupAutoDownloadPreference(); setupKeepUpdatedPreference(); setupAutoDeletePreference(); setupAuthentificationPreference(); setupEpisodeFilterPreference(); + setupPlaybackSpeedPreference(); updateAutoDeleteSummary(); updateAutoDownloadEnabled(); - }).dispose(); + updatePlaybackSpeedPreference(); + }, error -> Log.d(TAG, Log.getStackTraceString(error)), + this::startPostponedEnterTransition); + } + + @Override + public void onResume() { + super.onResume(); + ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.feed_settings_label); + if (feed != null) { + ((MainActivity) getActivity()).getSupportActionBar().setSubtitle(feed.getTitle()); + } + } + + @Override + public void onStop() { + super.onStop(); + ((MainActivity) getActivity()).getSupportActionBar().setSubtitle(null); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposable != null) { + disposable.dispose(); + } + } + + private void setupPlaybackSpeedPreference() { + ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); + + String[] speeds = UserPreferences.getPlaybackSpeedArray(); + + String[] values = new String[speeds.length + 1]; + values[0] = decimalFormat.format(SPEED_USE_GLOBAL); + + String[] entries = new String[speeds.length + 1]; + entries[0] = getString(R.string.feed_auto_download_global); + + System.arraycopy(speeds, 0, values, 1, speeds.length); + System.arraycopy(speeds, 0, entries, 1, speeds.length); + + feedPlaybackSpeedPreference.setEntryValues(values); + feedPlaybackSpeedPreference.setEntries(entries); + + feedPlaybackSpeedPreference.setOnPreferenceChangeListener((preference, newValue) -> { + feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) newValue)); + feed.savePreferences(); + updatePlaybackSpeedPreference(); + return false; + }); } private void setupEpisodeFilterPreference() { @@ -95,8 +180,15 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { }); } + private void updatePlaybackSpeedPreference() { + ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); + + float speedValue = feedPreferences.getFeedPlaybackSpeed(); + feedPlaybackSpeedPreference.setValue(decimalFormat.format(speedValue)); + } + private void updateAutoDeleteSummary() { - ListPreference autoDeletePreference = (ListPreference) findPreference("autoDelete"); + ListPreference autoDeletePreference = findPreference("autoDelete"); switch (feedPreferences.getAutoDeleteAction()) { case GLOBAL: @@ -170,7 +262,7 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { } @Override - public void onConfirmButtonPressed(DialogInterface dialog) { + public void onConfirmButtonPressed(DialogInterface dialog) { DBWriter.setFeedsItemsAutoDownload(feed, autoDownload); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java index 9c16cfe56..aa26610aa 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java @@ -2,9 +2,9 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; +import androidx.fragment.app.Fragment; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.widget.SearchView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -100,7 +100,6 @@ public class FyydSearchFragment extends Fragment { inflater.inflate(R.menu.itunes_search, menu); MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.search_fyyd_label)); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override 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 5cf2c5eeb..85978b761 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -2,7 +2,6 @@ package de.danoeh.antennapod.fragment; import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.Context; import android.content.Intent; @@ -12,8 +11,8 @@ import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -96,13 +95,7 @@ public class ItemDescriptionFragment extends Fragment { if (Timeline.isTimecodeLink(url)) { onTimecodeLinkSelected(url); } else { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - e.printStackTrace(); - return true; - } + IntentUtils.openInBrowser(getContext(), url); } return true; } @@ -151,7 +144,6 @@ public class ItemDescriptionFragment extends Fragment { } }; - @SuppressWarnings("deprecation") @SuppressLint("NewApi") @Override public boolean onContextItemSelected(MenuItem item) { @@ -159,11 +151,7 @@ public class ItemDescriptionFragment extends Fragment { if (selectedURL != null) { switch (item.getItemId()) { case R.id.open_in_browser_item: - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - getActivity().startActivity(intent); - } + IntentUtils.openInBrowser(getContext(), selectedURL); break; case R.id.share_url_item: ShareUtils.shareLink(getActivity(), selectedURL); 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 3a48c5431..9a88441e0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -3,122 +3,97 @@ package de.danoeh.antennapod.fragment; import android.content.ClipData; import android.content.Context; import android.content.Intent; +import android.content.res.TypedArray; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.GestureDetectorCompat; import android.text.Layout; import android.text.TextUtils; import android.util.Log; +import android.util.TypedValue; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; +import android.widget.Button; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; - +import androidx.annotation.AttrRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconButton; - -import org.apache.commons.lang3.ArrayUtils; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.List; - import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.CastEnabledActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.Downloader; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; -import de.danoeh.antennapod.core.util.Flavors; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.playback.Timeline; -import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; -import de.danoeh.antennapod.view.OnSwipeGesture; -import de.danoeh.antennapod.view.SwipeGestureDetector; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.ArrayUtils; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.List; /** * Displays information about a FeedItem and actions. */ -public class ItemFragment extends Fragment implements OnSwipeGesture { +public class ItemFragment extends Fragment { private static final String TAG = "ItemFragment"; - - private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE; - - private static final String ARG_FEEDITEMS = "feeditems"; - private static final String ARG_FEEDITEM_POS = "feeditem_pos"; - - private GestureDetectorCompat headerGestureDetector; - private GestureDetectorCompat webviewGestureDetector; + private static final String ARG_FEEDITEM = "feeditem"; /** * Creates a new instance of an ItemFragment * - * @param feeditem The ID of the FeedItem that should be displayed. + * @param feeditem The ID of the FeedItem to show * @return The ItemFragment instance */ public static ItemFragment newInstance(long feeditem) { - return newInstance(new long[] { feeditem }, 0); - } - - /** - * Creates a new instance of an ItemFragment - * - * @param feeditems The IDs of the FeedItems that belong to the same list - * @param feedItemPos The position of the FeedItem that is currently shown - * @return The ItemFragment instance - */ - public static ItemFragment newInstance(long[] feeditems, int feedItemPos) { ItemFragment fragment = new ItemFragment(); Bundle args = new Bundle(); - args.putLongArray(ARG_FEEDITEMS, feeditems); - args.putInt(ARG_FEEDITEM_POS, feedItemPos); + args.putLong(ARG_FEEDITEM, feeditem); fragment.setArguments(args); return fragment; } private boolean itemsLoaded = false; - private long[] feedItems; - private int feedItemPos; + private long itemId; private FeedItem item; private String webviewData; private List<Downloader> downloaderList; @@ -132,9 +107,8 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { private ImageView imgvCover; private ProgressBar progbarDownload; private ProgressBar progbarLoading; - private IconButton butAction1; - private IconButton butAction2; - private Menu popupMenu; + private Button butAction1; + private Button butAction2; private Disposable disposable; @@ -146,20 +120,8 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setRetainInstance(true); - setHasOptionsMenu(true); - feedItems = getArguments().getLongArray(ARG_FEEDITEMS); - feedItemPos = getArguments().getInt(ARG_FEEDITEM_POS); - - headerGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this)); - webviewGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this) { - // necessary for the longclick context menu to work properly - @Override - public boolean onDown(MotionEvent e) { - return false; - } - }); + itemId = getArguments().getLong(ARG_FEEDITEM); } @Override @@ -169,11 +131,6 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { root = layout.findViewById(R.id.content_root); - LinearLayout header = root.findViewById(R.id.header); - if(feedItems.length > 0) { - header.setOnTouchListener((v, event) -> headerGestureDetector.onTouchEvent(event)); - } - txtvPodcast = layout.findViewById(R.id.txtvPodcast); txtvPodcast.setOnClickListener(v -> openPodcast()); txtvTitle = layout.findViewById(R.id.txtvTitle); @@ -204,17 +161,12 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { webvDescription.getSettings().setLayoutAlgorithm( WebSettings.LayoutAlgorithm.NARROW_COLUMNS); webvDescription.getSettings().setLoadWithOverviewMode(true); - if(feedItems.length > 0) { webvDescription.setOnLongClickListener(webViewLongClickListener); - } - webvDescription.setOnTouchListener((v, event) -> webviewGestureDetector.onTouchEvent(event)); + webvDescription.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - if(IntentUtils.isCallable(getActivity(), intent)) { - startActivity(intent); - } + IntentUtils.openInBrowser(getContext(), url); return true; } }); @@ -264,6 +216,12 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { } @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + load(); + } + + @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } @@ -271,9 +229,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); - load(); } @Override @@ -288,7 +244,6 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Override public void onStop() { super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); EventBus.getDefault().unregister(this); } @@ -304,68 +259,6 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { } } - @Override - public boolean onSwipeLeftToRight() { - return swipeFeedItem(-1); - } - - @Override - public boolean onSwipeRightToLeft() { - return swipeFeedItem(+1); - } - - private boolean swipeFeedItem(int position) { - Log.d(TAG, String.format("onSwipe() shift: %s", position)); - feedItemPos = (feedItemPos + position) % feedItems.length; - if (feedItemPos < 0) { - feedItemPos = feedItems.length - 1; - } - load(); - return true; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(!isAdded() || item == null) { - return; - } - super.onCreateOptionsMenu(menu, inflater); - if (Flavors.FLAVOR == Flavors.PLAY) { - ((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); - } - inflater.inflate(R.menu.feeditem_options, menu); - popupMenu = menu; - if (item.hasMedia()) { - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null); - } else { - // these are already available via button1 and button2 - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null, - R.id.mark_read_item, R.id.visit_website_item); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - switch(menuItem.getItemId()) { - case R.id.open_podcast: - openPodcast(); - return true; - default: - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); - } - } - - private final FeedItemMenuHandler.MenuInterface popupMenuInterface = new FeedItemMenuHandler.MenuInterface() { - @Override - public void setItemVisibility(int id, boolean visible) { - MenuItem item = popupMenu.findItem(id); - if (item != null) { - item.setVisible(visible); - } - } - }; - - private void onFragmentLoaded() { if (webviewData != null) { webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData, "text/html", "utf-8", "about:blank"); @@ -378,7 +271,6 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { Log.d(TAG, "updateAppearance item is null"); return; } - getActivity().supportInvalidateOptionsMenu(); txtvPodcast.setText(item.getFeed().getTitle()); txtvTitle.setText(item.getTitle()); @@ -388,7 +280,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { } Glide.with(getActivity()) - .load(item.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(item)) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) @@ -409,53 +301,59 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { } FeedMedia media = item.getMedia(); - String butAction1Icon = null; - int butAction1Text = 0; - String butAction2Icon = null; - int butAction2Text = 0; + @AttrRes int butAction1Icon = 0; + @StringRes int butAction1Text = 0; + @AttrRes int butAction2Icon = 0; + @StringRes int butAction2Text = 0; if (media == null) { if (!item.isPlayed()) { - butAction1Icon = "{fa-check 24sp}"; + butAction1Icon = R.attr.navigation_accept; butAction1Text = R.string.mark_read_label; } if (item.getLink() != null) { - butAction2Icon = "{md-web 24sp}"; + butAction2Icon = R.attr.location_web_site; butAction2Text = R.string.visit_website_label; } } else { - if(media.getDuration() > 0) { + if (media.getDuration() > 0) { txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); } boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); if (!media.isDownloaded()) { - butAction2Icon = "{md-settings-input-antenna 24sp}"; + butAction2Icon = R.attr.action_stream; butAction2Text = R.string.stream_label; } else { - butAction2Icon = "{md-delete 24sp}"; + butAction2Icon = R.attr.content_discard; butAction2Text = R.string.delete_label; } if (isDownloading) { - butAction1Icon = "{md-cancel 24sp}"; + butAction1Icon = R.attr.navigation_cancel; butAction1Text = R.string.cancel_label; } else if (media.isDownloaded()) { - butAction1Icon = "{md-play-arrow 24sp}"; + butAction1Icon = R.attr.av_play; butAction1Text = R.string.play_label; } else { - butAction1Icon = "{md-file-download 24sp}"; + butAction1Icon = R.attr.av_download; butAction1Text = R.string.download_label; } } - if(butAction1Icon != null && butAction1Text != 0) { - butAction1.setText(butAction1Icon +"\u0020\u0020" + getActivity().getString(butAction1Text)); - Iconify.addIcons(butAction1); + if (butAction1Icon != 0 && butAction1Text != 0) { + butAction1.setText(butAction1Text); + butAction1.setTransformationMethod(null); + TypedValue typedValue = new TypedValue(); + getContext().getTheme().resolveAttribute(butAction1Icon, typedValue, true); + butAction1.setCompoundDrawablesWithIntrinsicBounds(typedValue.resourceId, 0, 0, 0); butAction1.setVisibility(View.VISIBLE); } else { butAction1.setVisibility(View.INVISIBLE); } - if(butAction2Icon != null && butAction2Text != 0) { - butAction2.setText(butAction2Icon +"\u0020\u0020" + getActivity().getString(butAction2Text)); - Iconify.addIcons(butAction2); + if (butAction2Icon != 0 && butAction2Text != 0) { + butAction2.setText(butAction2Text); + butAction2.setTransformationMethod(null); + TypedValue typedValue = new TypedValue(); + getContext().getTheme().resolveAttribute(butAction2Icon, typedValue, true); + butAction2.setCompoundDrawablesWithIntrinsicBounds(typedValue.resourceId, 0, 0, 0); butAction2.setVisibility(View.VISIBLE); } else { butAction2.setVisibility(View.INVISIBLE); @@ -485,11 +383,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { if (selectedURL != null) { switch (item.getItemId()) { case R.id.open_in_browser_item: - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - getActivity().startActivity(intent); - } + IntentUtils.openInBrowser(getContext(), selectedURL); break; case R.id.share_url_item: ShareUtils.shareLink(getActivity(), selectedURL); @@ -541,8 +435,8 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - for(FeedItem item : event.items) { - if(feedItems[feedItemPos] == item.getId()) { + for (FeedItem item : event.items) { + if (this.item.getId() == item.getId()) { load(); return; } @@ -565,18 +459,13 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { } } - - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EVENTS) != 0) { - load(); - } - } - }; + @Subscribe + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + load(); + } private void load() { - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } progbarLoading.setVisibility(View.VISIBLE); @@ -593,7 +482,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Nullable private FeedItem loadInBackground() { - FeedItem feedItem = DBReader.getFeedItem(feedItems[feedItemPos]); + FeedItem feedItem = DBReader.getFeedItem(itemId); Context context = getContext(); if (feedItem != null && context != null) { Timeline t = new Timeline(context, feedItem); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java new file mode 100644 index 000000000..74530e424 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java @@ -0,0 +1,193 @@ +package de.danoeh.antennapod.fragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.ViewPager; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.CastEnabledActivity; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.util.Flavors; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +/** + * Displays information about a list of FeedItems. + */ +public class ItemPagerFragment extends Fragment { + private static final String ARG_FEEDITEMS = "feeditems"; + private static final String ARG_FEEDITEM_POS = "feeditem_pos"; + + /** + * Creates a new instance of an ItemPagerFragment. + * + * @param feeditem The ID of the FeedItem that should be displayed. + * @return The ItemFragment instance + */ + public static ItemPagerFragment newInstance(long feeditem) { + return newInstance(new long[] { feeditem }, 0); + } + + /** + * Creates a new instance of an ItemPagerFragment. + * + * @param feeditems The IDs of the FeedItems that belong to the same list + * @param feedItemPos The position of the FeedItem that is currently shown + * @return The ItemFragment instance + */ + public static ItemPagerFragment newInstance(long[] feeditems, int feedItemPos) { + ItemPagerFragment fragment = new ItemPagerFragment(); + Bundle args = new Bundle(); + args.putLongArray(ARG_FEEDITEMS, feeditems); + args.putInt(ARG_FEEDITEM_POS, feedItemPos); + fragment.setArguments(args); + return fragment; + } + + private long[] feedItems; + private int feedItemPos; + private FeedItem item; + private Disposable disposable; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + feedItems = getArguments().getLongArray(ARG_FEEDITEMS); + feedItemPos = getArguments().getInt(ARG_FEEDITEM_POS); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + View layout = inflater.inflate(R.layout.feeditem_pager_fragment, container, false); + + ViewPager pager = layout.findViewById(R.id.pager); + // FragmentStatePagerAdapter documentation: + // > When using FragmentStatePagerAdapter the host ViewPager must have a valid ID set. + // When opening multiple ItemPagerFragments by clicking "item" -> "visit podcast" -> "item" -> etc, + // the ID is no longer unique and FragmentStatePagerAdapter does not display any pages. + int newId = ViewCompat.generateViewId(); + pager.setId(newId); + pager.setAdapter(new ItemPagerAdapter()); + pager.setCurrentItem(feedItemPos); + loadItem(feedItems[feedItemPos]); + pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + loadItem(feedItems[position]); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + return layout; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (disposable != null) { + disposable.dispose(); + } + } + + private void loadItem(long itemId) { + if (disposable != null) { + disposable.dispose(); + } + + disposable = Observable.fromCallable(() -> DBReader.getFeedItem(itemId)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + item = result; + getActivity().invalidateOptionsMenu(); + }, Throwable::printStackTrace); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (!isAdded() || item == null) { + return; + } + super.onCreateOptionsMenu(menu, inflater); + if (Flavors.FLAVOR == Flavors.PLAY) { + ((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); + } + inflater.inflate(R.menu.feeditem_options, menu); + + FeedItemMenuHandler.MenuInterface popupMenuInterface = (id, visible) -> { + MenuItem item = menu.findItem(id); + if (item != null) { + item.setVisible(visible); + } + }; + + if (item.hasMedia()) { + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item); + } else { + // these are already available via button1 and button2 + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, + R.id.mark_read_item, R.id.visit_website_item); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case R.id.open_podcast: + openPodcast(); + return true; + default: + return FeedItemMenuHandler.onMenuItemClicked(this, menuItem.getItemId(), item); + } + } + + private void openPodcast() { + Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); + ((MainActivity) getActivity()).loadChildFragment(fragment); + } + + private class ItemPagerAdapter extends FragmentStatePagerAdapter { + + ItemPagerAdapter() { + super(getFragmentManager(), BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + } + + @NonNull + @Override + public Fragment getItem(int position) { + return ItemFragment.newInstance(feedItems[position]); + } + + @Override + public int getCount() { + return feedItems.length; + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java index 80767bef2..4b1544e47 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java @@ -2,10 +2,11 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; -import android.support.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.widget.SearchView; +import androidx.annotation.NonNull; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -18,34 +19,18 @@ import android.widget.GridView; import android.widget.ProgressBar; import android.widget.TextView; -import com.afollestad.materialdialogs.MaterialDialog; - import de.danoeh.antennapod.discovery.ItunesPodcastSearcher; import de.danoeh.antennapod.discovery.ItunesTopListLoader; import de.danoeh.antennapod.discovery.PodcastSearchResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.itunes.ItunesAdapter; -import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.menuhandler.MenuItemUtils; -import io.reactivex.Single; -import io.reactivex.SingleOnSubscribe; -import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; //Searches iTunes store for given string and displays results in a list public class ItunesSearchFragment extends Fragment { @@ -134,9 +119,9 @@ public class ItunesSearchFragment extends Fragment { progressBar.setVisibility(View.GONE); gridView.setVisibility(View.VISIBLE); String prefix = getString(R.string.error_msg_prefix); - new MaterialDialog.Builder(getActivity()) - .content(prefix + " " + error.getMessage()) - .neutralText(android.R.string.ok) + new AlertDialog.Builder(getActivity()) + .setMessage(prefix + " " + error.getMessage()) + .setPositiveButton(android.R.string.ok, null) .show(); }); }); @@ -165,9 +150,8 @@ public class ItunesSearchFragment extends Fragment { inflater.inflate(R.menu.itunes_search, menu); MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.search_itunes_label)); - sv.setOnQueryTextListener(new android.support.v7.widget.SearchView.OnQueryTextListener() { + sv.setOnQueryTextListener(new androidx.appcompat.widget.SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { sv.clearFocus(); 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 adae4f2a5..2bfdd040b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -1,12 +1,11 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.ItemTouchHelper; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -16,6 +15,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; /** * Like 'EpisodesFragment' except that it only shows new episodes and @@ -54,7 +54,8 @@ public class NewEpisodesFragment extends EpisodesListFragment { emptyView.setTitle(R.string.no_new_episodes_head_label); emptyView.setMessage(R.string.no_new_episodes_label); - ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { + ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, + ItemTouchHelper.RIGHT) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; @@ -63,7 +64,7 @@ public class NewEpisodesFragment extends EpisodesListFragment { @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder; - removeNewFlagWithUndo(holder.getFeedItem()); + FeedItemMenuHandler.removeNewFlagWithUndo(NewEpisodesFragment.this, holder.getFeedItem()); } @Override 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 e2060481f..a97e3dae8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -2,9 +2,9 @@ package de.danoeh.antennapod.fragment; import android.content.res.TypedArray; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.ListFragment; -import android.support.v4.view.MenuItemCompat; +import androidx.annotation.NonNull; +import androidx.fragment.app.ListFragment; +import androidx.core.view.MenuItemCompat; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -12,6 +12,8 @@ import android.view.MenuItem; import android.view.View; import android.widget.ListView; +import de.danoeh.antennapod.core.event.PlaybackHistoryEvent; +import de.danoeh.antennapod.core.event.PlayerStatusEvent; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -24,7 +26,6 @@ import de.danoeh.antennapod.adapter.FeedItemlistAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.service.download.Downloader; @@ -39,12 +40,8 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; public class PlaybackHistoryFragment extends ListFragment { - public static final String TAG = "PlaybackHistoryFragment"; - private static final int EVENTS = EventDistributor.PLAYBACK_HISTORY_UPDATE | - EventDistributor.PLAYER_STATUS_UPDATE; - private List<FeedItem> playbackHistory; private FeedItemlistAdapter adapter; private List<Downloader> downloaderList; @@ -83,7 +80,6 @@ public class PlaybackHistoryFragment extends ListFragment { @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); loadItems(); } @@ -92,7 +88,6 @@ public class PlaybackHistoryFragment extends ListFragment { public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); - EventDistributor.getInstance().unregister(contentUpdate); if (disposable != null) { disposable.dispose(); } @@ -111,7 +106,7 @@ public class PlaybackHistoryFragment extends ListFragment { super.onListItemClick(l, v, position, id); position -= l.getHeaderViewsCount(); long[] ids = FeedItemUtil.getIds(playbackHistory); - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(ids, position)); + ((MainActivity) getActivity()).loadChildFragment(ItemPagerFragment.newInstance(ids, position)); } @Override @@ -166,16 +161,17 @@ public class PlaybackHistoryFragment extends ListFragment { } } - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Subscribe(threadMode = ThreadMode.MAIN) + public void onHistoryUpdated(PlaybackHistoryEvent event) { + loadItems(); + getActivity().supportInvalidateOptionsMenu(); + } - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EVENTS) != 0) { - loadItems(); - getActivity().supportInvalidateOptionsMenu(); - } - } - }; + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerStatusChanged(PlayerStatusEvent event) { + loadItems(); + getActivity().supportInvalidateOptionsMenu(); + } private void onFragmentLoaded() { adapter.notifyDataSetChanged(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index 9763f8f2d..b36ce6145 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -4,14 +4,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.SimpleItemAnimator; -import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -19,11 +11,26 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.CheckBox; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.SearchView; +import androidx.core.view.MenuItemCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; + +import com.google.android.material.snackbar.Snackbar; import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + import java.util.List; import de.danoeh.antennapod.R; @@ -33,35 +40,32 @@ import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; -import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.LongList; -import de.danoeh.antennapod.core.util.QueueSorter; import de.danoeh.antennapod.core.util.SortOrder; +import de.danoeh.antennapod.core.util.download.AutoUpdateManager; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; - import de.danoeh.antennapod.view.EmptyViewHandler; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE; @@ -70,13 +74,8 @@ import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REM * Shows all items in the queue */ public class QueueFragment extends Fragment { - public static final String TAG = "QueueFragment"; - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | - EventDistributor.UNREAD_ITEMS_UPDATE | // sent when playback position is reset - EventDistributor.PLAYER_STATUS_UPDATE; - private TextView infoBar; private RecyclerView recyclerView; private QueueRecyclerAdapter recyclerAdapter; @@ -91,10 +90,12 @@ public class QueueFragment extends Fragment { private static final String PREFS = "QueueFragment"; private static final String PREF_SCROLL_POSITION = "scroll_position"; private static final String PREF_SCROLL_OFFSET = "scroll_offset"; + private static final String PREF_SHOW_LOCK_WARNING = "show_lock_warning"; private Disposable disposable; private LinearLayoutManager layoutManager; private ItemTouchHelper itemTouchHelper; + private SharedPreferences prefs; @Override @@ -102,6 +103,7 @@ public class QueueFragment extends Fragment { super.onCreate(savedInstanceState); setRetainInstance(true); setHasOptionsMenu(true); + prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); } @Override @@ -111,7 +113,6 @@ public class QueueFragment extends Fragment { onFragmentLoaded(true); } loadItems(true); - EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); } @@ -124,9 +125,8 @@ public class QueueFragment extends Fragment { @Override public void onStop() { super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); EventBus.getDefault().unregister(this); - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } } @@ -196,8 +196,8 @@ public class QueueFragment extends Fragment { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; downloaderList = update.downloaders; - if (isUpdatingFeeds != update.feedIds.length > 0) { - getActivity().supportInvalidateOptionsMenu(); + if (event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) { + getActivity().invalidateOptionsMenu(); } if (recyclerAdapter != null && update.mediaIds.length > 0) { for (long mediaId : update.mediaIds) { @@ -209,6 +209,37 @@ public class QueueFragment extends Fragment { } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (recyclerAdapter != null) { + for (int i = 0; i < recyclerAdapter.getItemCount(); i++) { + QueueRecyclerAdapter.ViewHolder holder = (QueueRecyclerAdapter.ViewHolder) + recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; + } + } + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerStatusChanged(PlayerStatusEvent event) { + loadItems(false); + if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { + getActivity().supportInvalidateOptionsMenu(); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + // Sent when playback position is reset + loadItems(false); + if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { + getActivity().supportInvalidateOptionsMenu(); + } + } + private void saveScrollPosition() { int firstItem = layoutManager.findFirstVisibleItemPosition(); View firstItemView = layoutManager.findViewByPosition(firstItem); @@ -219,15 +250,13 @@ public class QueueFragment extends Fragment { topOffset = firstItemView.getTop(); } - SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(PREF_SCROLL_POSITION, firstItem); - editor.putFloat(PREF_SCROLL_OFFSET, topOffset); - editor.commit(); + prefs.edit() + .putInt(PREF_SCROLL_POSITION, firstItem) + .putFloat(PREF_SCROLL_OFFSET, topOffset) + .apply(); } private void restoreScrollPosition() { - SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); int position = prefs.getInt(PREF_SCROLL_POSITION, 0); float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f); if (position > 0 || offset > 0) { @@ -259,7 +288,6 @@ public class QueueFragment extends Fragment { MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.search_hint)); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override @@ -299,25 +327,10 @@ public class QueueFragment extends Fragment { if (!super.onOptionsItemSelected(item)) { switch (item.getItemId()) { case R.id.queue_lock: - boolean newLockState = !UserPreferences.isQueueLocked(); - UserPreferences.setQueueLocked(newLockState); - getActivity().supportInvalidateOptionsMenu(); - if (recyclerAdapter != null) { - recyclerAdapter.setLocked(newLockState); - } - if (newLockState) { - Snackbar.make(getActivity().findViewById(R.id.content), R.string - .queue_locked, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(getActivity().findViewById(R.id.content), R.string - .queue_unlocked, Snackbar.LENGTH_SHORT).show(); - } + toggleQueueLock(); return true; case R.id.refresh_item: - List<Feed> feeds = ((MainActivity) getActivity()).getFeeds(); - if (feeds != null) { - DBTasks.refreshAllFeeds(getActivity(), feeds); - } + AutoUpdateManager.runImmediate(requireContext()); return true; case R.id.clear_queue: // make sure the user really wants to clear the queue @@ -335,7 +348,7 @@ public class QueueFragment extends Fragment { conDialog.createNewDialog().show(); return true; case R.id.episode_actions: - ((MainActivity) requireActivity()) .loadChildFragment( + ((MainActivity) requireActivity()).loadChildFragment( EpisodesApplyActionFragment.newInstance(queue, ACTION_DELETE | ACTION_REMOVE_FROM_QUEUE)); return true; case R.id.queue_sort_episode_title_asc: @@ -377,7 +390,7 @@ public class QueueFragment extends Fragment { UserPreferences.setQueueKeepSorted(keepSortedNew); if (keepSortedNew) { SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder(); - QueueSorter.sort(sortOrder, true); + DBWriter.reorderQueue(sortOrder, true); if (recyclerAdapter != null) { recyclerAdapter.setLocked(true); } @@ -394,6 +407,48 @@ public class QueueFragment extends Fragment { } } + private void toggleQueueLock() { + boolean isLocked = UserPreferences.isQueueLocked(); + if (isLocked) { + setQueueLocked(false); + } else { + boolean shouldShowLockWarning = prefs.getBoolean(PREF_SHOW_LOCK_WARNING, true); + if (!shouldShowLockWarning) { + setQueueLocked(true); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(R.string.lock_queue); + builder.setMessage(R.string.queue_lock_warning); + + View view = View.inflate(getContext(), R.layout.checkbox_do_not_show_again, null); + CheckBox checkDoNotShowAgain = view.findViewById(R.id.checkbox_do_not_show_again); + builder.setView(view); + + builder.setPositiveButton(R.string.lock_queue, (dialog, which) -> { + prefs.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked()).apply(); + setQueueLocked(true); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.show(); + } + } + } + + private void setQueueLocked(boolean locked) { + UserPreferences.setQueueLocked(locked); + getActivity().supportInvalidateOptionsMenu(); + if (recyclerAdapter != null) { + recyclerAdapter.setLocked(locked); + } + if (locked) { + Snackbar.make(getActivity().findViewById(R.id.content), R.string + .queue_locked, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(getActivity().findViewById(R.id.content), R.string + .queue_unlocked, Snackbar.LENGTH_SHORT).show(); + } + } + /** * This method is called if the user clicks on a sort order menu item. * @@ -401,7 +456,7 @@ public class QueueFragment extends Fragment { */ private void setSortOrder(SortOrder sortOrder) { UserPreferences.setQueueKeepSortedOrder(sortOrder); - QueueSorter.sort(sortOrder, true); + DBWriter.reorderQueue(sortOrder, true); } @Override @@ -430,7 +485,7 @@ public class QueueFragment extends Fragment { DBWriter.moveQueueItemToBottom(selectedItem.getId(), true); return true; default: - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); + return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem); } } @@ -454,7 +509,8 @@ public class QueueFragment extends Fragment { registerForContextMenu(recyclerView); itemTouchHelper = new ItemTouchHelper( - new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT) { + new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, + ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { // Position tracking whilst dragging int dragFrom = -1; @@ -596,17 +652,19 @@ public class QueueFragment extends Fragment { private void refreshInfoBar() { String info = queue.size() + getString(R.string.episodes_suffix); - if(queue.size() > 0) { + if (queue.size() > 0) { long timeLeft = 0; - float playbackSpeed = UserPreferences.getPlaybackSpeed(); - for(FeedItem item : queue) { - if(item.getMedia() != null) { - timeLeft += - (long) ((item.getMedia().getDuration() - item.getMedia().getPosition()) - / playbackSpeed); + for (FeedItem item : queue) { + float playbackSpeed = 1; + if (UserPreferences.timeRespectsSpeed()) { + playbackSpeed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(item.getMedia()); + } + if (item.getMedia() != null) { + long itemTimeLeft = item.getMedia().getDuration() - item.getMedia().getPosition(); + timeLeft += itemTimeLeft / playbackSpeed; } } - info += " \u2022 "; + info += " • "; info += getString(R.string.time_left_label); info += Converter.getDurationStringLocalized(getActivity(), timeLeft); } @@ -673,19 +731,6 @@ public class QueueFragment extends Fragment { } }; - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EVENTS) != 0) { - Log.d(TAG, "arg: " + arg); - loadItems(false); - if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { - getActivity().supportInvalidateOptionsMenu(); - } - } - } - }; - private void loadItems(final boolean restoreScrollPosition) { Log.d(TAG, "loadItems()"); if(disposable != null) { 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 e4213cc6b..7e217cde4 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java @@ -2,7 +2,8 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -11,7 +12,6 @@ import android.widget.AdapterView; import android.widget.GridView; import android.widget.ProgressBar; import android.widget.TextView; -import com.afollestad.materialdialogs.MaterialDialog; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; @@ -110,9 +110,9 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. Log.e(TAG, Log.getStackTraceString(error)); view.setAlpha(1f); String prefix = getString(R.string.error_msg_prefix); - new MaterialDialog.Builder(getActivity()) - .content(prefix + " " + error.getMessage()) - .neutralText(android.R.string.ok) + new AlertDialog.Builder(getActivity()) + .setMessage(prefix + " " + error.getMessage()) + .setPositiveButton(android.R.string.ok, null) .show(); }); } 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 2a7f7d12b..7e8823c27 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.support.v4.app.ListFragment; +import androidx.fragment.app.ListFragment; import android.util.Log; import android.view.View; import android.widget.ListView; @@ -25,6 +25,7 @@ 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.view.EmptyViewHandler; +import org.greenrobot.eventbus.ThreadMode; /** * Displays all running downloads and provides actions to cancel them @@ -75,7 +76,7 @@ public class RunningDownloadsFragment extends ListFragment { setListAdapter(null); } - @Subscribe(sticky = true) + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEvent(DownloadEvent event) { Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 0892bce0a..d124d6aa2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -2,11 +2,11 @@ package de.danoeh.antennapod.fragment; import android.content.Context; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.ListFragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.SearchView; +import androidx.annotation.NonNull; +import androidx.fragment.app.ListFragment; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SearchView; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -20,7 +20,7 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.SearchlistAdapter; -import de.danoeh.antennapod.core.feed.EventDistributor; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; @@ -30,6 +30,8 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; /** * Performs a search operation on all feeds or one specific feed and displays the search result. @@ -76,7 +78,6 @@ public class SearchFragment extends ListFragment { @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); search(); } @@ -86,7 +87,6 @@ public class SearchFragment extends ListFragment { if(disposable != null) { disposable.dispose(); } - EventDistributor.getInstance().unregister(contentUpdate); } @Override @@ -103,6 +103,13 @@ public class SearchFragment extends ListFragment { searchAdapter = new SearchlistAdapter(getActivity(), itemAccess); setListAdapter(searchAdapter); + EventBus.getDefault().register(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + EventBus.getDefault().unregister(this); } @Override @@ -145,15 +152,10 @@ public class SearchFragment extends ListFragment { MenuItemCompat.setActionView(item, sv); } - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & (EventDistributor.UNREAD_ITEMS_UPDATE - | EventDistributor.DOWNLOAD_HANDLED)) != 0) { - search(); - } - } - }; + @Subscribe + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + search(); + } private void onSearchResults(List<SearchResult> results) { searchResults = results; 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 15c6052a9..aa6d80bb0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -5,8 +5,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; -import android.support.annotation.StringRes; -import android.support.v4.app.Fragment; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -18,6 +18,8 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.GridView; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import java.util.concurrent.Callable; import de.danoeh.antennapod.R; @@ -25,19 +27,29 @@ 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.feed.EventDistributor; +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.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.RenameFeedDialog; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.view.EmptyViewHandler; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; /** * Fragment for displaying feed subscriptions @@ -45,17 +57,17 @@ import io.reactivex.schedulers.Schedulers; public class SubscriptionFragment extends Fragment { public static final String TAG = "SubscriptionFragment"; - - private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE - | EventDistributor.UNREAD_ITEMS_UPDATE; private static final String PREFS = "SubscriptionFragment"; private static final String PREF_NUM_COLUMNS = "columns"; private GridView subscriptionGridLayout; private DBReader.NavDrawerData navDrawerData; private SubscriptionsAdapter subscriptionAdapter; + private FloatingActionButton subscriptionAddButton; + private EmptyViewHandler emptyView; private int mPosition = -1; + private boolean isUpdatingFeeds = false; private Disposable disposable; private SharedPreferences prefs; @@ -76,6 +88,8 @@ public class SubscriptionFragment extends Fragment { subscriptionGridLayout = root.findViewById(R.id.subscriptions_grid); subscriptionGridLayout.setNumColumns(prefs.getInt(PREF_NUM_COLUMNS, 3)); registerForContextMenu(subscriptionGridLayout); + subscriptionAddButton = root.findViewById(R.id.subscriptions_add); + setupEmptyView(); return root; } @@ -89,6 +103,8 @@ public class SubscriptionFragment extends Fragment { menu.findItem(R.id.subscription_num_columns_3).setChecked(columns == 3); menu.findItem(R.id.subscription_num_columns_4).setChecked(columns == 4); menu.findItem(R.id.subscription_num_columns_5).setChecked(columns == 5); + + isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker); } @Override @@ -97,6 +113,9 @@ public class SubscriptionFragment extends Fragment { return true; } switch (item.getItemId()) { + case R.id.refresh_item: + AutoUpdateManager.runImmediate(requireContext()); + return true; case R.id.subscription_num_columns_2: setColumnNumber(2); return true; @@ -120,13 +139,28 @@ public class SubscriptionFragment extends Fragment { getActivity().invalidateOptionsMenu(); } + private void setupEmptyView() { + emptyView = new EmptyViewHandler(getContext()); + emptyView.setIcon(R.attr.ic_folder); + emptyView.setTitle(R.string.no_subscriptions_head_label); + emptyView.setMessage(R.string.no_subscriptions_label); + emptyView.attachToListView(subscriptionGridLayout); + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - subscriptionAdapter = new SubscriptionsAdapter((MainActivity)getActivity(), itemAccess); + + subscriptionAdapter = new SubscriptionsAdapter((MainActivity) getActivity(), itemAccess); subscriptionGridLayout.setAdapter(subscriptionAdapter); subscriptionGridLayout.setOnItemClickListener(subscriptionAdapter); + subscriptionAddButton.setOnClickListener(view -> { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).loadChildFragment(new AddFeedFragment()); + } + }); + if (getActivity() instanceof MainActivity) { ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.subscriptions_label); } @@ -135,14 +169,14 @@ public class SubscriptionFragment extends Fragment { @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); + EventBus.getDefault().register(this); loadSubscriptions(); } @Override public void onStop() { super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); + EventBus.getDefault().unregister(this); if(disposable != null) { disposable.dispose(); } @@ -152,12 +186,14 @@ public class SubscriptionFragment extends Fragment { if(disposable != null) { disposable.dispose(); } + emptyView.hide(); disposable = Observable.fromCallable(DBReader::getNavDrawerData) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { navDrawerData = result; subscriptionAdapter.notifyDataSetChanged(); + emptyView.updateVisibility(); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } @@ -268,15 +304,26 @@ public class SubscriptionFragment extends Fragment { dialog.createNewDialog().show(); } - private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - Log.d(TAG, "Received contentUpdate Intent."); - loadSubscriptions(); - } + @Subscribe + public void onFeedListChanged(FeedListUpdateEvent event) { + loadSubscriptions(); + } + + @Subscribe + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + loadSubscriptions(); + } + + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if (event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) { + getActivity().invalidateOptionsMenu(); } - }; + } + + private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = + () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); private final SubscriptionsAdapter.ItemAccess itemAccess = new SubscriptionsAdapter.ItemAccess() { @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/TransitionEffect.java b/app/src/main/java/de/danoeh/antennapod/fragment/TransitionEffect.java new file mode 100644 index 000000000..461fa9da3 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/TransitionEffect.java @@ -0,0 +1,5 @@ +package de.danoeh.antennapod.fragment; + +public enum TransitionEffect { + NONE, FLIP, FADE +} 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 4dc114f9b..380f6741a 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 @@ -4,11 +4,11 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; +import com.google.android.material.tabs.TabLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; 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 49851ebb4..4baa74df6 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,9 +4,9 @@ import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; +import androidx.fragment.app.Fragment; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.widget.SearchView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -54,9 +54,8 @@ public abstract class PodcastListFragment extends Fragment { inflater.inflate(R.menu.gpodder_podcasts, menu); MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.gpodnet_search_hint)); - sv.setOnQueryTextListener(new android.support.v7.widget.SearchView.OnQueryTextListener() { + sv.setOnQueryTextListener(new androidx.appcompat.widget.SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { sv.clearFocus(); 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 index 10bd636dd..ffe69aa9a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.fragment.gpodnet; import android.os.Bundle; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.widget.SearchView; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -50,7 +50,6 @@ public class SearchListFragment extends PodcastListFragment { // parent already inflated menu MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.gpodnet_search_hint)); sv.setQuery(query, false); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java index d39829260..92cd4ca84 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.fragment.gpodnet; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.apache.commons.lang3.Validate; 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 1e46b1ac5..cde8fb3df 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 @@ -4,9 +4,9 @@ import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v4.app.ListFragment; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; +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; @@ -40,7 +40,6 @@ public class TagListFragment extends ListFragment { inflater.inflate(R.menu.gpodder_podcasts, menu); MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); sv.setQueryHint(getString(R.string.gpodnet_search_hint)); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutDevelopersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutDevelopersFragment.java new file mode 100644 index 000000000..62a5eb306 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutDevelopersFragment.java @@ -0,0 +1,65 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.os.Bundle; +import android.view.View; +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; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; + +public class AboutDevelopersFragment extends ListFragment { + private Disposable developersLoader; + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + getListView().setDivider(null); + getListView().setSelector(android.R.color.transparent); + + developersLoader = Single.create((SingleOnSubscribe<ArrayList<SimpleIconListAdapter.ListItem>>) emitter -> { + ArrayList<SimpleIconListAdapter.ListItem> developers = new ArrayList<>(); + BufferedReader reader = new BufferedReader(new InputStreamReader( + getContext().getAssets().open("developers.csv"))); + String line; + while ((line = reader.readLine()) != null) { + String[] info = line.split(";"); + developers.add(new SimpleIconListAdapter.ListItem(info[0], info[2], + "https://avatars2.githubusercontent.com/u/" + info[1] + "?s=60&v=4")); + } + emitter.onSuccess(developers); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + developers -> setListAdapter(new SimpleIconListAdapter<>(getContext(), developers)), + error -> Toast.makeText(getContext(), "Error while loading developers", Toast.LENGTH_LONG).show() + ); + + } + + @Override + public void onStop() { + super.onStop(); + if (developersLoader != null) { + developersLoader.dispose(); + } + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.developers); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutFragment.java new file mode 100644 index 000000000..0fa7bd4bb --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutFragment.java @@ -0,0 +1,56 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Bundle; +import androidx.preference.PreferenceFragmentCompat; +import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.BuildConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.core.util.IntentUtils; + +public class AboutFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences_about); + + findPreference("about_version").setSummary(String.format( + "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.COMMIT_HASH)); + findPreference("about_version").setOnPreferenceClickListener((preference) -> { + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(getString(R.string.bug_report_title), + findPreference("about_version").getSummary()); + clipboard.setPrimaryClip(clip); + Snackbar.make(getView(), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show(); + return true; + }); + findPreference("about_developers").setOnPreferenceClickListener((preference) -> { + getFragmentManager().beginTransaction().replace(R.id.content, new AboutDevelopersFragment()) + .addToBackStack(getString(R.string.developers)).commit(); + return true; + }); + findPreference("about_translators").setOnPreferenceClickListener((preference) -> { + getFragmentManager().beginTransaction().replace(R.id.content, new AboutTranslatorsFragment()) + .addToBackStack(getString(R.string.translators)).commit(); + return true; + }); + findPreference("about_privacy_policy").setOnPreferenceClickListener((preference) -> { + IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy.html"); + return true; + }); + findPreference("about_licenses").setOnPreferenceClickListener((preference) -> { + getFragmentManager().beginTransaction().replace(R.id.content, new AboutLicensesFragment()) + .addToBackStack(getString(R.string.translators)).commit(); + return true; + }); + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.about_pref); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutLicensesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutLicensesFragment.java new file mode 100644 index 000000000..536d11e01 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutLicensesFragment.java @@ -0,0 +1,126 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.ListFragment; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.adapter.SimpleIconListAdapter; +import de.danoeh.antennapod.core.util.IntentUtils; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; + +public class AboutLicensesFragment extends ListFragment { + private Disposable licensesLoader; + private final ArrayList<LicenseItem> licenses = new ArrayList<>(); + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + getListView().setDivider(null); + + licensesLoader = Single.create((SingleOnSubscribe<ArrayList<LicenseItem>>) emitter -> { + licenses.clear(); + InputStream stream = getContext().getAssets().open("licenses.xml"); + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + NodeList libraryList = docBuilder.parse(stream).getElementsByTagName("library"); + for (int i = 0; i < libraryList.getLength(); i++) { + NamedNodeMap lib = libraryList.item(i).getAttributes(); + licenses.add(new LicenseItem( + lib.getNamedItem("name").getTextContent(), + String.format("By %s, %s license", + lib.getNamedItem("author").getTextContent(), + lib.getNamedItem("license").getTextContent()), + null, + lib.getNamedItem("website").getTextContent(), + lib.getNamedItem("licenseText").getTextContent())); + } + emitter.onSuccess(licenses); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + developers -> setListAdapter(new SimpleIconListAdapter<LicenseItem>(getContext(), developers)), + error -> Toast.makeText(getContext(), "Error while loading licenses", Toast.LENGTH_LONG).show() + ); + + } + + private static class LicenseItem extends SimpleIconListAdapter.ListItem { + final String licenseUrl; + final String licenseTextFile; + + LicenseItem(String title, String subtitle, String imageUrl, String licenseUrl, String licenseTextFile) { + super(title, subtitle, imageUrl); + this.licenseUrl = licenseUrl; + this.licenseTextFile = licenseTextFile; + } + } + + @Override + public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) { + super.onListItemClick(l, v, position, id); + + LicenseItem item = licenses.get(position); + CharSequence[] items = {"View website", "View license"}; + new AlertDialog.Builder(getContext()) + .setTitle(item.title) + .setItems(items, (dialog, which) -> { + if (which == 0) { + IntentUtils.openInBrowser(getContext(), item.licenseUrl); + } else if (which == 1) { + showLicenseText(item.licenseTextFile); + } + }).show(); + } + + private void showLicenseText(String licenseTextFile) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader( + getContext().getAssets().open(licenseTextFile))); + StringBuilder licenseText = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + licenseText.append(line).append("\n"); + } + + new AlertDialog.Builder(getContext()) + .setMessage(licenseText) + .show(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onStop() { + super.onStop(); + if (licensesLoader != null) { + licensesLoader.dispose(); + } + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.licenses); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutTranslatorsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutTranslatorsFragment.java new file mode 100644 index 000000000..914dbb9a2 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AboutTranslatorsFragment.java @@ -0,0 +1,64 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.os.Bundle; +import android.view.View; +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; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; + +public class AboutTranslatorsFragment extends ListFragment { + private Disposable translatorsLoader; + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + getListView().setDivider(null); + getListView().setSelector(android.R.color.transparent); + + translatorsLoader = Single.create((SingleOnSubscribe<ArrayList<SimpleIconListAdapter.ListItem>>) emitter -> { + ArrayList<SimpleIconListAdapter.ListItem> translators = new ArrayList<>(); + BufferedReader reader = new BufferedReader(new InputStreamReader( + getContext().getAssets().open("translators.csv"))); + String line; + while ((line = reader.readLine()) != null) { + String[] info = line.split(";"); + translators.add(new SimpleIconListAdapter.ListItem(info[0], info[1], null)); + } + emitter.onSuccess(translators); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + translators -> setListAdapter(new SimpleIconListAdapter<>(getContext(), translators)), + error -> Toast.makeText(getContext(), "Error while loading translators", Toast.LENGTH_LONG).show() + ); + + } + + @Override + public void onStop() { + super.onStop(); + if (translatorsLoader != null) { + translatorsLoader.dispose(); + } + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.translators); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java index a04615a00..fa17fed0a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java @@ -1,29 +1,42 @@ package de.danoeh.antennapod.fragment.preferences; +import android.Manifest; import android.app.Activity; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; +import android.os.Build; import android.os.Bundle; -import android.support.v7.preference.CheckBoxPreference; -import android.support.v7.preference.ListPreference; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceFragmentCompat; -import android.support.v7.preference.PreferenceScreen; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.preference.CheckBoxPreference; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; import android.util.Log; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.core.preferences.UserPreferences; + public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat { private static final String TAG = "AutoDnldPrefFragment"; + + private static final int LOCATION_PERMISSION_REQUEST_CODE = 1; + private static final String PREF_KEY_LOCATION_PERMISSION_REQUEST_PROMPT = "prefAutoDownloadWifiFilterAndroid10PermissionPrompt"; + private CheckBoxPreference[] selectedNetworks; + private Preference prefPermissionRequestPromptOnAndroid10 = null; + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.preferences_autodownload); @@ -35,6 +48,12 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat { } @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.pref_automatic_download_title); + } + + @Override public void onResume() { super.onResume(); checkAutodownloadItemVisibility(UserPreferences.isEnableAutodownload()); @@ -175,10 +194,65 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat { } private void setSelectedNetworksEnabled(boolean b) { + if (showPermissionRequestPromptOnAndroid10IfNeeded(b)) { + return; + } + if (selectedNetworks != null) { for (Preference p : selectedNetworks) { p.setEnabled(b); } } } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) { + return; + } + if (permissions.length > 0 && permissions[0].equals(Manifest.permission.ACCESS_FINE_LOCATION) && + grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + buildAutodownloadSelectedNetworksPreference(); + } + } + + private boolean showPermissionRequestPromptOnAndroid10IfNeeded(boolean wifiFilterEnabled) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + return false; + } + + // Cases Android 10(Q) or later + if (prefPermissionRequestPromptOnAndroid10 != null) { + getPreferenceScreen().removePreference(prefPermissionRequestPromptOnAndroid10); + prefPermissionRequestPromptOnAndroid10 = null; + } + + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + return false; + } + + // Case location permission not yet granted, permission-specific UI is needed + if (!wifiFilterEnabled) { + // Don't show the UI when WiFi filter disabled. + // it still return true, so that the caller knows + // it does not have required permission, and will not invoke codes that require so. + return true; + } + + Preference pref = new Preference(requireActivity()); + pref.setKey(PREF_KEY_LOCATION_PERMISSION_REQUEST_PROMPT); + pref.setTitle(R.string.autodl_wifi_filter_permission_title); + pref.setSummary(R.string.autodl_wifi_filter_permission_message); + pref.setIcon(R.drawable.ic_warning_red); + pref.setOnPreferenceClickListener(preference -> { + requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); + return true; + }); + pref.setPersistent(false); + getPreferenceScreen().addPreference(pref); + prefPermissionRequestPromptOnAndroid10 = pref; + return true; + } + } 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 491922056..c6ae8e20c 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 @@ -3,12 +3,13 @@ package de.danoeh.antennapod.fragment.preferences; import android.app.Activity; import android.content.SharedPreferences; import android.os.Bundle; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceFragmentCompat; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; import android.text.Html; import android.text.format.DateUtils; import android.widget.Toast; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.service.GpodnetSyncService; import de.danoeh.antennapod.dialog.AuthenticationDialog; @@ -30,6 +31,12 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { } @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.gpodnet_main_label); + } + + @Override public void onResume() { super.onResume(); GpodnetPreferences.registerOnSharedPreferenceChangeListener(gpoddernetListener); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java index 229274b76..51f31eb92 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.fragment.preferences; import android.os.Bundle; -import android.support.v7.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceFragmentCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; @@ -14,6 +14,12 @@ public class IntegrationsPreferencesFragment extends PreferenceFragmentCompat { setupIntegrationsScreen(); } + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.integrations_label); + } + private void setupIntegrationsScreen() { findPreference(PREF_SCREEN_GPODDER).setOnPreferenceClickListener(preference -> { ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_gpodder); 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 dcba5fe89..5fd38d663 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,27 +1,15 @@ package de.danoeh.antennapod.fragment.preferences; -import android.content.ActivityNotFoundException; -import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.support.v4.content.FileProvider; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.preference.PreferenceFragmentCompat; -import android.util.Log; -import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.PreferenceFragmentCompat; import com.bytehamster.lib.preferencesearch.SearchConfiguration; import com.bytehamster.lib.preferencesearch.SearchPreference; -import de.danoeh.antennapod.CrashReportWriter; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.AboutActivity; +import de.danoeh.antennapod.activity.BugReportActivity; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.activity.StatisticsActivity; - -import java.util.List; +import de.danoeh.antennapod.core.util.IntentUtils; public class MainPreferencesFragment extends PreferenceFragmentCompat { private static final String TAG = "MainPreferencesFragment"; @@ -31,9 +19,9 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork"; private static final String PREF_SCREEN_INTEGRATIONS = "prefScreenIntegrations"; private static final String PREF_SCREEN_STORAGE = "prefScreenStorage"; - private static final String PREF_KNOWN_ISSUES = "prefKnownIssues"; private static final String PREF_FAQ = "prefFaq"; - private static final String PREF_SEND_CRASH_REPORT = "prefSendCrashReport"; + private static final String PREF_VIEW_MAILING_LIST = "prefViewMailingList"; + private static final String PREF_SEND_BUG_REPORT = "prefSendBugReport"; private static final String STATISTICS = "statistics"; private static final String PREF_ABOUT = "prefAbout"; @@ -44,6 +32,12 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { setupSearch(); } + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label); + } + private void setupMainScreen() { findPreference(PREF_SCREEN_USER_INTERFACE).setOnPreferenceClickListener(preference -> { ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_user_interface); @@ -68,59 +62,32 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { findPreference(PREF_ABOUT).setOnPreferenceClickListener( preference -> { - startActivity(new Intent(getActivity(), AboutActivity.class)); + getFragmentManager().beginTransaction().replace(R.id.content, new AboutFragment()) + .addToBackStack(getString(R.string.about_pref)).commit(); return true; } ); findPreference(STATISTICS).setOnPreferenceClickListener( preference -> { - startActivity(new Intent(getActivity(), StatisticsActivity.class)); + getFragmentManager().beginTransaction().replace(R.id.content, new StatisticsFragment()) + .addToBackStack(getString(R.string.statistics_label)).commit(); return true; } ); - findPreference(PREF_KNOWN_ISSUES).setOnPreferenceClickListener(preference -> { - openInBrowser("https://github.com/AntennaPod/AntennaPod/labels/bug"); + findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> { + IntentUtils.openInBrowser(getContext(), "https://antennapod.org/faq.html"); return true; }); - findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> { - openInBrowser("https://antennapod.org/faq.html"); + findPreference(PREF_VIEW_MAILING_LIST).setOnPreferenceClickListener(preference -> { + IntentUtils.openInBrowser(getContext(), "https://groups.google.com/forum/#!forum/antennapod"); return true; }); - findPreference(PREF_SEND_CRASH_REPORT).setOnPreferenceClickListener(preference -> { - Context context = getActivity().getApplicationContext(); - Intent emailIntent = new Intent(Intent.ACTION_SEND); - emailIntent.setType("text/plain"); - emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"Martin.Fietz@gmail.com"}); - emailIntent.putExtra(Intent.EXTRA_SUBJECT, "AntennaPod Crash Report"); - emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe what you were doing when the app crashed"); - // the attachment - Uri fileUri = FileProvider.getUriForFile(context, context.getString(R.string.provider_authority), - CrashReportWriter.getFile()); - emailIntent.putExtra(Intent.EXTRA_STREAM, fileUri); - emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - String intentTitle = getActivity().getString(R.string.send_email); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(emailIntent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - } - getActivity().startActivity(Intent.createChooser(emailIntent, intentTitle)); + findPreference(PREF_SEND_BUG_REPORT).setOnPreferenceClickListener(preference -> { + startActivity(new Intent(getActivity(), BugReportActivity.class)); return true; }); } - private void openInBrowser(String url) { - try { - Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(myIntent); - } catch (ActivityNotFoundException e) { - Toast.makeText(getActivity(), R.string.pref_no_browser_found, Toast.LENGTH_LONG).show(); - Log.e(TAG, Log.getStackTraceString(e)); - } - } - private void setupSearch() { SearchPreference searchPreference = (SearchPreference) findPreference("searchPreference"); SearchConfiguration config = searchPreference.getSearchConfiguration(); 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 ac2436e25..1ca8f63aa 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 @@ -4,12 +4,9 @@ import android.app.TimePickerDialog; import android.content.Context; import android.content.res.Resources; import android.os.Bundle; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceFragmentCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceFragmentCompat; import android.text.format.DateFormat; -import com.afollestad.materialdialogs.MaterialDialog; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -31,6 +28,12 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { } @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.network_pref); + } + + @Override public void onResume() { super.onResume(); setUpdateIntervalText(); @@ -59,7 +62,7 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { // validate and set correct value: number of downloads between 1 and 50 (inclusive) findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> { ProxyDialog dialog = new ProxyDialog(getActivity()); - dialog.createDialog().show(); + dialog.show(); return true; }); } @@ -101,13 +104,10 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { private void showUpdateIntervalTimePreferencesDialog() { final Context context = getActivity(); - MaterialDialog.Builder builder = new MaterialDialog.Builder(context); - builder.title(R.string.pref_autoUpdateIntervallOrTime_title); - builder.content(R.string.pref_autoUpdateIntervallOrTime_message); - builder.positiveText(R.string.pref_autoUpdateIntervallOrTime_Interval); - builder.negativeText(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay); - builder.neutralText(R.string.pref_autoUpdateIntervallOrTime_Disable); - builder.onPositive((dialog, which) -> { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.pref_autoUpdateIntervallOrTime_title); + builder.setMessage(R.string.pref_autoUpdateIntervallOrTime_message); + builder.setPositiveButton(R.string.pref_autoUpdateIntervallOrTime_Interval, (dialog, which) -> { AlertDialog.Builder builder1 = new AlertDialog.Builder(context); builder1.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_Interval)); final String[] values = context.getResources().getStringArray(R.array.update_intervall_values); @@ -127,8 +127,9 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { builder1.setNegativeButton(context.getString(R.string.cancel_label), null); builder1.show(); }); - builder.onNegative((dialog, which) -> { - int hourOfDay = 7, minute = 0; + builder.setNegativeButton(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay, (dialog, which) -> { + int hourOfDay = 7; + int minute = 0; int[] updateTime = UserPreferences.getUpdateTimeOfDay(); if (updateTime.length == 2) { hourOfDay = updateTime[0]; @@ -145,7 +146,7 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { timePickerDialog.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay)); timePickerDialog.show(); }); - builder.onNeutral((dialog, which) -> { + builder.setNeutralButton(R.string.pref_autoUpdateIntervallOrTime_Disable, (dialog, which) -> { UserPreferences.disableAutoUpdate(); setUpdateIntervalText(); }); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java index e1714d4bd..b82bba89b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java @@ -4,10 +4,18 @@ import android.app.Activity; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; -import android.support.v7.preference.ListPreference; -import android.support.v7.preference.PreferenceFragmentCompat; + +import androidx.annotation.NonNull; +import androidx.collection.ArrayMap; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import java.util.Map; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MediaplayerActivity; +import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; import de.danoeh.antennapod.dialog.VariableSpeedDialog; @@ -28,9 +36,9 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { } @Override - public void onResume() { - super.onResume(); - checkSonicItemVisibility(); + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.playback_pref); } private void setupPlaybackScreen() { @@ -56,6 +64,43 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { behaviour.setEntries(R.array.video_background_behavior_options_without_pip); behaviour.setEntryValues(R.array.video_background_behavior_values_without_pip); } + + buildEnqueueLocationPreference(); + } + + private void buildEnqueueLocationPreference() { + final Resources res = requireActivity().getResources(); + final Map<String, String> options = new ArrayMap<>(); + { + String[] keys = res.getStringArray(R.array.enqueue_location_values); + String[] values = res.getStringArray(R.array.enqueue_location_options); + for (int i = 0; i < keys.length; i++) { + options.put(keys[i], values[i]); + } + } + + ListPreference pref = requirePreference(UserPreferences.PREF_ENQUEUE_LOCATION); + pref.setSummary(res.getString(R.string.pref_enqueue_location_sum, options.get(pref.getValue()))); + + pref.setOnPreferenceChangeListener((preference, newValue) -> { + if (!(newValue instanceof String)) { + return false; + } + String newValStr = (String)newValue; + pref.setSummary(res.getString(R.string.pref_enqueue_location_sum, options.get(newValStr))); + return true; + }); + } + + @NonNull + private <T extends Preference> T requirePreference(@NonNull CharSequence key) { + // Possibly put it to a common method in abstract base class + T result = findPreference(key); + if (result == null) { + throw new IllegalArgumentException("Preference with key '" + key + "' is not found"); + + } + return result; } private void buildSmartMarkAsPlayedPreference() { @@ -79,14 +124,4 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { } pref.setEntries(entries); } - - - - private void checkSonicItemVisibility() { - if (Build.VERSION.SDK_INT < 16) { - ListPreference p = (ListPreference) findPreference(UserPreferences.PREF_MEDIA_PLAYER); - p.setEntries(R.array.media_player_options_no_sonic); - p.setEntryValues(R.array.media_player_values_no_sonic); - } - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java new file mode 100644 index 000000000..7e1fdf2b9 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java @@ -0,0 +1,180 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +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; +import android.widget.ProgressBar; +import android.widget.RadioButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.adapter.StatisticsListAdapter; +import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +/** + * Displays the 'statistics' screen + */ +public class StatisticsFragment extends Fragment { + private static final String TAG = StatisticsFragment.class.getSimpleName(); + private static final String PREF_NAME = "StatisticsActivityPrefs"; + private static final String PREF_COUNT_ALL = "countAll"; + + private Disposable disposable; + private RecyclerView feedStatisticsList; + private ProgressBar progressBar; + private StatisticsListAdapter listAdapter; + private boolean countAll = false; + private SharedPreferences prefs; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + prefs = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + countAll = prefs.getBoolean(PREF_COUNT_ALL, false); + setHasOptionsMenu(true); + } + + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.statistics_activity, container, false); + feedStatisticsList = root.findViewById(R.id.statistics_list); + progressBar = root.findViewById(R.id.progressBar); + listAdapter = new StatisticsListAdapter(getContext()); + listAdapter.setCountAll(countAll); + feedStatisticsList.setLayoutManager(new LinearLayoutManager(getContext())); + feedStatisticsList.setAdapter(listAdapter); + return root; + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.statistics_label); + refreshStatistics(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (disposable != null) { + disposable.dispose(); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.statistics, menu); + menu.findItem(R.id.statistics_reset).setEnabled(!countAll); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.statistics_mode) { + selectStatisticsMode(); + return true; + } + if (item.getItemId() == R.id.statistics_reset) { + confirmResetStatistics(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void selectStatisticsMode() { + View contentView = View.inflate(getContext(), R.layout.statistics_mode_select_dialog, null); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setView(contentView); + builder.setTitle(R.string.statistics_mode); + + if (countAll) { + ((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).setChecked(true); + } else { + ((RadioButton) contentView.findViewById(R.id.statistics_mode_normal)).setChecked(true); + } + + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { + countAll = ((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).isChecked(); + listAdapter.setCountAll(countAll); + prefs.edit().putBoolean(PREF_COUNT_ALL, countAll).apply(); + refreshStatistics(); + getActivity().invalidateOptionsMenu(); + }); + + builder.show(); + } + + private void confirmResetStatistics() { + if (!countAll) { + ConfirmationDialog conDialog = new ConfirmationDialog( + getActivity(), + R.string.statistics_reset_data, + R.string.statistics_reset_data_msg) { + + @Override + public void onConfirmButtonPressed(DialogInterface dialog) { + dialog.dismiss(); + doResetStatistics(); + } + }; + conDialog.createNewDialog().show(); + } + } + + private void doResetStatistics() { + progressBar.setVisibility(View.VISIBLE); + feedStatisticsList.setVisibility(View.GONE); + if (disposable != null) { + disposable.dispose(); + } + + disposable = Completable.fromFuture(DBWriter.resetStatistics()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::refreshStatistics, error -> Log.e(TAG, Log.getStackTraceString(error))); + } + + private void refreshStatistics() { + progressBar.setVisibility(View.VISIBLE); + feedStatisticsList.setVisibility(View.GONE); + loadStatistics(); + } + + private void loadStatistics() { + if (disposable != null) { + disposable.dispose(); + } + disposable = Observable.fromCallable(() -> DBReader.getStatistics(countAll)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + listAdapter.update(result); + progressBar.setVisibility(View.GONE); + feedStatisticsList.setVisibility(View.VISIBLE); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java index b4226b546..2c1590c47 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java @@ -4,6 +4,7 @@ import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -11,15 +12,19 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.FileProvider; -import android.support.v7.app.AlertDialog; -import android.support.v7.preference.PreferenceFragmentCompat; +import androidx.core.app.ActivityCompat; +import androidx.core.content.FileProvider; +import androidx.documentfile.provider.DocumentFile; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceFragmentCompat; import android.util.Log; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.DirectoryChooserActivity; import de.danoeh.antennapod.activity.ImportExportActivity; import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; import de.danoeh.antennapod.asynctask.ExportWorker; import de.danoeh.antennapod.core.export.ExportWriter; import de.danoeh.antennapod.core.export.html.HtmlWriter; @@ -45,6 +50,12 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 41; + private static final int CHOOSE_OPML_EXPORT_PATH = 1; + private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml"; + private static final String CONTENT_TYPE_OPML = "text/x-opml"; + private static final int CHOOSE_HTML_EXPORT_PATH = 2; + private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html"; + private static final String CONTENT_TYPE_HTML = "text/html"; private Disposable disposable; @Override @@ -54,11 +65,25 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { } @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.storage_pref); + } + + @Override public void onResume() { super.onResume(); setDataFolderText(); } + @Override + public void onStop() { + super.onStop(); + if (disposable != null) { + disposable.dispose(); + } + } + private void setupStorageScreen() { final Activity activity = getActivity(); @@ -69,9 +94,16 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { } ); findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( - preference -> export(new OpmlWriter())); + preference -> { + openOpmlExportPathPicker(); + return true; + } + ); findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( - preference -> export(new HtmlWriter())); + preference -> { + openHtmlExportPathPicker(); + return true; + }); findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( preference -> { activity.startActivity(new Intent(activity, OpmlImportFromPathActivity.class)); @@ -129,52 +161,65 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { } private boolean export(ExportWriter exportWriter) { + return export(exportWriter, null); + } + + private boolean export(ExportWriter exportWriter, final Uri uri) { Context context = getActivity(); final ProgressDialog progressDialog = new ProgressDialog(context); progressDialog.setMessage(context.getString(R.string.exporting_label)); progressDialog.setIndeterminate(true); progressDialog.show(); - final AlertDialog.Builder alert = new AlertDialog.Builder(context) - .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - Observable<File> observable = new ExportWorker(exportWriter).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - alert.setTitle(R.string.export_success_title); - String message = context.getString(R.string.export_success_sum, output.toString()); - alert.setMessage(message); - alert.setPositiveButton(R.string.send_label, (dialog, which) -> { + if (uri == null) { + Observable<File> observable = new ExportWorker(exportWriter).exportObservable(); + disposable = observable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> { Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), context.getString(R.string.provider_authority), output); - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_SUBJECT, - context.getResources().getText(R.string.opml_export_label)); - sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri); - sendIntent.setType("text/plain"); - sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - } - context.startActivity(Intent.createChooser(sendIntent, - context.getResources().getText(R.string.send_label))); - }); - alert.create().show(); - }, error -> { - alert.setTitle(R.string.export_error_label); - alert.setMessage(error.getMessage()); - alert.show(); - }, progressDialog::dismiss); + showExportSuccessDialog(context.getString(R.string.export_success_sum, output.toString()), fileUri); + }, this::showExportErrorDialog, progressDialog::dismiss); + } else { + Observable<DocumentFile> observable = new DocumentFileExportWorker(exportWriter, context, uri).exportObservable(); + disposable = observable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> { + showExportSuccessDialog(context.getString(R.string.export_success_sum, output.getUri()), output.getUri()); + }, this::showExportErrorDialog, progressDialog::dismiss); + } return true; } - public void unsubscribeExportSubscription() { - if (disposable != null) { - disposable.dispose(); - } + private void showExportSuccessDialog(final String message, final Uri streamUri) { + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) + .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_success_title); + alert.setMessage(message); + alert.setPositiveButton(R.string.send_label, (dialog, which) -> { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label)); + sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); + sendIntent.setType("text/plain"); + sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + List<ResolveInfo> resInfoList = getContext().getPackageManager() + .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } + getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); + }); + alert.create().show(); + } + + private void showExportErrorDialog(final Throwable error) { + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) + .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_error_label); + alert.setMessage(error.getMessage()); + alert.show(); } @SuppressLint("NewApi") @@ -184,22 +229,22 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); File path; - if(dir != null) { + if (dir != null) { path = new File(dir); } else { path = getActivity().getExternalFilesDir(null); } String message = null; - final Context context= getActivity().getApplicationContext(); - if(!path.exists()) { + final Context context = getActivity().getApplicationContext(); + if (!path.exists()) { message = String.format(context.getString(R.string.folder_does_not_exist_error), dir); - } else if(!path.canRead()) { + } else if (!path.canRead()) { message = String.format(context.getString(R.string.folder_not_readable_error), dir); - } else if(!path.canWrite()) { + } else if (!path.canWrite()) { message = String.format(context.getString(R.string.folder_not_writable_error), dir); } - if(message == null) { + if (message == null) { Log.d(TAG, "Setting data folder: " + dir); UserPreferences.setDataFolder(dir); setDataFolderText(); @@ -210,6 +255,16 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { ab.show(); } } + + if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_OPML_EXPORT_PATH) { + Uri uri = data.getData(); + export(new OpmlWriter(), uri); + } + + if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_HTML_EXPORT_PATH) { + Uri uri = data.getData(); + export(new HtmlWriter(), uri); + } } private void setDataFolderText() { @@ -231,6 +286,50 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { activity.startActivityForResult(intent, DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); } + private void openOpmlExportPathPicker() { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { + Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(CONTENT_TYPE_OPML) + .putExtra(Intent.EXTRA_TITLE, DEFAULT_OPML_OUTPUT_NAME); + + // Creates an implicit intent to launch a file manager which lets + // the user choose a specific directory to export to. + try { + startActivityForResult(intentPickAction, CHOOSE_OPML_EXPORT_PATH); + return; + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + } + + // If we are using a SDK lower than API 21 or the implicit intent failed + // fallback to the legacy export process + export(new OpmlWriter()); + } + + private void openHtmlExportPathPicker() { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { + Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(CONTENT_TYPE_HTML) + .putExtra(Intent.EXTRA_TITLE, DEFAULT_HTML_OUTPUT_NAME); + + // Creates an implicit intent to launch a file manager which lets + // the user choose a specific directory to export to. + try { + startActivityForResult(intentPickAction, CHOOSE_HTML_EXPORT_PATH); + return; + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + } + + // If we are using a SDK lower than API 21 or the implicit intent failed + // fallback to the legacy export process + export(new HtmlWriter()); + } + private void showChooseDataFolderDialog() { ChooseDataFolderDialog.showDialog( getActivity(), new ChooseDataFolderDialog.RunnableWithString() { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java index e1d44f7d3..191999cf7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java @@ -4,13 +4,14 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.support.design.widget.Snackbar; -import android.support.v7.app.AlertDialog; -import android.support.v7.preference.PreferenceFragmentCompat; +import com.google.android.material.snackbar.Snackbar; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceFragmentCompat; import android.widget.ListView; import android.widget.Toast; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.preferences.UserPreferences; import org.apache.commons.lang3.ArrayUtils; @@ -25,6 +26,12 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat { setupInterfaceScreen(); } + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.user_interface_label); + } + private void setupInterfaceScreen() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 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 156657a00..60eaf14a5 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -1,11 +1,11 @@ package de.danoeh.antennapod.menuhandler; import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.support.annotation.Nullable; +import android.os.Handler; +import androidx.annotation.NonNull; +import com.google.android.material.snackbar.Snackbar; +import androidx.fragment.app.Fragment; import android.util.Log; -import android.widget.Toast; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; @@ -18,7 +18,6 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService; 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.core.util.LongList; import de.danoeh.antennapod.core.util.ShareUtils; /** @@ -51,35 +50,21 @@ public class FeedItemMenuHandler { * @param mi An instance of MenuInterface that the method uses to change a * MenuItem's visibility * @param selectedItem The FeedItem for which the menu is supposed to be prepared - * @param showExtendedMenu True if MenuItems that let the user share information about - * the FeedItem and visit its website should be set visible. This - * parameter should be set to false if the menu space is limited. - * @param queueAccess Used for testing if the queue contains the selected item; only used for - * move to top/bottom in the queue * @return Returns true if selectedItem is not null. */ public static boolean onPrepareMenu(MenuInterface mi, - FeedItem selectedItem, - boolean showExtendedMenu, - @Nullable LongList queueAccess) { + FeedItem selectedItem) { if (selectedItem == null) { return false; } boolean hasMedia = selectedItem.getMedia() != null; boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING; - boolean keepSorted = UserPreferences.isQueueKeepSorted(); if (!isPlaying) { mi.setItemVisibility(R.id.skip_episode_item, false); } boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE); - if (queueAccess == null || queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId() || keepSorted) { - mi.setItemVisibility(R.id.move_to_top_item, false); - } - if (queueAccess == null || queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == selectedItem.getId() || keepSorted) { - mi.setItemVisibility(R.id.move_to_bottom_item, false); - } if (!isInQueue) { mi.setItemVisibility(R.id.remove_from_queue_item, false); } @@ -87,12 +72,12 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.add_to_queue_item, false); } - if (!showExtendedMenu || !ShareUtils.hasLinkToShare(selectedItem)) { + if (!ShareUtils.hasLinkToShare(selectedItem)) { mi.setItemVisibility(R.id.visit_website_item, false); mi.setItemVisibility(R.id.share_link_item, false); mi.setItemVisibility(R.id.share_link_with_position_item, false); } - if (!showExtendedMenu || !hasMedia || selectedItem.getMedia().getDownload_url() == null) { + if (!hasMedia || selectedItem.getMedia().getDownload_url() == null) { mi.setItemVisibility(R.id.share_download_url_item, false); mi.setItemVisibility(R.id.share_download_url_with_position_item, false); } @@ -104,6 +89,7 @@ public class FeedItemMenuHandler { boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists(); mi.setItemVisibility(R.id.share_file, fileDownloaded); + mi.setItemVisibility(R.id.remove_new_flag_item, selectedItem.isNew()); if (selectedItem.isPlayed()) { mi.setItemVisibility(R.id.mark_read_item, false); } else { @@ -114,7 +100,7 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.reset_position, false); } - if(!UserPreferences.isEnableAutodownload()) { + if(!UserPreferences.isEnableAutodownload() || fileDownloaded) { mi.setItemVisibility(R.id.activate_auto_download, false); mi.setItemVisibility(R.id.deactivate_auto_download, false); } else if(selectedItem.getAutoDownload()) { @@ -141,10 +127,8 @@ public class FeedItemMenuHandler { */ public static boolean onPrepareMenu(MenuInterface mi, FeedItem selectedItem, - boolean showExtendedMenu, - LongList queueAccess, int... excludeIds) { - boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess); + boolean rc = onPrepareMenu(mi, selectedItem); if (rc && excludeIds != null) { for (int id : excludeIds) { mi.setItemVisibility(id, false); @@ -153,8 +137,16 @@ public class FeedItemMenuHandler { return rc; } - public static boolean onMenuItemClicked(Context context, int menuItemId, - FeedItem selectedItem) { + /** + * Default menu handling for the given FeedItem. + * + * A Fragment instance, (rather than the more generic Context), is needed as a parameter + * to support some UI operations, e.g., creating a Snackbar. + */ + public static boolean onMenuItemClicked(@NonNull Fragment fragment, int menuItemId, + @NonNull FeedItem selectedItem) { + + @NonNull Context context = fragment.requireContext(); switch (menuItemId) { case R.id.skip_episode_item: IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SKIP_CURRENT_EPISODE); @@ -162,9 +154,12 @@ public class FeedItemMenuHandler { case R.id.remove_item: DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId()); break; + case R.id.remove_new_flag_item: + removeNewFlagWithUndo(fragment, selectedItem); + break; case R.id.mark_read_item: selectedItem.setPlayed(true); - DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, false); + DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true); if(GpodnetPreferences.loggedIn()) { FeedMedia media = selectedItem.getMedia(); // not all items have media, Gpodder only cares about those that do @@ -216,14 +211,7 @@ public class FeedItemMenuHandler { DBWriter.setFeedItemAutoDownload(selectedItem, false); break; case R.id.visit_website_item: - Uri uri = Uri.parse(FeedItemUtil.getLinkWithFallback(selectedItem)); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(context, intent)) { - context.startActivity(intent); - } else { - Toast.makeText(context, context.getString(R.string.download_error_malformed_url), - Toast.LENGTH_SHORT).show(); - } + IntentUtils.openInBrowser(context, FeedItemUtil.getLinkWithFallback(selectedItem)); break; case R.id.share_link_item: ShareUtils.shareFeedItemLink(context, selectedItem); @@ -249,4 +237,39 @@ public class FeedItemMenuHandler { return true; } + /** + * Remove new flag with additional UI logic to allow undo with Snackbar. + * + * Undo is useful for Remove new flag, given there is no UI to undo it otherwise + * ,i.e., there is (context) menu item for add new flag + */ + public static void removeNewFlagWithUndo(@NonNull Fragment fragment, FeedItem item) { + if (item == null) { + return; + } + + Log.d(TAG, "removeNewFlagWithUndo(" + item.getId() + ")"); + // we're marking it as unplayed since the user didn't actually play it + // but they don't want it considered 'NEW' anymore + DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId()); + + final Handler h = new Handler(fragment.requireContext().getMainLooper()); + final Runnable r = () -> { + FeedMedia media = item.getMedia(); + if (media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) { + DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), media.getId()); + } + }; + + Snackbar snackbar = Snackbar.make(fragment.getView(), fragment.getString(R.string.removed_new_flag_label), + Snackbar.LENGTH_LONG); + snackbar.setAction(fragment.getString(R.string.undo), v -> { + DBWriter.markItemPlayed(FeedItem.NEW, item.getId()); + // don't forget to cancel the thing that's going to remove the media + h.removeCallbacks(r); + }); + snackbar.show(); + h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f)); + } + } 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 3949119fc..e32deba27 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -2,31 +2,28 @@ package de.danoeh.antennapod.menuhandler; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.support.v7.app.AlertDialog; -import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.Toast; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; +import androidx.annotation.NonNull; + +import org.apache.commons.lang3.StringUtils; + import java.util.Set; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedItemFilter; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.SortOrder; import de.danoeh.antennapod.dialog.FilterDialog; +import de.danoeh.antennapod.dialog.IntraFeedSortDialog; /** * Handles interactions with the FeedItemMenu. @@ -50,6 +47,10 @@ public class FeedMenuHandler { Log.d(TAG, "Preparing options menu"); menu.findItem(R.id.refresh_complete_item).setVisible(selectedFeed.isPaged()); + if (StringUtils.isBlank(selectedFeed.getLink())) { + menu.findItem(R.id.visit_website_item).setVisible(false); + menu.findItem(R.id.share_link_item).setVisible(false); + } return true; } @@ -68,6 +69,9 @@ public class FeedMenuHandler { case R.id.refresh_complete_item: DBTasks.forceRefreshCompleteFeed(context, selectedFeed); break; + case R.id.sort_items: + showSortDialog(context, selectedFeed); + break; case R.id.filter_items: showFilterDialog(context, selectedFeed); break; @@ -86,14 +90,7 @@ public class FeedMenuHandler { conDialog.createNewDialog().show(); break; case R.id.visit_website_item: - Uri uri = Uri.parse(selectedFeed.getLink()); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(context, intent)) { - context.startActivity(intent); - } else { - Toast.makeText(context, context.getString(R.string.download_error_malformed_url), - Toast.LENGTH_SHORT).show(); - } + IntentUtils.openInBrowser(context, selectedFeed.getLink()); break; case R.id.share_link_item: ShareUtils.shareFeedlink(context, selectedFeed); @@ -118,4 +115,17 @@ public class FeedMenuHandler { filterDialog.openDialog(); } + + + private static void showSortDialog(Context context, Feed selectedFeed) { + IntraFeedSortDialog sortDialog = new IntraFeedSortDialog(context, selectedFeed.getSortOrder()) { + @Override + protected void updateSort(@NonNull SortOrder sortOrder) { + selectedFeed.setSortOrder(sortOrder); + DBWriter.setFeedItemSortOrder(selectedFeed.getId(), sortOrder); + } + }; + sortDialog.openDialog(); + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java index 7b9fcad9b..64eb72ee3 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java @@ -2,14 +2,8 @@ package de.danoeh.antennapod.menuhandler; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Color; -import android.os.Build; -import android.support.v7.widget.SearchView; import android.view.Menu; import android.view.MenuItem; -import android.widget.EditText; - -import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; /** @@ -17,18 +11,6 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; */ public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuItemUtils { - public static void adjustTextColor(Context context, SearchView sv) { - if(Build.VERSION.SDK_INT < 14) { - EditText searchEditText = sv.findViewById(R.id.search_src_text); - if (UserPreferences.getTheme() == de.danoeh.antennapod.R.style.Theme_AntennaPod_Dark - || UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack) { - searchEditText.setTextColor(Color.WHITE); - } else { - searchEditText.setTextColor(Color.BLACK); - } - } - } - @SuppressWarnings("ResourceType") public static void refreshLockItem(Context context, Menu menu) { final MenuItem queueLock = menu.findItem(de.danoeh.antennapod.R.id.queue_lock); diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/MasterSwitchPreference.java b/app/src/main/java/de/danoeh/antennapod/preferences/MasterSwitchPreference.java index b810cbfa6..007457c24 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/MasterSwitchPreference.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/MasterSwitchPreference.java @@ -4,8 +4,8 @@ import android.annotation.TargetApi; import android.content.Context; import android.graphics.Typeface; import android.os.Build; -import android.support.v14.preference.SwitchPreference; -import android.support.v7.preference.PreferenceViewHolder; +import androidx.preference.SwitchPreference; +import androidx.preference.PreferenceViewHolder; import android.util.AttributeSet; import android.util.TypedValue; import android.widget.TextView; diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/NumberPickerPreference.java b/app/src/main/java/de/danoeh/antennapod/preferences/NumberPickerPreference.java index 50e76838c..a58986241 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/NumberPickerPreference.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/NumberPickerPreference.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.preferences; import android.app.AlertDialog; import android.content.Context; -import android.support.v7.preference.Preference; +import androidx.preference.Preference; import android.text.InputFilter; import android.util.AttributeSet; import android.view.View; diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index 1879dcbf8..d0321ddd2 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -3,8 +3,12 @@ package de.danoeh.antennapod.preferences; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; + import de.danoeh.antennapod.BuildConfig; +import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.preferences.UserPreferences.EnqueueLocation; +import de.danoeh.antennapod.core.util.download.AutoUpdateManager; import de.danoeh.antennapod.core.util.gui.NotificationUtils; public class PreferenceUpgrader { @@ -21,7 +25,7 @@ public class PreferenceUpgrader { if (oldVersion != newVersion) { NotificationUtils.createChannels(context); - UserPreferences.restartUpdateAlarm(); + AutoUpdateManager.restartUpdateAlarm(); upgrade(oldVersion); upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply(); @@ -29,6 +33,9 @@ public class PreferenceUpgrader { } private static void upgrade(int oldVersion) { + if (oldVersion == -1) { + return; + } if (oldVersion < 1070196) { // migrate episode cleanup value (unit changed from days to hours) int oldValueInDays = UserPreferences.getEpisodeCleanupValue(); @@ -62,5 +69,21 @@ public class PreferenceUpgrader { break; } } + if (oldVersion < 1070400) { + int theme = UserPreferences.getTheme(); + if (theme == R.style.Theme_AntennaPod_Light) { + prefs.edit().putString(UserPreferences.PREF_THEME, "system").apply(); + } + + UserPreferences.setQueueLocked(false); + prefs.edit().putBoolean(UserPreferences.PREF_STREAM_OVER_DOWNLOAD, false).apply(); + + if (!prefs.contains(UserPreferences.PREF_ENQUEUE_LOCATION)) { + final String keyOldPrefEnqueueFront = "prefQueueAddToFront"; + boolean enqueueAtFront = prefs.getBoolean(keyOldPrefEnqueueFront, false); + EnqueueLocation enqueueLocation = enqueueAtFront ? EnqueueLocation.FRONT : EnqueueLocation.BACK; + UserPreferences.setEnqueueLocation(enqueueLocation); + } + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/spa/SPAUtil.java b/app/src/main/java/de/danoeh/antennapod/spa/SPAUtil.java index 03958508d..5fa6588d9 100644 --- a/app/src/main/java/de/danoeh/antennapod/spa/SPAUtil.java +++ b/app/src/main/java/de/danoeh/antennapod/spa/SPAUtil.java @@ -46,7 +46,7 @@ public class SPAUtil { SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(PREF_HAS_QUERIED_SP_APPS, true); - editor.commit(); + editor.apply(); return true; } else { @@ -63,7 +63,7 @@ public class SPAUtil { SharedPreferences.Editor editor = PreferenceManager .getDefaultSharedPreferences(c.getApplicationContext()).edit(); editor.putBoolean(PREF_HAS_QUERIED_SP_APPS, false); - editor.commit(); + editor.apply(); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/AspectRatioVideoView.java b/app/src/main/java/de/danoeh/antennapod/view/AspectRatioVideoView.java index e79389fb3..4c116fa2d 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/AspectRatioVideoView.java +++ b/app/src/main/java/de/danoeh/antennapod/view/AspectRatioVideoView.java @@ -88,7 +88,7 @@ public class AspectRatioVideoView extends VideoView { mVideoWidth = videoWidth; mVideoHeight = videoHeight; - /** + /* * If this isn't set the video is stretched across the * SurfaceHolders display surface (i.e. the SurfaceHolder * as the same size and the video is drawn to fit this diff --git a/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java b/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java index 8b886e699..0bfd0247f 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java @@ -2,14 +2,14 @@ package de.danoeh.antennapod.view; import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.support.annotation.AttrRes;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.RecyclerView;
+import android.widget.AbsListView;
+import androidx.annotation.AttrRes;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
-import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -54,28 +54,31 @@ public class EmptyViewHandler { emptyView.setVisibility(View.GONE);
}
- public void attachToListView(ListView listView) {
+ public void attachToListView(AbsListView listView) {
if (layoutAdded) {
- throw new IllegalStateException("Can not attach to ListView multiple times");
+ throw new IllegalStateException("Can not attach EmptyView multiple times");
}
+ addToParentView(listView);
layoutAdded = true;
- ((ViewGroup) listView.getParent()).addView(emptyView);
listView.setEmptyView(emptyView);
}
public void attachToRecyclerView(RecyclerView recyclerView) {
if (layoutAdded) {
- throw new IllegalStateException("Can not attach to ListView multiple times");
+ throw new IllegalStateException("Can not attach EmptyView multiple times");
}
+ addToParentView(recyclerView);
layoutAdded = true;
this.recyclerView = recyclerView;
- ViewGroup parent = ((ViewGroup) recyclerView.getParent());
- parent.addView(emptyView);
updateAdapter(recyclerView.getAdapter());
+ }
+ private void addToParentView(View view) {
+ ViewGroup parent = ((ViewGroup) view.getParent());
+ parent.addView(emptyView);
if (parent instanceof RelativeLayout) {
RelativeLayout.LayoutParams layoutParams =
- (RelativeLayout.LayoutParams)emptyView.getLayoutParams();
+ (RelativeLayout.LayoutParams) emptyView.getLayoutParams();
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
emptyView.setLayoutParams(layoutParams);
}
@@ -99,7 +102,7 @@ public class EmptyViewHandler { }
};
- private void updateVisibility() {
+ public void updateVisibility() {
boolean empty;
if (adapter == null) {
empty = true;
diff --git a/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java new file mode 100644 index 000000000..d1b2abf23 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java @@ -0,0 +1,121 @@ +package de.danoeh.antennapod.view; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatImageView; +import io.reactivex.annotations.Nullable; + +public class PieChartView extends AppCompatImageView { + private PieChartDrawable drawable; + + public PieChartView(Context context) { + super(context); + setup(); + } + + public PieChartView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public PieChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + @SuppressLint("ClickableViewAccessibility") + private void setup() { + drawable = new PieChartDrawable(); + setImageDrawable(drawable); + } + + /** + * Set array od names, array of values and array of colors. + */ + public void setData(float[] dataValues) { + drawable.dataValues = dataValues; + drawable.valueSum = 0; + for (float datum : dataValues) { + drawable.valueSum += datum; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = getMeasuredWidth(); + setMeasuredDimension(width, width / 2); + } + + private static class PieChartDrawable extends Drawable { + private static final float MIN_DEGREES = 10f; + private static final float PADDING_DEGREES = 3f; + private static final float STROKE_SIZE = 15f; + private static final int[] COLOR_VALUES = new int[]{0xFF3775E6, 0xffe51c23, 0xffff9800, 0xff259b24, 0xff9c27b0, + 0xff0099c6, 0xffdd4477, 0xff66aa00, 0xffb82e2e, 0xff316395, + 0xff994499, 0xff22aa99, 0xffaaaa11, 0xff6633cc, 0xff0073e6}; + private float[] dataValues; + private float valueSum; + private final Paint paint; + + private PieChartDrawable() { + paint = new Paint(); + paint.setFlags(Paint.ANTI_ALIAS_FLAG); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeJoin(Paint.Join.ROUND); + paint.setStrokeCap(Paint.Cap.ROUND); + paint.setStrokeWidth(STROKE_SIZE); + } + + @Override + public void draw(@NonNull Canvas canvas) { + if (valueSum == 0) { + return; + } + float radius = getBounds().height() - STROKE_SIZE; + float center = getBounds().width() / 2.f; + RectF arcBounds = new RectF(center - radius, STROKE_SIZE, center + radius, STROKE_SIZE + radius * 2); + + float startAngle = 180; + for (int i = 0; i < dataValues.length; i++) { + float datum = dataValues[i]; + float sweepAngle = (180f - PADDING_DEGREES) * (datum / valueSum); + if (sweepAngle < MIN_DEGREES) { + break; + } + paint.setColor(COLOR_VALUES[i % COLOR_VALUES.length]); + float padding = i == 0 ? PADDING_DEGREES / 2 : PADDING_DEGREES; + canvas.drawArc(arcBounds, startAngle + padding, sweepAngle - padding, false, paint); + startAngle = startAngle + sweepAngle; + } + + paint.setColor(Color.GRAY); + float sweepAngle = 360 - startAngle - PADDING_DEGREES / 2; + if (sweepAngle > PADDING_DEGREES) { + canvas.drawArc(arcBounds, startAngle + PADDING_DEGREES, sweepAngle - PADDING_DEGREES, false, paint); + } + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/SimpleAdapterDataObserver.java b/app/src/main/java/de/danoeh/antennapod/view/SimpleAdapterDataObserver.java index 45c3a35bd..5bd335532 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/SimpleAdapterDataObserver.java +++ b/app/src/main/java/de/danoeh/antennapod/view/SimpleAdapterDataObserver.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.view; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; /** * AdapterDataObserver that relays all events to the method anythingChanged(). diff --git a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java index 7ce33e11f..f82309c4a 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java +++ b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.view; import android.content.Context; -import android.support.v7.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatImageView; import android.util.AttributeSet; /** diff --git a/app/src/main/java/de/danoeh/antennapod/viewmodel/FeedSettingsViewModel.java b/app/src/main/java/de/danoeh/antennapod/viewmodel/FeedSettingsViewModel.java deleted file mode 100644 index fe11a645c..000000000 --- a/app/src/main/java/de/danoeh/antennapod/viewmodel/FeedSettingsViewModel.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.danoeh.antennapod.viewmodel; - -import android.arch.lifecycle.ViewModel; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.storage.DBReader; -import io.reactivex.Maybe; - -public class FeedSettingsViewModel extends ViewModel { - private Feed feed; - - public Maybe<Feed> getFeed(long feedId) { - if (feed == null) { - return loadFeed(feedId); - } else { - return Maybe.just(feed); - } - } - - private Maybe<Feed> loadFeed(long feedId) { - return Maybe.create(emitter -> { - Feed feed = DBReader.getFeed(feedId); - if (feed != null) { - this.feed = feed; - emitter.onSuccess(feed); - } else { - emitter.onComplete(); - } - }); - } -} |