diff options
author | Jonas Kalderstam <jonas@cowboyprogrammer.org> | 2019-10-05 01:18:14 +0200 |
---|---|---|
committer | Jonas Kalderstam <jonas@cowboyprogrammer.org> | 2019-10-05 01:18:14 +0200 |
commit | 97aa36061186ae589b26e90939a32e83573368c2 (patch) | |
tree | da900ade55ada0976017046c8ba0b7dd49937f9e | |
parent | de78c0e31eeff0609fe5388204e6e39e8f60579b (diff) | |
parent | eb70b4e0fa48f72eef50b2ca55eb590ae5df5726 (diff) | |
download | AntennaPod-97aa36061186ae589b26e90939a32e83573368c2.zip |
Merge remote-tracking branch 'antennapod/develop' into per_feed_playbackspeed
397 files changed, 3719 insertions, 2865 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index 258340afc..b5903350f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,39 +1,74 @@ -version: 2 +version: 2.1 jobs: build: + parameters: + build-steps: + description: "Steps that will be executed for this build" + type: steps + default: [] docker: - image: circleci/android:api-28 - working_directory: ~/AntennaPod - environment: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms256m -Xmx1280m" - steps: - checkout - - restore_cache: keys: - v1-android-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - v1-android- - - run: - # To build release, we need to create a temporary keystore that can be used to sign the app - command: | - keytool -noprompt -genkey -v -keystore "app/keystore" -alias alias -storepass password -keypass password -keyalg RSA -validity 10 -dname "CN=antennapod.org, OU=dummy, O=dummy, L=dummy, S=dummy, C=US" - ./gradlew assembleRelease :core:testPlayReleaseUnitTest :app:assemblePlayDebugAndroidTest -PdisablePreDex - no_output_timeout: 1800 - - - store_artifacts: - path: app/build/outputs/apk - destination: apks - + name: Create temporary release keystore + command: keytool -noprompt -genkey -v -keystore "app/keystore" -alias alias -storepass password -keypass password -keyalg RSA -validity 10 -dname "CN=antennapod.org, OU=dummy, O=dummy, L=dummy, S=dummy, C=US" + - steps: << parameters.build-steps >> - save_cache: paths: - ~/.android - ~/.gradle - ~/android key: v1-android-{{ checksum "build.gradle" }} + +workflows: + unit-tests: + jobs: + - build: + name: Build debug + build-steps: + - run: + name: Build debug + command: ./gradlew assembleDebug -PdisablePreDex + - run: + name: Execute debug unit tests + command: ./gradlew :core:testPlayDebugUnitTest -PdisablePreDex + - build: + name: Build release + build-steps: + - run: + name: Build release + command: ./gradlew assembleRelease -PdisablePreDex + - run: + name: Execute release unit tests + command: ./gradlew :core:testPlayReleaseUnitTest -PdisablePreDex + - build: + name: Build integration tests + build-steps: + - run: + name: Build integration tests + command: ./gradlew :app:assemblePlayDebugAndroidTest -PdisablePreDex + - build: + name: Build free + build-steps: + - run: + name: Build free (for F-Droid) + command: ./gradlew assembleFreeRelease -PdisablePreDex -PfreeBuild + + static-analysis: + jobs: + - build: + name: Checkstyle + build-steps: + - run: + name: Checkstyle + command: ./gradlew checkstyle diff --git a/.gitignore b/.gitignore index 7e39550e9..b10f948bb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ build/ # Local configuration file (sdk path, etc) local.properties -gradle.properties .gradle build.xml diff --git a/.tx/config b/.tx/config index d7356e199..ea1c0ccef 100644 --- a/.tx/config +++ b/.tx/config @@ -24,7 +24,7 @@ trans.gl = core/src/main/res/values-gl-rES/strings.xml trans.he_IL = core/src/main/res/values-iw-rIL/strings.xml trans.hi_IN = core/src/main/res/values-hi-rIN/strings.xml trans.hu = core/src/main/res/values-hu/strings.xml -trans.id = core/src/main/res/values-id/strings.xml +trans.id = core/src/main/res/values-in/strings.xml trans.it_IT = core/src/main/res/values-it/strings.xml trans.is = core/src/main/res/values-is-rIS/strings.xml trans.ja = core/src/main/res/values-ja/strings.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e966b997..fb429f026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ Change Log ========== +Version 1.7.3 +------------- +* Display episode image on widget (by @brad) +* Added checkbox to keep queue sorted (by @damoasda) +* New UI for "Add podcast" screen (by @ByteHamster) +* Added batch editing to the queue (by @ByteHamster) +* Added option to adapt remaining time to playback speed (by @CedricCabessa) +* Removed broken Flattr integration (by @ByteHamster) +* Added filter to "All episodes" list (by @jhunnius) +* Tons of bug fixes and performance improvements + Version 1.7.2 ------------- * Added configurable behavior of the back button diff --git a/app/build.gradle b/app/build.gradle index 7545386a2..5f70d285f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,10 +18,10 @@ android { // "1.2.3-SNAPSHOT" -> 1020300 // "1.2.3-RC4" -> 1020304 // "1.2.3" -> 1020395 - versionCode 1070306 - versionName "1.7.3-RC6" + versionCode 1070396 + versionName "1.7.3b" testApplicationId "de.test.antennapod" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" generatedDensities = [] javaCompileOptions { @@ -131,18 +131,14 @@ dependencies { } else { System.out.println("app: free build hack, skipping some dependencies") } - implementation "com.android.support:support-v4:$supportVersion" - implementation "com.android.support:appcompat-v7:$supportVersion" - implementation "com.android.support:design:$supportVersion" - implementation "com.android.support:preference-v14:$supportVersion" - implementation "com.android.support:gridlayout-v7:$supportVersion" - implementation "com.android.support:recyclerview-v7:$supportVersion" - compileOnly 'com.google.android.wearable:wearable:2.2.0' - - // ViewModel and LiveData - implementation "android.arch.lifecycle:extensions:$lifecycle_version" - annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" - + implementation "androidx.appcompat:appcompat:1.1.0" + implementation "androidx.preference:preference:1.1.0" + implementation "androidx.gridlayout:gridlayout:1.0.0" + implementation "androidx.recyclerview:recyclerview:1.0.0" + implementation "androidx.media:media:1.1.0" + implementation "com.google.android.material:material:1.0.0" + annotationProcessor "androidx.annotation:annotation:1.1.0" + compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion" implementation "org.apache.commons:commons-lang3:$commonslangVersion" implementation "commons-io:commons-io:$commonsioVersion" implementation "org.jsoup:jsoup:$jsoupVersion" @@ -162,26 +158,21 @@ dependencies { transitive = true } implementation "com.yqritc:recyclerview-flexibledivider:$recyclerviewFlexibledividerVersion" - implementation("com.githang:viewpagerindicator:2.5.1@aar") { - exclude module: "support-v4" - } - + implementation "com.githang:viewpagerindicator:2.5.1@aar" implementation "com.github.shts:TriangleLabelView:$triangleLabelViewVersion" - implementation 'com.leinardi.android:speed-dial:1.0.2' // 1.0.2 uses support 27.1.1 ; newer versions use 28.0.0; - + implementation 'com.leinardi.android:speed-dial:3.0.0' implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion" - implementation 'com.github.mfietz:fyydlin:v0.4.2' implementation 'com.github.ByteHamster:SearchPreference:v1.3.0' - implementation "org.awaitility:awaitility:$awaitilityVersion" + androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion" androidTestImplementation 'com.nanohttpd:nanohttpd-webserver:2.1.1' androidTestImplementation "com.jayway.android.robotium:robotium-solo:$robotiumSoloVersion" - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.2' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" + androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" + androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' } if (project.hasProperty("antennaPodServiceAccountEmail")) { @@ -193,20 +184,14 @@ if (project.hasProperty("antennaPodServiceAccountEmail")) { } } -// about.html is templatized so that we can automatically insert -// our version string in to it at build time. -task filterAbout { - inputs.files files(["src/main/templates/about.html", - "src/main/AndroidManifest.xml"]) - outputs.file "src/main/assets/about.html" -} doLast { - copy { - from "src/main/templates/about.html" - into "src/main/assets" - filter(ReplaceTokens, tokens: [versionname: android.defaultConfig.versionName, - commit : "git rev-parse --short HEAD".execute().text, - year : new Date().format('yyyy')]) - } +task filterAbout(type: Copy) { + from "src/main/templates/about.html" + into "src/main/assets" + filter(ReplaceTokens, tokens: [ + versionname: android.defaultConfig.versionName, + commit : "git rev-parse --short HEAD".execute().text, + year : new Date().format('yyyy')]) + outputs.upToDateWhen { false } } task copyTextFiles(type: Copy) { @@ -216,6 +201,7 @@ task copyTextFiles(type: Copy) { rename { String fileName -> fileName + ".txt" } + outputs.upToDateWhen { false } } preBuild.dependsOn filterAbout, copyTextFiles diff --git a/app/proguard.cfg b/app/proguard.cfg index 958048ef3..5309e833d 100644 --- a/app/proguard.cfg +++ b/app/proguard.cfg @@ -66,13 +66,13 @@ # for retrolambda -dontwarn java.lang.invoke.* --keep class android.support.v4.** { *; } --keep interface android.support.v4.** { *; } --keep class !android.support.v7.internal.view.menu.**,android.support.v7.** {*;} --keep interface android.support.v7.** { *; } --keep class com.google.android.wearable.** { *; } --dontwarn android.support.v4.** --dontwarn android.support.v7.** +-dontwarn com.google.android.material.** +-keep class com.google.android.material.** { *; } + +-dontwarn androidx.** +-keep class androidx.** { *; } +-keep interface androidx.** { *; } + -dontwarn com.google.android.wearable.** -keep class org.apache.commons.** { *; } diff --git a/app/src/androidTest/java/de/danoeh/antennapod/core/service/download/StubDownloader.java b/app/src/androidTest/java/de/danoeh/antennapod/core/service/download/StubDownloader.java new file mode 100644 index 000000000..e7fe079a6 --- /dev/null +++ b/app/src/androidTest/java/de/danoeh/antennapod/core/service/download/StubDownloader.java @@ -0,0 +1,52 @@ +package de.danoeh.antennapod.core.service.download; + +import androidx.annotation.NonNull; + +import de.danoeh.antennapod.core.util.Consumer; + +public class StubDownloader extends Downloader { + + private final long downloadTime; + + @NonNull + private final Consumer<DownloadStatus> onDownloadComplete; + + public StubDownloader(@NonNull DownloadRequest request, long downloadTime, @NonNull Consumer<DownloadStatus> onDownloadComplete) { + super(request); + this.downloadTime = downloadTime; + this.onDownloadComplete = onDownloadComplete; + } + + @Override + protected void download() { + try { + Thread.sleep(downloadTime); + } catch (Throwable t) { + t.printStackTrace(); + } + onDownloadComplete.accept(result); + } + + @NonNull + @Override + public DownloadRequest getDownloadRequest() { + return super.getDownloadRequest(); + } + + @NonNull + @Override + public DownloadStatus getResult() { + return super.getResult(); + } + + @Override + public boolean isFinished() { + return super.isFinished(); + } + + @Override + public void cancel() { + super.cancel(); + result.setCancelled(); + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java index 9e86275fc..f895b4d5e 100644 --- a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java @@ -1,33 +1,31 @@ package de.test.antennapod; import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.StringRes; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.PerformException; -import android.support.test.espresso.UiController; -import android.support.test.espresso.ViewAction; -import android.support.test.espresso.contrib.DrawerActions; -import android.support.test.espresso.contrib.RecyclerViewActions; -import android.support.test.espresso.util.HumanReadables; -import android.support.test.espresso.util.TreeIterables; +import androidx.annotation.StringRes; +import androidx.test.InstrumentationRegistry; +import androidx.test.espresso.PerformException; +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; +import androidx.test.espresso.contrib.DrawerActions; +import androidx.test.espresso.contrib.RecyclerViewActions; +import androidx.test.espresso.util.HumanReadables; +import androidx.test.espresso.util.TreeIterables; import android.view.View; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.dialog.RatingDialog; -import de.danoeh.antennapod.fragment.QueueFragment; import org.hamcrest.Matcher; import java.io.File; import java.util.concurrent.TimeoutException; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; -import static android.support.test.espresso.matcher.ViewMatchers.isRoot; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; public class EspressoTestUtils { /** @@ -115,7 +113,7 @@ public class EspressoTestUtils { } public static void clickPreference(@StringRes int title) { - onView(withId(R.id.list)).perform( + onView(withId(R.id.recycler_view)).perform( RecyclerViewActions.actionOnItem(hasDescendant(withText(title)), click())); } diff --git a/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java b/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java index e8a87ecf1..b3c79367f 100644 --- a/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java +++ b/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java @@ -4,9 +4,9 @@ import android.annotation.SuppressLint; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.util.playback.ExternalMedia; import org.junit.After; diff --git a/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java b/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java index 8cd9b0fa1..4b81a4f2b 100644 --- a/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java @@ -1,6 +1,6 @@ package de.test.antennapod.feed; -import android.support.test.filters.SmallTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.FeedFilter; import de.danoeh.antennapod.core.feed.FeedItem; import org.junit.Test; diff --git a/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java b/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java index 9779b32a5..0b9a67d0a 100644 --- a/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java +++ b/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java @@ -1,6 +1,6 @@ package de.test.antennapod.feed; -import android.support.test.filters.SmallTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.FeedItem; import org.junit.Test; diff --git a/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java b/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java index 91e31e73c..cd3847f17 100644 --- a/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java +++ b/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; import de.danoeh.antennapod.core.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice; diff --git a/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java b/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java index dfb78d5a9..4f6bf0c1f 100644 --- a/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java @@ -1,8 +1,8 @@ package de.test.antennapod.handler; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -12,7 +12,6 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.nio.file.Files; import java.util.ArrayList; import java.util.Date; import java.util.List; diff --git a/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java b/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java new file mode 100644 index 000000000..cb099cc85 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java @@ -0,0 +1,127 @@ +package de.test.antennapod.service.download; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.awaitility.Awaitility; +import org.awaitility.core.ConditionTimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +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.service.download.DownloadRequest; +import de.danoeh.antennapod.core.service.download.DownloadService; +import de.danoeh.antennapod.core.service.download.DownloadStatus; +import de.danoeh.antennapod.core.service.download.Downloader; +import de.danoeh.antennapod.core.service.download.StubDownloader; +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.Consumer; + +import static de.test.antennapod.util.event.FeedItemEventListener.withFeedItemEventListener; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @see HttpDownloaderTest for the test of actual download (and saving the file) + */ +@RunWith(AndroidJUnit4.class) +public class DownloadServiceTest { + + private CountDownLatch latch = null; + private Feed testFeed = null; + private FeedMedia testMedia11 = null; + + private DownloadService.DownloaderFactory origFactory = null; + + @Before + public void setUp() throws Exception { + origFactory = DownloadService.getDownloaderFactory(); + testFeed = setUpTestFeeds(); + testMedia11 = testFeed.getItemAtIndex(0).getMedia(); + } + + private Feed setUpTestFeeds() throws Exception { + Feed feed = new Feed("url", null, "Test Feed title 1"); + List<FeedItem> items = new ArrayList<>(); + feed.setItems(items); + FeedItem item1 = new FeedItem(0, "Item 1-1", "Item 1-1", "url", new Date(), FeedItem.NEW, feed); + items.add(item1); + FeedMedia media1 = new FeedMedia(0, item1, 123, 1, 1, "audio/mp3", null, "http://example.com/episode.mp3", false, null, 0, 0); + item1.setMedia(media1); + + DBWriter.setFeedItem(item1).get(); + return feed; + } + + + @After + public void tearDown() throws Exception { + DownloadService.setDownloaderFactory(origFactory); + } + + @Test + public void testEventsGeneratedCaseMediaDownloadSuccess() throws Exception { + // create a stub download that returns successful + // + // OPEN: Ideally, I'd like the download time long enough so that multiple in-progress DownloadEvents + // are generated (to simulate typical download), but it'll make download time quite long (1-2 seconds) + // to do so + DownloadService.setDownloaderFactory(new StubDownloaderFactory(50, downloadStatus -> { + downloadStatus.setSuccessful(); + })); + + withFeedItemEventListener(feedItemEventListener -> { + try { + assertEquals(0, feedItemEventListener.getEvents().size()); + assertFalse("The media in test should not yet been downloaded", + DBReader.getFeedMedia(testMedia11.getId()).isDownloaded()); + + DownloadRequester.getInstance().downloadMedia(InstrumentationRegistry.getTargetContext(), + testMedia11); + Awaitility.await() + .atMost(1000, TimeUnit.MILLISECONDS) + .until(() -> feedItemEventListener.getEvents().size() > 0); + assertTrue("After media download has completed, FeedMedia object in db should indicate so.", + DBReader.getFeedMedia(testMedia11.getId()).isDownloaded()); + } catch (ConditionTimeoutException cte) { + fail("The expected FeedItemEvent (for media download complete) has not been posted. " + + cte.getMessage()); + } + }); + } + + private static class StubDownloaderFactory implements DownloadService.DownloaderFactory { + private final long downloadTime; + + @NonNull + private final Consumer<DownloadStatus> onDownloadComplete; + + StubDownloaderFactory(long downloadTime, @NonNull Consumer<DownloadStatus> onDownloadComplete) { + this.downloadTime = downloadTime; + this.onDownloadComplete = onDownloadComplete; + } + + @Nullable + @Override + public Downloader create(@NonNull DownloadRequest request) { + return new StubDownloader(request, downloadTime, onDownloadComplete); + } + } + +} diff --git a/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java b/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java index 967bf431c..4530b7679 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java @@ -1,7 +1,7 @@ package de.test.antennapod.service.download; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; import android.util.Log; import java.io.File; diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java b/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java index 085e2c479..6bd519228 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java @@ -1,6 +1,6 @@ package de.test.antennapod.service.playback; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.core.util.playback.Playable; diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java b/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java index 113239908..5a16ab954 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java @@ -1,7 +1,7 @@ package de.test.antennapod.service.playback; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.core.util.playback.Playable; diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java index 502115cc3..c43818a73 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java @@ -2,8 +2,8 @@ package de.test.antennapod.service.playback; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.MediumTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; import de.test.antennapod.EspressoTestUtils; import junit.framework.AssertionFailedError; @@ -32,7 +32,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java index 9c0e90929..415ee9b68 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java @@ -1,9 +1,15 @@ package de.test.antennapod.service.playback; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.annotation.UiThreadTest; -import android.support.test.filters.LargeTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.annotation.UiThreadTest; +import androidx.test.filters.LargeTest; + +import org.awaitility.Awaitility; +import org.greenrobot.eventbus.EventBus; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import java.util.ArrayList; import java.util.Date; @@ -15,14 +21,14 @@ 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.service.playback.PlaybackServiceTaskManager; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.playback.Playable; -import org.greenrobot.eventbus.EventBus; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import static de.test.antennapod.util.event.FeedItemEventListener.withFeedItemEventListener; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -124,6 +130,43 @@ public class PlaybackServiceTaskManagerTest { } @Test + public void testQueueUpdatedUponDownloadComplete() throws Exception { + final Context c = InstrumentationRegistry.getInstrumentation().getTargetContext(); + { // Setup test data + List<FeedItem> queue = writeTestQueue("a"); + FeedItem item = DBReader.getFeedItem(queue.get(0).getId()); + FeedMedia media = new FeedMedia(item, "http://example.com/episode.mp3", 12345, "audio/mp3"); + item.setMedia(media); + DBWriter.setFeedMedia(media).get(); + DBWriter.setFeedItem(item).get(); + } + + PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM); + final FeedItem testItem = pstm.getQueue().get(0); + assertFalse("The item should not yet be downloaded", testItem.getMedia().isDownloaded()); + + withFeedItemEventListener( feedItemEventListener -> { + // simulate download complete (in DownloadService.MediaHandlerThread) + FeedItem item = DBReader.getFeedItem(testItem.getId()); + item.getMedia().setDownloaded(true); + item.getMedia().setFile_url("file://123"); + item.setAutoDownload(false); + DBWriter.setFeedMedia(item.getMedia()).get(); + DBWriter.setFeedItem(item).get(); + + Awaitility.await() + .atMost(1000, TimeUnit.MILLISECONDS) + .until(() -> feedItemEventListener.getEvents().size() > 0); + + final FeedItem itemUpdated = pstm.getQueue().get(0); + assertTrue("media.isDownloaded() should be true - The queue in PlaybackService should be updated after download is completed", + itemUpdated.getMedia().isDownloaded()); + }); + + pstm.shutdown(); + } + + @Test public void testStartPositionSaver() throws InterruptedException { final Context c = InstrumentationRegistry.getInstrumentation().getTargetContext(); final int NUM_COUNTDOWNS = 2; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java index e78894e59..1acb96d01 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java @@ -10,9 +10,9 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java index fe5091eb9..24cc80061 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java @@ -10,9 +10,9 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java index 3320aa545..de810c701 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java @@ -5,7 +5,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import android.support.test.filters.SmallTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java index d99a409d3..647ab911f 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java @@ -7,9 +7,9 @@ import java.util.Date; import java.util.List; import java.util.Random; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java index 6af12315c..55ea16f13 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java @@ -7,9 +7,9 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java index 7663bb3c9..42f4413d3 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java @@ -4,9 +4,9 @@ import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.preference.PreferenceManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.MediumTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.MediumTest; import android.util.Log; import org.awaitility.Awaitility; @@ -33,7 +33,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java index 0c65bb2e6..9f16888fa 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java @@ -1,20 +1,16 @@ package de.test.antennapod.ui; import android.app.Activity; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.intent.rule.IntentsTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.InstrumentationRegistry; +import androidx.test.espresso.intent.rule.IntentsTestRule; +import androidx.test.runner.AndroidJUnit4; import com.robotium.solo.Solo; import com.robotium.solo.Timeout; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.storage.PodDBAdapter; -import de.danoeh.antennapod.dialog.RatingDialog; import de.test.antennapod.EspressoTestUtils; import org.junit.After; import org.junit.Before; @@ -24,11 +20,11 @@ import org.junit.runner.RunWith; import java.io.IOException; -import static android.support.test.InstrumentationRegistry.getInstrumentation; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.contrib.ActivityResultMatchers.hasResultCode; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.contrib.ActivityResultMatchers.hasResultCode; +import static androidx.test.espresso.matcher.ViewMatchers.withText; import static de.test.antennapod.EspressoTestUtils.clickPreference; import static de.test.antennapod.EspressoTestUtils.openNavDrawer; import static junit.framework.TestCase.assertTrue; diff --git a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java index 548843636..360b55ce6 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java @@ -1,21 +1,17 @@ package de.test.antennapod.ui; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.ViewInteraction; -import android.support.test.espresso.contrib.DrawerActions; -import android.support.test.espresso.intent.rule.IntentsTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.InstrumentationRegistry; +import androidx.test.espresso.ViewInteraction; +import androidx.test.espresso.contrib.DrawerActions; +import androidx.test.espresso.intent.rule.IntentsTestRule; +import androidx.test.runner.AndroidJUnit4; import android.view.View; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.PodDBAdapter; -import de.danoeh.antennapod.dialog.RatingDialog; import de.danoeh.antennapod.fragment.DownloadsFragment; import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; @@ -33,15 +29,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.action.ViewActions.longClick; -import static android.support.test.espresso.action.ViewActions.scrollTo; -import static android.support.test.espresso.intent.Intents.intended; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; -import static android.support.test.espresso.matcher.ViewMatchers.isRoot; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.longClick; +import static androidx.test.espresso.action.ViewActions.scrollTo; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; import static de.test.antennapod.EspressoTestUtils.waitForView; import static de.test.antennapod.NthMatcher.first; import static junit.framework.TestCase.assertTrue; diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java index 22959917d..5b3530ea8 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java @@ -4,9 +4,9 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.rule.ActivityTestRule; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; import android.view.View; import android.widget.ListView; @@ -23,14 +23,13 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.test.antennapod.EspressoTestUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java index b302bcc91..eb5de03c4 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java @@ -4,9 +4,9 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.rule.ActivityTestRule; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; import android.view.View; import android.widget.ListView; diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index 1063ac90a..b46af83c8 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -4,9 +4,9 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.preference.PreferenceManager; -import android.support.test.filters.LargeTest; +import androidx.test.filters.LargeTest; -import android.support.test.rule.ActivityTestRule; +import androidx.test.rule.ActivityTestRule; import com.robotium.solo.Solo; import com.robotium.solo.Timeout; @@ -29,12 +29,12 @@ import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; -import static android.support.test.InstrumentationRegistry.getInstrumentation; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withText; import static de.test.antennapod.EspressoTestUtils.clickPreference; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; diff --git a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java index d1023dd9e..7f0bf8fa2 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java @@ -1,9 +1,9 @@ package de.test.antennapod.ui; import android.content.Intent; -import android.support.test.espresso.Espresso; -import android.support.test.espresso.intent.rule.IntentsTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.espresso.Espresso; +import androidx.test.espresso.intent.rule.IntentsTestRule; +import androidx.test.runner.AndroidJUnit4; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.fragment.QueueFragment; @@ -13,10 +13,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; +import static androidx.test.espresso.matcher.ViewMatchers.withText; /** * User interface tests for queue fragment diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java index 7555bb69a..a183b648e 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java @@ -5,9 +5,9 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.List; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.MediumTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.MediumTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import org.junit.After; diff --git a/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java index c4dd032d7..ab16d7603 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java @@ -1,8 +1,8 @@ package de.test.antennapod.ui; import android.content.Intent; -import android.support.test.filters.MediumTest; -import android.support.test.rule.ActivityTestRule; +import androidx.test.filters.MediumTest; +import androidx.test.rule.ActivityTestRule; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.VideoplayerActivity; @@ -10,10 +10,10 @@ import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; /** * Test class for VideoplayerActivity diff --git a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java index ac98d2802..f2dfca92e 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java @@ -1,8 +1,8 @@ package de.test.antennapod.util; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.filters.SmallTest; import android.text.TextUtils; import java.io.File; diff --git a/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java b/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java index b96cb273b..5bc2f7bd8 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java @@ -1,6 +1,6 @@ package de.test.antennapod.util; -import android.support.test.filters.SmallTest; +import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.util.URLChecker; import org.junit.Test; diff --git a/app/src/androidTest/java/de/test/antennapod/util/event/FeedItemEventListener.java b/app/src/androidTest/java/de/test/antennapod/util/event/FeedItemEventListener.java new file mode 100644 index 000000000..601bba853 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/util/event/FeedItemEventListener.java @@ -0,0 +1,46 @@ +package de.test.antennapod.util.event; + +import androidx.annotation.NonNull; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; + +import java.util.ArrayList; +import java.util.List; + +import de.danoeh.antennapod.core.event.FeedItemEvent; +import io.reactivex.functions.Consumer; + +/** + * Test helpers to listen {@link FeedItemEvent} and handle them accordingly + * + */ +public class FeedItemEventListener { + private final List<FeedItemEvent> events = new ArrayList<>(); + + /** + * Provides an listener subscribing to {@link FeedItemEvent} that the callers can use + * + * Note: it uses RxJava's version of {@link Consumer} because it allows exceptions to be thrown. + */ + public static void withFeedItemEventListener(@NonNull Consumer<FeedItemEventListener> consumer) + throws Exception { + FeedItemEventListener feedItemEventListener = new FeedItemEventListener(); + try { + EventBus.getDefault().register(feedItemEventListener); + consumer.accept(feedItemEventListener); + } finally { + EventBus.getDefault().unregister(feedItemEventListener); + } + } + + @Subscribe + public void onEvent(FeedItemEvent event) { + events.add(event); + } + + @NonNull + public List<? extends FeedItemEvent> getEvents() { + return events; + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index df69859a0..c42695a57 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -2,8 +2,8 @@ package de.test.antennapod.util.playback; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; diff --git a/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java b/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java index 3d8417bf6..4158fd31c 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java +++ b/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java @@ -72,20 +72,6 @@ public class HTTPBin extends NanoHTTPD { return servedFiles.size() - 1; } - /** - * Removes the file with the given ID from the server. - * - * @return True if a file was removed, false otherwise - */ - public synchronized boolean removeFile(int id) { - if (id < 0) throw new IllegalArgumentException("ID < 0"); - if (id >= servedFiles.size()) { - return false; - } else { - return servedFiles.remove(id) != null; - } - } - public synchronized File accessFile(int id) { if (id < 0 || id >= servedFiles.size()) { return null; diff --git a/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java b/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java index d4837ef60..2dda77524 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java @@ -1,12 +1,11 @@ package de.test.antennapod.util.syndication; -import android.support.test.InstrumentationRegistry; +import androidx.test.InstrumentationRegistry; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.FileOutputStream; -import java.nio.file.Files; import java.util.Map; import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer; diff --git a/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java index 79d7e02f2..aff1d6ea4 100644 --- a/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java +++ b/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java @@ -1,218 +1,15 @@ package de.danoeh.antennapod.activity; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; /** * Activity that allows for showing the MediaRouter button whenever there's a cast device in the * network. */ public abstract class CastEnabledActivity extends AppCompatActivity { -// implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String TAG = "CastEnabledActivity"; -// protected CastManager castManager; -// protected SwitchableMediaRouteActionProvider mediaRouteActionProvider; -// private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager(); -// -// @Override -// protected void onCreate(Bundle savedInstanceState) { -// super.onCreate(savedInstanceState); -// -// PreferenceManager.getDefaultSharedPreferences(getApplicationContext()). -// registerOnSharedPreferenceChangeListener(this); -// -// castManager = CastManager.getInstance(); -// castManager.addCastConsumer(castConsumer); -// castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled()); -// onCastConnectionChanged(castManager.isConnected()); -// } -// -// @Override -// protected void onDestroy() { -// PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) -// .unregisterOnSharedPreferenceChangeListener(this); -// castManager.removeCastConsumer(castConsumer); -// super.onDestroy(); -// } -// -// @Override -// @CallSuper -// public boolean onCreateOptionsMenu(Menu menu) { -// super.onCreateOptionsMenu(menu); -// getMenuInflater().inflate(R.menu.cast_enabled, menu); -// castButtonVisibilityManager.setMenu(menu); -// return true; -// } -// -// @Override -// @CallSuper -// public boolean onPrepareOptionsMenu(Menu menu) { -// super.onPrepareOptionsMenu(menu); -// mediaRouteActionProvider = castManager -// .addMediaRouterButton(menu.findItem(R.id.media_route_menu_item)); -// mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable()); -// return true; -// } -// -// @Override -// protected void onResume() { -// super.onResume(); -// castButtonVisibilityManager.setResumed(true); -// } -// -// @Override -// protected void onPause() { -// super.onPause(); -// castButtonVisibilityManager.setResumed(false); -// } -// -// @Override -// public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { -// if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { -// boolean newValue = UserPreferences.isCastEnabled(); -// Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue); -// castButtonVisibilityManager.setPrefEnabled(newValue); -// // PlaybackService has its own listener, so if it's active we don't have to take action here. -// if (!newValue && !PlaybackService.isRunning) { -// CastManager.getInstance().disconnect(); -// } -// } -// } -// -// CastConsumer castConsumer = new DefaultCastConsumer() { -// @Override -// public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { -// onCastConnectionChanged(true); -// } -// -// @Override -// public void onDisconnected() { -// onCastConnectionChanged(false); -// } -// }; -// -// private void onCastConnectionChanged(boolean connected) { -// if (connected) { -// castButtonVisibilityManager.onConnected(); -// setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE); -// } else { -// castButtonVisibilityManager.onDisconnected(); -// setVolumeControlStream(AudioManager.STREAM_MUSIC); -// } -// } -// -// /** -// * Should be called by any activity or fragment for which the cast button should be shown. -// * -// * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)} -// */ public final void requestCastButton(int showAsAction) { // no-op } - -// private class CastButtonVisibilityManager { -// private volatile boolean prefEnabled = false; -// private volatile boolean viewRequested = false; -// private volatile boolean resumed = false; -// private volatile boolean connected = false; -// private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM; -// private Menu menu; -// -// public synchronized void setPrefEnabled(boolean newValue) { -// if (prefEnabled != newValue && resumed && (viewRequested || connected)) { -// if (newValue) { -// castManager.incrementUiCounter(); -// } else { -// castManager.decrementUiCounter(); -// } -// } -// prefEnabled = newValue; -// if (mediaRouteActionProvider != null) { -// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); -// } -// } -// -// public synchronized void setResumed(boolean newValue) { -// if (resumed == newValue) { -// Log.e(TAG, "resumed should never change to the same value"); -// return; -// } -// resumed = newValue; -// if (prefEnabled && (viewRequested || connected)) { -// if (resumed) { -// castManager.incrementUiCounter(); -// } else { -// castManager.decrementUiCounter(); -// } -// } -// } -// -// public synchronized void setViewRequested(boolean newValue) { -// if (viewRequested != newValue && resumed && prefEnabled && !connected) { -// if (newValue) { -// castManager.incrementUiCounter(); -// } else { -// castManager.decrementUiCounter(); -// } -// } -// viewRequested = newValue; -// if (mediaRouteActionProvider != null) { -// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); -// } -// } -// -// public synchronized void setConnected(boolean newValue) { -// if (connected != newValue && resumed && prefEnabled && !prefEnabled) { -// if (newValue) { -// castManager.incrementUiCounter(); -// } else { -// castManager.decrementUiCounter(); -// } -// } -// connected = newValue; -// if (mediaRouteActionProvider != null) { -// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); -// } -// } -// -// public synchronized boolean shouldEnable() { -// return prefEnabled && viewRequested; -// } -// -// public void setMenu(Menu menu) { -// setViewRequested(false); -// showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM; -// this.menu = menu; -// setShowAsAction(); -// } -// -// public void requestCastButton(int showAsAction) { -// setViewRequested(true); -// this.showAsAction = showAsAction; -// setShowAsAction(); -// } -// -// public void onConnected() { -// setConnected(true); -// setShowAsAction(); -// } -// -// public void onDisconnected() { -// setConnected(false); -// setShowAsAction(); -// } -// -// private void setShowAsAction() { -// if (menu == null) { -// Log.d(TAG, "setShowAsAction() without a menu"); -// return; -// } -// MenuItem item = menu.findItem(R.id.media_route_menu_item); -// if (item == null) { -// Log.e(TAG, "setShowAsAction(), but cast button not inflated"); -// return; -// } -// MenuItemCompat.setShowAsAction(item, connected? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction); -// } -// } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 73af654e9..488d4e473 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,7 +36,7 @@ android:usesCleartextTraffic="true" android:logo="@mipmap/ic_launcher"> <meta-data android:name="com.google.android.gms.car.notification.SmallIcon" - android:resource="@drawable/ic_notification" /> + android:resource="@drawable/ic_antenna" /> <meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/> @@ -105,17 +105,6 @@ android:value="de.danoeh.antennapod.activity.MainActivity"/> </activity> - <activity - android:name=".activity.FeedInfoActivity" - android:label="@string/feed_info_label"> - </activity> - - <activity - android:name=".activity.FeedSettingsActivity" - android:windowSoftInputMode="stateHidden" - android:label="@string/feed_settings_label"> - </activity> - <service android:name=".core.service.PlayerWidgetJobService" android:permission="android.permission.BIND_JOB_SERVICE" @@ -151,13 +140,6 @@ android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> </activity> <activity - android:name=".activity.StatisticsActivity" - android:label="@string/statistics_label"> - <meta-data - android:name="android.support.PARENT_ACTIVITY" - android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> - </activity> - <activity android:name=".activity.ImportExportActivity" android:label="@string/import_export"> <meta-data @@ -209,6 +191,13 @@ android:name=".activity.OpmlFeedChooserActivity" android:label="@string/opml_import_label"> </activity> + <activity + android:name=".activity.BugReportActivity" + android:label="@string/bug_report_title"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> + </activity> <activity android:name=".activity.VideoplayerActivity" @@ -350,7 +339,7 @@ <provider android:authorities="@string/provider_authority" - android:name="android.support.v4.content.FileProvider" + android:name="androidx.core.content.FileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data 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..94d281a45 100644 --- a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java +++ b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java @@ -63,6 +63,8 @@ public class PodcastApp extends Application { 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 index 1bcdada44..ef7ea2b16 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java @@ -1,12 +1,10 @@ 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 androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -15,6 +13,7 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.LinearLayout; +import de.danoeh.antennapod.core.util.IntentUtils; import org.apache.commons.io.IOUtils; import java.io.IOException; @@ -57,8 +56,7 @@ public class AboutActivity extends AppCompatActivity { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("http")) { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent); + IntentUtils.openInBrowser(AboutActivity.this, url); return true; } else { url = url.replace("file:///android_asset/", ""); 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 8e063374a..7c26cc484 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; @@ -62,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 @@ -76,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(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 @@ -142,6 +145,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..6a38f8f0a 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,11 @@ import android.widget.Toast; import com.bumptech.glide.Glide; -import de.danoeh.antennapod.preferences.PreferenceUpgrader; 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,7 +47,6 @@ 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; @@ -66,14 +69,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. @@ -93,7 +95,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 +129,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 +250,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi SharedPreferences.Editor edit = prefs.edit(); edit.putBoolean(PREF_IS_FIRST_LAUNCH, false); - edit.commit(); + edit.apply(); } } @@ -368,15 +378,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(); } @@ -778,25 +807,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); 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 a907c738a..7bfdc6fc6 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,13 @@ 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.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; @@ -34,6 +33,7 @@ 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,9 @@ 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.core.feed.FeedPreferences.SPEED_USE_GLOBAL; @@ -196,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; } @@ -276,6 +284,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements controller.init(); loadMediaInfo(); onPositionObserverUpdate(); + EventBus.getDefault().register(this); } @Override @@ -288,6 +297,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements if (disposable != null) { disposable.dispose(); } + EventBus.getDefault().unregister(this); super.onStop(); } @@ -324,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); @@ -391,29 +403,24 @@ 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: @@ -450,28 +457,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: @@ -637,7 +649,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); @@ -815,11 +827,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; } @@ -874,4 +882,13 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements } } } + + @Nullable + private static FeedItem getFeedItem(@Nullable Playable playable) { + if ((playable != null) && (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..016168b45 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; @@ -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; @@ -120,7 +120,6 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem disposable.dispose(); } EventDistributor.getInstance().unregister(contentUpdate); - EventBus.getDefault().unregister(this); saveCurrentFragment(); } @@ -173,7 +172,6 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem protected void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().register(this); loadData(); } @@ -258,6 +256,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); 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..39715495a 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,12 @@ 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.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -32,7 +32,6 @@ import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; -import de.danoeh.antennapod.core.glide.FastBlurTransformation; import org.apache.commons.lang3.StringUtils; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -55,6 +54,7 @@ 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; @@ -442,11 +442,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { 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); + Intent intent = MainActivity.getIntentToOpenFeed(this, getFeedId(feed)); startActivity(intent); } else { Feed f = new Feed(selectedDownloadUrl, null, feed.getTitle()); 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..9caff0fc0 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java @@ -5,9 +5,9 @@ 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.core.app.ActivityCompat; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import com.afollestad.materialdialogs.MaterialDialog; 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/StorageErrorActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java index 20e34cc52..c9c9a0e2c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java @@ -9,9 +9,9 @@ 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; 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..2d28ea561 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; 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..5b735cd1f 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,10 +24,12 @@ 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; @@ -50,6 +53,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR private final boolean showOnlyNewEpisodes; private FeedItem selectedItem; + private Holder currentlyPlayingItem = null; private final int playingBackGroundColor; private final int normalBackGroundColor; @@ -165,8 +169,9 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR holder.progress.setVisibility(View.INVISIBLE); } - if(media.isCurrentlyPlaying()) { + if (media.isCurrentlyPlaying()) { holder.container.setBackgroundColor(playingBackGroundColor); + currentlyPlayingItem = holder; } else { holder.container.setBackgroundColor(normalBackGroundColor); } @@ -196,6 +201,22 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR .load(); } + @Override + public void onBindViewHolder(@NonNull Holder holder, int pos, List<Object> payload) { + onBindViewHolder(holder, pos); + + if (holder == currentlyPlayingItem && payload.size() == 1 && payload.get(0) instanceof PlaybackPositionEvent) { + PlaybackPositionEvent event = (PlaybackPositionEvent) payload.get(0); + holder.progress.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); + } + } + + public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event) { + if (currentlyPlayingItem != null && currentlyPlayingItem.getAdapterPosition() != RecyclerView.NO_POSITION) { + notifyItemChanged(currentlyPlayingItem.getAdapterPosition(), event); + } + } + @Nullable public FeedItem getSelectedItem() { return selectedItem; @@ -262,7 +283,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 +298,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR item1.setVisible(visible); } }; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null); - - contextMenuInterface.setItemVisibility(R.id.remove_new_flag_item, item.isNew()); + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item); } } 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..9014de525 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,9 @@ 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 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; 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..b8764c2ae 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,7 @@ package de.danoeh.antennapod.adapter; import android.content.Context; import android.os.Build; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; import android.text.Layout; import android.text.format.DateUtils; import android.util.Log; 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..fcdcb4ba6 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,12 @@ 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.NonNull; +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,9 +25,11 @@ import android.widget.TextView; import com.joanzapata.iconify.Iconify; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import org.apache.commons.lang3.ArrayUtils; import java.lang.ref.WeakReference; +import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; @@ -58,6 +60,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap private boolean locked; private FeedItem selectedItem; + private ViewHolder currentlyPlayingItem = null; private final int playingBackGroundColor; private final int normalBackGroundColor; @@ -94,6 +97,18 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap }); } + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int pos, List<Object> payload) { + onBindViewHolder(holder, pos); + + if (holder == currentlyPlayingItem && payload.size() == 1 && payload.get(0) instanceof PlaybackPositionEvent) { + PlaybackPositionEvent event = (PlaybackPositionEvent) payload.get(0); + holder.progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); + holder.progressLeft.setText(Converter.getDurationStringLong(event.getPosition())); + holder.progressRight.setText(Converter.getDurationStringLong(event.getDuration())); + } + } + @Nullable public FeedItem getSelectedItem() { return selectedItem; @@ -109,6 +124,12 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap return itemAccess.getCount(); } + public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event) { + if (currentlyPlayingItem != null && currentlyPlayingItem.getAdapterPosition() != RecyclerView.NO_POSITION) { + notifyItemChanged(currentlyPlayingItem.getAdapterPosition(), event); + } + } + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnCreateContextMenuListener, @@ -169,7 +190,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 +206,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 @@ -276,6 +309,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap if(media.isCurrentlyPlaying()) { container.setBackgroundColor(playingBackGroundColor); + currentlyPlayingItem = this; } else { container.setBackgroundColor(normalBackGroundColor); } 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..230b7ee31 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; 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..55ca5471b 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,9 +1,9 @@ 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 androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +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/ItemActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java index da5ebf6e1..31e9fccb5 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,9 +2,9 @@ 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; @@ -20,12 +20,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; 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/PlayActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayActionButton.java index 3992c7240..23a7e03ad 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; 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..4cfa7e870 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/ChooseDataFolderDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ChooseDataFolderDialog.java @@ -9,7 +9,7 @@ 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(); } 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..f6d08b7bf 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,16 @@ 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 androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.PluralsRes; +import androidx.annotation.StringRes; +import com.google.android.material.snackbar.Snackbar; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.collection.ArrayMap; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; 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/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index 3dc8abbbe..2f0caa982 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -2,8 +2,8 @@ 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.fragment.app.DialogFragment; import android.widget.Button; import android.widget.CheckBox; import android.widget.SeekBar; 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..0499d02f1 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,7 @@ 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.core.content.ContextCompat; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; 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..c49e9153e 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java @@ -2,11 +2,9 @@ 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; @@ -15,6 +13,7 @@ import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.util.IntentUtils; public class RatingDialog { @@ -59,14 +58,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(); } 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..1cf34b2b3 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -5,7 +5,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.util.Log; import android.view.View; 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..b7bfe3438 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; 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 4bebfe4c9..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; @@ -87,11 +87,10 @@ public class ChaptersFragment extends ListFragment { controller = null; } - private void scrollTo(int position) { - getListView().setSelection(position); - } - private int getCurrentChapter(Playable media) { + if (media == null || media.getChapters() == null || media.getChapters().size() == 0 || controller == null) { + return -1; + } int currentPosition = controller.getPosition(); List<Chapter> chapters = media.getChapters(); @@ -126,8 +125,10 @@ public class ChaptersFragment extends ListFragment { if (adapter != null) { adapter.setMedia(media); adapter.notifyDataSetChanged(); - if (media != null && media.getChapters() != null && media.getChapters().size() != 0) { - scrollTo(getCurrentChapter(media)); + + int positionOfCurrentChapter = getCurrentChapter(media); + if (positionOfCurrentChapter != -1) { + getListView().setSelection(positionOfCurrentChapter); } } } 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..5ab781fe3 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; 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..1917e4c75 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; 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..cf9ee6c41 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; 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..528c50747 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; 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..3fc67f795 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,17 @@ 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.PlaybackPositionEvent; +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; @@ -33,21 +40,16 @@ 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 +57,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 @@ -149,7 +144,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 +157,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(); } } @@ -202,10 +197,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 +254,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 +385,13 @@ public abstract class EpisodesListFragment extends Fragment { } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (listAdapter != null) { + listAdapter.notifyCurrentlyPlayingItemChanged(event); + } + } + protected boolean shouldUpdatedItemRemainInList(FeedItem item) { return true; } @@ -412,7 +401,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) { @@ -453,36 +442,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..bbc33c6ca 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,7 +18,7 @@ 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; @@ -28,6 +28,9 @@ 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 @@ -138,6 +141,7 @@ public class ExternalPlayerFragment extends Fragment { controller = setupPlaybackController(); controller.init(); loadMediaInfo(); + EventBus.getDefault().register(this); } @Override @@ -147,6 +151,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 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..5282a6bb2 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; 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..6b270e220 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -0,0 +1,234 @@ +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 = 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); + + 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 f8ef7f7a3..0c33dce5a 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,12 @@ 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 androidx.annotation.NonNull; +import androidx.fragment.app.ListFragment; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.widget.SearchView; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -17,7 +16,6 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ImageButton; import android.widget.ImageView; @@ -30,6 +28,7 @@ import com.bumptech.glide.request.RequestOptions; import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconTextView; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import org.apache.commons.lang3.Validate; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -38,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; @@ -141,44 +138,33 @@ public class FeedItemlistFragment extends ListFragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - ((ListView) view.findViewById(android.R.id.list)).setFastScrollEnabled(true); - return view; + public void onHiddenChanged(boolean hidden) { + super.onHiddenChanged(hidden); + if (!hidden && getActivity() != null) { + ((MainActivity) getActivity()).getSupportActionBar().setTitle(""); + } } @Override - public void onStart() { - super.onStart(); + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + registerForContextMenu(getListView()); + EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); loadItems(); } @Override - public void onResume() { - super.onResume(); - ((MainActivity)getActivity()).getSupportActionBar().setTitle(""); - updateProgressBarVisibility(); - } + public void onDestroyView() { + super.onDestroyView(); - @Override - public void onStop() { - super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); EventBus.getDefault().unregister(this); - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - resetViewState(); - } - - private void resetViewState() { adapter = null; listFooter = null; } @@ -331,7 +317,7 @@ public class FeedItemlistFragment extends ListFragment { contextMenu = menu; lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null); + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item); } @Override @@ -348,14 +334,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 @@ -398,14 +377,21 @@ 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(); } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (adapter != null) { + adapter.notifyCurrentlyPlayingItemChanged(event, getListView()); + } + } + private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override @@ -429,8 +415,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) { @@ -514,10 +501,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; @@ -525,10 +510,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); } } @@ -634,7 +617,7 @@ public class FeedItemlistFragment extends ListFragment { .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { feed = result.orElse(null); - onFragmentLoaded(); + displayList(); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } 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 8eb5ecd6b..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,46 +1,73 @@ 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(); @@ -52,11 +79,35 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { updateAutoDeleteSummary(); updateAutoDownloadEnabled(); updatePlaybackSpeedPreference(); - }).dispose(); + }, 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 = (ListPreference) findPreference(PREF_FEED_PLAYBACK_SPEED); + ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); String[] speeds = UserPreferences.getPlaybackSpeedArray(); @@ -130,14 +181,14 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { } private void updatePlaybackSpeedPreference() { - ListPreference feedPlaybackSpeedPreference = (ListPreference) findPreference(PREF_FEED_PLAYBACK_SPEED); + 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: 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..a3c3df340 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; 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..a97d60099 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; } @@ -159,11 +152,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..f17f8c645 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -6,10 +6,10 @@ import android.content.Intent; 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 androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.core.content.ContextCompat; +import androidx.core.view.GestureDetectorCompat; import android.text.Layout; import android.text.TextUtils; import android.util.Log; @@ -55,7 +55,6 @@ import de.danoeh.antennapod.core.feed.FeedMedia; 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; @@ -211,10 +210,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { 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; } }); @@ -336,10 +332,10 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { inflater.inflate(R.menu.feeditem_options, menu); popupMenu = menu; if (item.hasMedia()) { - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null); + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item); } else { // these are already available via button1 and button2 - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null, + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, R.id.mark_read_item, R.id.visit_website_item); } } @@ -351,7 +347,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { openPodcast(); return true; default: - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); + return FeedItemMenuHandler.onMenuItemClicked(this, menuItem.getItemId(), item); } } @@ -485,11 +481,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); 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..673b58901 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,10 @@ 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.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; @@ -23,29 +23,15 @@ 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 { @@ -167,7 +153,7 @@ public class ItunesSearchFragment extends Fragment { 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..5dbb84bc7 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 @@ -63,7 +63,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..f9fca87fc 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; 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 423e8b17b..dcb16d192 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,15 @@ 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 com.google.android.material.snackbar.Snackbar; +import androidx.fragment.app.Fragment; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.SearchView; +import androidx.recyclerview.widget.SimpleItemAnimator; +import androidx.recyclerview.widget.ItemTouchHelper; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -19,11 +20,17 @@ 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 com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + import java.util.List; import de.danoeh.antennapod.R; @@ -42,7 +49,6 @@ 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; @@ -50,18 +56,15 @@ 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.core.feed.FeedPreferences.SPEED_USE_GLOBAL; import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE; @@ -92,10 +95,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 @@ -103,6 +108,7 @@ public class QueueFragment extends Fragment { super.onCreate(savedInstanceState); setRetainInstance(true); setHasOptionsMenu(true); + prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); } @Override @@ -197,8 +203,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) { @@ -210,6 +216,13 @@ public class QueueFragment extends Fragment { } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (recyclerAdapter != null) { + recyclerAdapter.notifyCurrentlyPlayingItemChanged(event); + } + } + private void saveScrollPosition() { int firstItem = layoutManager.findFirstVisibleItemPosition(); View firstItemView = layoutManager.findViewByPosition(firstItem); @@ -220,15 +233,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) { @@ -300,25 +311,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 @@ -395,6 +391,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. * @@ -431,7 +469,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); } } @@ -614,7 +652,7 @@ public class QueueFragment extends Fragment { / playbackSpeed); } } - info += " \u2022 "; + info += " • "; info += getString(R.string.time_left_label); info += Converter.getDurationStringLocalized(getActivity(), timeLeft); } 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..226209740 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,7 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; 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..528fa7c32 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; 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..7f3c60e5d 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; 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..21c744727 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; @@ -25,19 +25,27 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.SubscriptionsAdapter; import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.feed.EventDistributor; 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 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 @@ -56,6 +64,7 @@ public class SubscriptionFragment extends Fragment { private SubscriptionsAdapter subscriptionAdapter; private int mPosition = -1; + private boolean isUpdatingFeeds = false; private Disposable disposable; private SharedPreferences prefs; @@ -89,6 +98,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 +108,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; @@ -136,6 +150,7 @@ public class SubscriptionFragment extends Fragment { public void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); + EventBus.getDefault().register(this); loadSubscriptions(); } @@ -143,6 +158,7 @@ public class SubscriptionFragment extends Fragment { public void onStop() { super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); + EventBus.getDefault().unregister(this); if(disposable != null) { disposable.dispose(); } @@ -278,6 +294,17 @@ public class SubscriptionFragment extends Fragment { } }; + @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 public int getCount() { 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..1d6debbfe 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; @@ -56,7 +56,7 @@ public abstract class PodcastListFragment extends Fragment { 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..60fc1f446 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; 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..b5a95bc33 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; 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..121b7fef8 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.auto_download_label); + } + + @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..00e69f1db 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,16 @@ 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 +20,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 +33,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); @@ -74,53 +69,25 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { ); 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..440660942 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,10 +4,10 @@ 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.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; import android.text.format.DateFormat; import com.afollestad.materialdialogs.MaterialDialog; import de.danoeh.antennapod.R; @@ -31,6 +31,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(); 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..1795dfc29 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,11 @@ 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.preference.ListPreference; +import androidx.preference.PreferenceFragmentCompat; 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,6 +29,12 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { } @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.playback_pref); + } + + @Override public void onResume() { super.onResume(); checkSonicItemVisibility(); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java index 37199ccf7..2b5310837 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java @@ -1,25 +1,27 @@ -package de.danoeh.antennapod.activity; +package de.danoeh.antennapod.fragment.preferences; +import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; 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.widget.AdapterView; -import android.widget.ListView; +import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.RadioButton; -import android.widget.TextView; - import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; 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; @@ -28,70 +30,63 @@ 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(); +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 TextView totalTimeTextView; - private ListView feedStatisticsList; + private RecyclerView feedStatisticsList; private ProgressBar progressBar; private StatisticsListAdapter listAdapter; private boolean countAll = false; private SharedPreferences prefs; @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayShowHomeEnabled(true); - setContentView(R.layout.statistics_activity); - - prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); + prefs = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); countAll = prefs.getBoolean(PREF_COUNT_ALL, false); + setHasOptionsMenu(true); + } - totalTimeTextView = findViewById(R.id.total_time); - feedStatisticsList = findViewById(R.id.statistics_list); - progressBar = findViewById(R.id.progressBar); - listAdapter = new StatisticsListAdapter(this); + @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); - feedStatisticsList.setOnItemClickListener(this); + return root; } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.statistics_label); refreshStatistics(); } @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuInflater inflater = getMenuInflater(); + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 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) { + if (item.getItemId() == R.id.statistics_mode) { selectStatisticsMode(); return true; - } else { - return super.onOptionsItemSelected(item); } + 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); + 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); @@ -113,7 +108,6 @@ public class StatisticsActivity extends AppCompatActivity private void refreshStatistics() { progressBar.setVisibility(View.VISIBLE); - totalTimeTextView.setVisibility(View.GONE); feedStatisticsList.setVisibility(View.GONE); loadStatistics(); } @@ -126,28 +120,9 @@ public class StatisticsActivity extends AppCompatActivity .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { - totalTimeTextView.setText(Converter - .shortLocalizedDuration(this, countAll ? result.totalTimeCountAll : result.totalTime)); - listAdapter.update(result.feedTime); + listAdapter.update(result); 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/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..fb2675db9 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,6 +154,9 @@ 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); @@ -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..5d012168e 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -2,25 +2,17 @@ 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 androidx.appcompat.app.AlertDialog; 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 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; @@ -86,14 +78,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); 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..4a57726b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java @@ -4,7 +4,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; -import android.support.v7.widget.SearchView; +import androidx.appcompat.widget.SearchView; import android.view.Menu; import android.view.MenuItem; import android.widget.EditText; 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 4d6fbcb7b..6392d0535 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,11 @@ 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.util.download.AutoUpdateManager; import de.danoeh.antennapod.core.util.gui.NotificationUtils; public class PreferenceUpgrader { @@ -21,7 +24,7 @@ public class PreferenceUpgrader { if (oldVersion != newVersion) { NotificationUtils.createChannels(context); - UserPreferences.restartUpdateAlarm(); + AutoUpdateManager.restartUpdateAlarm(); upgrade(oldVersion); upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply(); @@ -42,10 +45,8 @@ public class PreferenceUpgrader { } } if (oldVersion < 1070300) { - if (UserPreferences.getMediaPlayer().equals("builtin")) { - prefs.edit().putString(UserPreferences.PREF_MEDIA_PLAYER, - UserPreferences.PREF_MEDIA_PLAYER_EXOPLAYER).apply(); - } + prefs.edit().putString(UserPreferences.PREF_MEDIA_PLAYER, + UserPreferences.PREF_MEDIA_PLAYER_EXOPLAYER).apply(); if (prefs.getBoolean("prefEnableAutoDownloadOnMobile", false)) { UserPreferences.setAllowMobileAutoDownload(true); @@ -64,5 +65,13 @@ 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); + } } } 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/EmptyViewHandler.java b/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java index 8b886e699..1516c4eb6 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java @@ -2,9 +2,9 @@ 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 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;
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..ada5401d8 --- /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 = 180 * 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(); - } - }); - } -} diff --git a/app/src/main/res/anim/card_flip_left_in.xml b/app/src/main/res/anim/card_flip_left_in.xml new file mode 100644 index 000000000..0ffc85aec --- /dev/null +++ b/app/src/main/res/anim/card_flip_left_in.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Before rotating, immediately set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:duration="0" /> + + <!-- Rotate. --> + <objectAnimator + android:valueFrom="-180" + android:valueTo="0" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> + <objectAnimator + android:valueFrom="0.0" + android:valueTo="1.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set>
\ No newline at end of file diff --git a/app/src/main/res/anim/card_flip_left_out.xml b/app/src/main/res/anim/card_flip_left_out.xml new file mode 100644 index 000000000..817f0d3fc --- /dev/null +++ b/app/src/main/res/anim/card_flip_left_out.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Rotate. --> + <objectAnimator + android:valueFrom="0" + android:valueTo="180" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set>
\ No newline at end of file diff --git a/app/src/main/res/anim/card_flip_right_in.xml b/app/src/main/res/anim/card_flip_right_in.xml new file mode 100644 index 000000000..31ff1dde5 --- /dev/null +++ b/app/src/main/res/anim/card_flip_right_in.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Before rotating, immediately set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:duration="0" /> + + <!-- Rotate. --> + <objectAnimator + android:valueFrom="180" + android:valueTo="0" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> + <objectAnimator + android:valueFrom="0.0" + android:valueTo="1.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set>
\ No newline at end of file diff --git a/app/src/main/res/anim/card_flip_right_out.xml b/app/src/main/res/anim/card_flip_right_out.xml new file mode 100644 index 000000000..b57113fea --- /dev/null +++ b/app/src/main/res/anim/card_flip_right_out.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Rotate. --> + <objectAnimator + android:valueFrom="0" + android:valueTo="-180" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set>
\ No newline at end of file diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml index a7f7d9f12..99fa1c3f9 100644 --- a/app/src/main/res/layout/addfeed.xml +++ b/app/src/main/res/layout/addfeed.xml @@ -13,7 +13,7 @@ android:focusableInTouchMode="true" android:padding="8dp"> - <android.support.v7.widget.CardView + <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" app:cardCornerRadius="4dp" @@ -53,7 +53,7 @@ </LinearLayout> - </android.support.v7.widget.CardView> + </androidx.cardview.widget.CardView> <fragment android:id="@+id/quickFeedDiscovery" @@ -62,7 +62,7 @@ android:layout_height="wrap_content" android:layout_margin="8dp"/> - <android.support.v7.widget.CardView + <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" app:cardCornerRadius="4dp" @@ -102,9 +102,9 @@ </LinearLayout> - </android.support.v7.widget.CardView> + </androidx.cardview.widget.CardView> - <android.support.v7.widget.CardView + <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" app:cardCornerRadius="4dp" @@ -170,7 +170,7 @@ </LinearLayout> - </android.support.v7.widget.CardView> + </androidx.cardview.widget.CardView> </LinearLayout> diff --git a/app/src/main/res/layout/all_episodes_fragment.xml b/app/src/main/res/layout/all_episodes_fragment.xml index 53636c2b6..9160998ac 100644 --- a/app/src/main/res/layout/all_episodes_fragment.xml +++ b/app/src/main/res/layout/all_episodes_fragment.xml @@ -17,7 +17,7 @@ android:visibility="gone" tools:text="(i) Information" /> - <android.support.v7.widget.RecyclerView + <androidx.recyclerview.widget.RecyclerView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/app/src/main/res/layout/bug_report.xml b/app/src/main/res/layout/bug_report.xml new file mode 100644 index 000000000..e97e85265 --- /dev/null +++ b/app/src/main/res/layout/bug_report.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp"> + <Button + android:id="@+id/btn_open_bug_tracker" + android:text="@string/open_bug_tracker" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <Button + android:id="@+id/btn_copy_log" + android:text="@string/copy_to_clipboard" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <TextView + android:layout_marginTop="8dp" + android:id="@+id/crash_report_logs" + android:textIsSelectable="true" + android:textSize="12sp" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + +</LinearLayout> diff --git a/app/src/main/res/layout/checkbox_do_not_show_again.xml b/app/src/main/res/layout/checkbox_do_not_show_again.xml new file mode 100644 index 000000000..15f26e8b4 --- /dev/null +++ b/app/src/main/res/layout/checkbox_do_not_show_again.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="8dp" + android:paddingBottom="8dp"> + + <CheckBox + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/checkbox_do_not_show_again" + android:text="@string/checkbox_do_not_show_again"/> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/choose_data_folder_dialog_entry.xml b/app/src/main/res/layout/choose_data_folder_dialog_entry.xml index 9a216b36b..f4a2ff703 100644 --- a/app/src/main/res/layout/choose_data_folder_dialog_entry.xml +++ b/app/src/main/res/layout/choose_data_folder_dialog_entry.xml @@ -5,7 +5,7 @@ android:id="@+id/root" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="@dimen/diag_content_side_padding" + android:layout_margin="16dp" android:background="?attr/selectableItemBackground"> <RadioButton diff --git a/app/src/main/res/layout/downloaded_episodeslist_item.xml b/app/src/main/res/layout/downloaded_episodeslist_item.xml index 65a08251f..3f8065466 100644 --- a/app/src/main/res/layout/downloaded_episodeslist_item.xml +++ b/app/src/main/res/layout/downloaded_episodeslist_item.xml @@ -17,7 +17,7 @@ android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" android:contentDescription="@string/cover_label" android:scaleType="centerCrop" - tools:src="@drawable/ic_stat_antenna_default" + tools:src="@drawable/ic_antenna" tools:background="@android:color/holo_green_dark"/> diff --git a/app/src/main/res/layout/feedinfo.xml b/app/src/main/res/layout/feedinfo.xml index 50061c4d8..416fc3aec 100644 --- a/app/src/main/res/layout/feedinfo.xml +++ b/app/src/main/res/layout/feedinfo.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <android.support.v7.widget.GridLayout + <androidx.gridlayout.widget.GridLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" @@ -107,7 +107,7 @@ tools:text="http://www.example.com/feed" tools:background="@android:color/holo_green_dark"/> - </android.support.v7.widget.GridLayout> + </androidx.gridlayout.widget.GridLayout> <TextView style="@style/AntennaPod.TextView.Heading" diff --git a/app/src/main/res/layout/feeditem_fragment.xml b/app/src/main/res/layout/feeditem_fragment.xml index 78c0b3b16..b047b3da0 100644 --- a/app/src/main/res/layout/feeditem_fragment.xml +++ b/app/src/main/res/layout/feeditem_fragment.xml @@ -36,7 +36,8 @@ android:layout_marginBottom="16dp" android:contentDescription="@string/cover_label" android:gravity="center_vertical" - tools:src="@drawable/ic_stat_antenna_default" + android:foreground="?attr/selectableItemBackground" + tools:src="@drawable/ic_antenna" tools:background="@android:color/holo_green_dark" /> <TextView @@ -47,6 +48,7 @@ android:layout_alignTop="@id/imgvCover" android:layout_toRightOf="@id/imgvCover" android:layout_toEndOf="@id/imgvCover" + android:foreground="?attr/selectableItemBackground" tools:text="Podcast title" tools:background="@android:color/holo_green_dark" /> diff --git a/app/src/main/res/layout/feeditemlist_header.xml b/app/src/main/res/layout/feeditemlist_header.xml index ba39307b0..596135d88 100644 --- a/app/src/main/res/layout/feeditemlist_header.xml +++ b/app/src/main/res/layout/feeditemlist_header.xml @@ -27,7 +27,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:contentDescription="@string/cover_label" - tools:src="@drawable/ic_stat_antenna_default" + tools:src="@drawable/ic_antenna" tools:background="@android:color/holo_green_dark"/> <ImageButton @@ -37,11 +37,9 @@ android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:layout_marginTop="16dp" - android:layout_marginRight="16dp" - android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" android:background="?attr/selectableItemBackground" android:contentDescription="@string/show_info_label" android:src="@drawable/ic_info_white_24dp" @@ -57,8 +55,6 @@ tools:background="@android:color/holo_green_dark" android:layout_below="@+id/butShowInfo" android:layout_marginBottom="16dp" - android:layout_marginRight="16dp" - android:layout_marginEnd="16dp" android:layout_alignParentRight="true" android:layout_alignParentEnd="true"/> @@ -92,8 +88,8 @@ android:layout_marginBottom="16dp" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" - android:layout_marginRight="8dp" - android:layout_marginEnd="8dp" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" android:layout_toLeftOf="@id/butShowSettings" android:layout_toStartOf="@id/butShowSettings" android:layout_toRightOf="@id/imgvCover" diff --git a/app/src/main/res/layout/gpodnet_podcast_listitem.xml b/app/src/main/res/layout/gpodnet_podcast_listitem.xml index 27a8bbdca..6e02fa090 100644 --- a/app/src/main/res/layout/gpodnet_podcast_listitem.xml +++ b/app/src/main/res/layout/gpodnet_podcast_listitem.xml @@ -23,7 +23,7 @@ android:contentDescription="@string/cover_label" android:cropToPadding="true" android:scaleType="fitXY" - tools:src="@drawable/ic_stat_antenna_default" + tools:src="@drawable/ic_antenna" tools:background="@android:color/holo_green_dark" /> <LinearLayout diff --git a/app/src/main/res/layout/itunes_podcast_listitem.xml b/app/src/main/res/layout/itunes_podcast_listitem.xml index 4848563b1..b2411c5df 100644 --- a/app/src/main/res/layout/itunes_podcast_listitem.xml +++ b/app/src/main/res/layout/itunes_podcast_listitem.xml @@ -25,7 +25,7 @@ android:cropToPadding="true" android:scaleType="fitXY" tools:background="@android:color/holo_green_dark" - tools:src="@drawable/ic_stat_antenna_default" /> + tools:src="@drawable/ic_antenna" /> <LinearLayout android:layout_width="match_parent" diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 6cabcdff2..a226e482f 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<android.support.v4.widget.DrawerLayout +<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" @@ -19,7 +19,7 @@ tools:layout_height="64dp" tools:background="@android:color/holo_green_light" /> - <android.support.v7.widget.Toolbar + <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -48,4 +48,4 @@ <include layout="@layout/nav_list" /> -</android.support.v4.widget.DrawerLayout>
\ No newline at end of file +</androidx.drawerlayout.widget.DrawerLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/mediaplayerinfo_activity.xml b/app/src/main/res/layout/mediaplayerinfo_activity.xml index a6427e985..b759b0092 100644 --- a/app/src/main/res/layout/mediaplayerinfo_activity.xml +++ b/app/src/main/res/layout/mediaplayerinfo_activity.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<android.support.v4.widget.DrawerLayout +<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" @@ -12,12 +12,12 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <android.support.design.widget.AppBarLayout + <com.google.android.material.appbar.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="wrap_content"> - <android.support.v7.widget.Toolbar + <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -37,7 +37,7 @@ app:strokeColor="?android:attr/textColorSecondary" app:radius="4dp" /> - </android.support.design.widget.AppBarLayout> + </com.google.android.material.appbar.AppBarLayout> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" @@ -155,11 +155,11 @@ android:layout_marginTop="-8dp" android:gravity="center" android:text="30" - android:textSize="10sp" + android:textSize="12sp" android:textColor="?android:attr/textColorSecondary" android:clickable="false"/> - <Button + <ImageButton android:id="@+id/butPlaybackSpeed" android:layout_width="@dimen/audioplayer_playercontrols_length" android:layout_height="@dimen/audioplayer_playercontrols_length" @@ -167,13 +167,28 @@ android:layout_toStartOf="@id/butRev" android:background="?attr/selectableItemBackground" android:contentDescription="@string/set_playback_speed_label" - android:src="?attr/av_fast_forward" - android:textSize="@dimen/text_size_medium" - android:textAllCaps="false" - android:maxLines="1" + android:src="?attr/av_speed" + android:scaleType="fitCenter" + tools:src="@drawable/ic_playback_speed_white" tools:visibility="gone" tools:background="@android:color/holo_green_dark" /> + <TextView + android:id="@+id/txtvPlaybackSpeed" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/butPlaybackSpeed" + android:layout_alignLeft="@id/butPlaybackSpeed" + android:layout_alignStart="@id/butPlaybackSpeed" + android:layout_alignRight="@id/butPlaybackSpeed" + android:layout_alignEnd="@id/butPlaybackSpeed" + android:layout_marginTop="-8dp" + android:gravity="center" + android:text="1.00" + android:textSize="12sp" + android:textColor="?android:attr/textColorSecondary" + android:clickable="false"/> + <ImageButton android:id="@+id/butCastDisconnect" android:layout_width="@dimen/audioplayer_playercontrols_length" @@ -216,7 +231,7 @@ android:layout_marginTop="-8dp" android:gravity="center" android:text="30" - android:textSize="10sp" + android:textSize="12sp" android:textColor="?android:attr/textColorSecondary" android:clickable="false"/> @@ -236,7 +251,7 @@ </LinearLayout> - <android.support.v4.view.ViewPager + <androidx.viewpager.widget.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pager" android:layout_width="match_parent" @@ -257,4 +272,4 @@ <include layout="@layout/nav_list" /> -</android.support.v4.widget.DrawerLayout>
\ No newline at end of file +</androidx.drawerlayout.widget.DrawerLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/nav_feedlistitem.xml b/app/src/main/res/layout/nav_feedlistitem.xml index 816870d1c..52833b3cd 100644 --- a/app/src/main/res/layout/nav_feedlistitem.xml +++ b/app/src/main/res/layout/nav_feedlistitem.xml @@ -25,7 +25,7 @@ android:scaleType="centerCrop" android:layout_marginTop="4dp" android:layout_marginBottom="4dp" - tools:src="@drawable/ic_stat_antenna_default" + tools:src="@drawable/ic_antenna" tools:background="@android:color/holo_green_dark"/> <TextView diff --git a/app/src/main/res/layout/pager_fragment.xml b/app/src/main/res/layout/pager_fragment.xml index 54b711b1c..492743239 100644 --- a/app/src/main/res/layout/pager_fragment.xml +++ b/app/src/main/res/layout/pager_fragment.xml @@ -7,14 +7,14 @@ android:orientation="vertical"> - <android.support.design.widget.TabLayout + <com.google.android.material.tabs.TabLayout android:id="@+id/sliding_tabs" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabGravity="fill" app:tabMode="fixed" /> - <android.support.v4.view.ViewPager + <androidx.viewpager.widget.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="0px" diff --git a/app/src/main/res/layout/queue_fragment.xml b/app/src/main/res/layout/queue_fragment.xml index 85b0df58c..63da6a315 100644 --- a/app/src/main/res/layout/queue_fragment.xml +++ b/app/src/main/res/layout/queue_fragment.xml @@ -20,7 +20,7 @@ android:layout_below="@id/info_bar" android:background="?android:attr/listDivider"/> - <android.support.v7.widget.RecyclerView + <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/app/src/main/res/layout/queue_listitem.xml b/app/src/main/res/layout/queue_listitem.xml index 6b41b68d5..1dcc34bce 100644 --- a/app/src/main/res/layout/queue_listitem.xml +++ b/app/src/main/res/layout/queue_listitem.xml @@ -51,7 +51,7 @@ android:layout_height="@dimen/thumbnail_length_queue_item" android:layout_centerVertical="true" android:contentDescription="@string/cover_label" - tools:src="@drawable/ic_stat_antenna_default" + tools:src="@drawable/ic_antenna" tools:background="@android:color/holo_green_dark"/> </RelativeLayout> diff --git a/app/src/main/res/layout/quick_feed_discovery.xml b/app/src/main/res/layout/quick_feed_discovery.xml index ce5cfa65b..e791d19d7 100644 --- a/app/src/main/res/layout/quick_feed_discovery.xml +++ b/app/src/main/res/layout/quick_feed_discovery.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<android.support.v7.widget.CardView +<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" @@ -79,5 +79,5 @@ </LinearLayout> -</android.support.v7.widget.CardView> +</androidx.cardview.widget.CardView> diff --git a/app/src/main/res/layout/searchlist_item.xml b/app/src/main/res/layout/searchlist_item.xml index 50374c737..4a055fea9 100644 --- a/app/src/main/res/layout/searchlist_item.xml +++ b/app/src/main/res/layout/searchlist_item.xml @@ -18,7 +18,7 @@ android:layout_marginStart="@dimen/listitem_threeline_horizontalpadding" android:contentDescription="@string/cover_label" android:scaleType="centerCrop" - tools:src="@drawable/ic_stat_antenna_default" + tools:src="@drawable/ic_antenna" tools:background="@android:color/holo_green_dark"/> <LinearLayout diff --git a/app/src/main/res/layout/statistics_activity.xml b/app/src/main/res/layout/statistics_activity.xml index 4a72dc7de..9d9cad438 100644 --- a/app/src/main/res/layout/statistics_activity.xml +++ b/app/src/main/res/layout/statistics_activity.xml @@ -1,41 +1,24 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:paddingTop="8dp" - android:paddingBottom="8dp"> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/total_time_listened_to_podcasts" - android:gravity="center_horizontal"/> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> <ProgressBar - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/progressBar" - android:layout_gravity="center_horizontal" - style="?android:attr/progressBarStyleLarge"/> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/total_time" - android:gravity="center_horizontal" - android:textSize="45sp"/> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/progressBar" + android:layout_gravity="center"/> - <ListView - android:id="@+id/statistics_list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:choiceMode="singleChoice" - android:clipToPadding="false" - android:divider="@android:color/transparent" - android:dividerHeight="0dp" - android:paddingBottom="@dimen/list_vertical_padding" - android:paddingTop="@dimen/list_vertical_padding" - android:scrollbarStyle="outsideOverlay" /> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/statistics_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" + android:paddingBottom="@dimen/list_vertical_padding" + android:paddingTop="@dimen/list_vertical_padding" + android:scrollbarStyle="outsideOverlay" + tools:listitem="@layout/statistics_listitem"/> -</LinearLayout> +</FrameLayout> diff --git a/app/src/main/res/layout/statistics_listitem.xml b/app/src/main/res/layout/statistics_listitem.xml index b186add9e..6bd2a907e 100644 --- a/app/src/main/res/layout/statistics_listitem.xml +++ b/app/src/main/res/layout/statistics_listitem.xml @@ -3,63 +3,55 @@ xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="@dimen/listitem_iconwithtext_height" - android:paddingLeft="@dimen/listitem_threeline_verticalpadding" - android:paddingStart="@dimen/listitem_threeline_verticalpadding" - android:paddingRight="@dimen/listitem_threeline_verticalpadding" - android:paddingEnd="@dimen/listitem_threeline_verticalpadding" - tools:background="@android:color/darker_gray"> + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:background="?android:attr/selectableItemBackground"> <ImageView - android:id="@+id/imgvCover" - android:contentDescription="@string/cover_label" - android:layout_width="@dimen/thumbnail_length_navlist" - android:layout_height="@dimen/thumbnail_length_navlist" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:adjustViewBounds="true" - android:cropToPadding="true" - android:scaleType="centerCrop" - android:layout_marginTop="4dp" - android:layout_marginBottom="4dp" - tools:src="@drawable/ic_stat_antenna_default" - tools:background="@android:color/holo_green_dark"/> + android:id="@+id/imgvCover" + android:contentDescription="@string/cover_label" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:adjustViewBounds="true" + android:cropToPadding="true" + android:scaleType="centerCrop" + tools:src="@drawable/ic_antenna" + tools:background="@android:color/holo_green_dark"/> <TextView - android:id="@+id/txtvTime" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/list_vertical_padding" - android:layout_marginStart="@dimen/list_vertical_padding" - android:lines="1" - android:textColor="?android:attr/textColorTertiary" - android:textSize="@dimen/text_size_navdrawer" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - tools:text="23" - tools:background="@android:color/holo_green_dark"/> + android:id="@+id/txtvTitle" + android:lines="1" + android:ellipsize="end" + android:singleLine="true" + android:textColor="?android:attr/textColorPrimary" + android:textSize="16sp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_toRightOf="@id/imgvCover" + android:layout_alignTop="@id/imgvCover" + android:layout_alignWithParentIfMissing="true" + tools:text="Feed title"/> <TextView - android:id="@+id/txtvTitle" - android:lines="1" - android:ellipsize="end" - android:singleLine="true" - android:layout_centerVertical="true" - android:textColor="?android:attr/textColorPrimary" - android:textSize="@dimen/text_size_navdrawer" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/listitem_iconwithtext_textleftpadding" - android:layout_marginStart="@dimen/listitem_iconwithtext_textleftpadding" - android:layout_toRightOf="@id/imgvCover" - android:layout_toEndOf="@id/imgvCover" - android:layout_toLeftOf="@id/txtvTime" - android:layout_toStartOf="@id/txtvTime" - android:layout_alignWithParentIfMissing="true" - tools:text="Navigation feed item title" - tools:background="@android:color/holo_green_dark"/> - + android:id="@+id/txtvTime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:lines="1" + android:textColor="?android:attr/textColorTertiary" + android:textSize="14sp" + android:layout_toEndOf="@+id/imgvCover" + android:layout_toRightOf="@+id/imgvCover" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_below="@+id/txtvTitle" + tools:text="23 hours"/> </RelativeLayout> diff --git a/app/src/main/res/layout/statistics_listitem_total_time.xml b/app/src/main/res/layout/statistics_listitem_total_time.xml new file mode 100644 index 000000000..2e0ae54d6 --- /dev/null +++ b/app/src/main/res/layout/statistics_listitem_total_time.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp"> + + <de.danoeh.antennapod.view.PieChartView + android:id="@+id/pie_chart" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/total_time_description" + android:textSize="14sp" + android:text="@string/total_time_listened_to_podcasts" + android:gravity="center_horizontal" + android:layout_above="@+id/total_time"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/total_time" + android:textColor="?android:attr/textColorPrimary" + android:gravity="center_horizontal" + android:textSize="28sp" + android:layout_marginBottom="16dp" + android:layout_alignBottom="@id/pie_chart" + tools:text="10.0 hours"/> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:background="?android:attr/dividerVertical" + android:layout_below="@+id/pie_chart"/> + +</RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/menu/allepisodes_context.xml b/app/src/main/res/menu/allepisodes_context.xml deleted file mode 100644 index 907bc9334..000000000 --- a/app/src/main/res/menu/allepisodes_context.xml +++ /dev/null @@ -1,86 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - - <item - android:id="@id/skip_episode_item" - android:menuCategory="container" - android:title="@string/skip_episode_label" /> - - - <item - android:id="@+id/remove_new_flag_item" - android:menuCategory="container" - android:title="@string/remove_new_flag_label" /> - - <item - android:id="@+id/mark_read_item" - android:menuCategory="container" - android:title="@string/mark_read_label" /> - <item - android:id="@+id/mark_unread_item" - android:menuCategory="container" - android:title="@string/mark_unread_label" /> - - <item - android:id="@+id/add_to_queue_item" - android:menuCategory="container" - android:title="@string/add_to_queue_label" /> - <item - android:id="@+id/remove_from_queue_item" - android:menuCategory="container" - android:title="@string/remove_from_queue_label" /> - <item - android:id="@+id/add_to_favorites_item" - android:menuCategory="container" - android:title="@string/add_to_favorite_label" /> - <item - android:id="@+id/remove_from_favorites_item" - android:menuCategory="container" - android:title="@string/remove_from_favorite_label" /> - <item - android:id="@+id/reset_position" - android:menuCategory="container" - android:title="@string/reset_position" /> - - <item - android:id="@+id/activate_auto_download" - android:menuCategory="container" - android:title="@string/activate_auto_download" /> - <item - android:id="@+id/deactivate_auto_download" - android:menuCategory="container" - android:title="@string/deactivate_auto_download" /> - - <item - android:id="@+id/visit_website_item" - android:menuCategory="container" - android:title="@string/visit_website_label" /> - <item - android:id="@+id/share_item" - android:menuCategory="container" - android:title="@string/share_label"> - <menu> - <item - android:id="@+id/share_link_item" - android:menuCategory="container" - android:title="@string/share_link_label" /> - <item - android:id="@+id/share_link_with_position_item" - android:menuCategory="container" - android:title="@string/share_link_with_position_label" /> - <item - android:id="@+id/share_download_url_item" - android:menuCategory="container" - android:title="@string/share_item_url_label" /> - <item - android:id="@+id/share_download_url_with_position_item" - android:menuCategory="container" - android:title="@string/share_item_url_with_position_label" /> - <item - android:id="@+id/share_file" - android:menuCategory="container" - android:title="@string/share_file_label" /> - </menu> - </item> -</menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/episodes.xml b/app/src/main/res/menu/episodes.xml index 1e1aa8f56..7398754e5 100644 --- a/app/src/main/res/menu/episodes.xml +++ b/app/src/main/res/menu/episodes.xml @@ -7,7 +7,7 @@ android:id="@+id/action_search" android:icon="?attr/action_search" custom:showAsAction="collapseActionView|ifRoom" - custom:actionViewClass="android.support.v7.widget.SearchView" + custom:actionViewClass="androidx.appcompat.widget.SearchView" android:title="@string/search_label"/> <item diff --git a/app/src/main/res/menu/feedinfo.xml b/app/src/main/res/menu/feedinfo.xml index 300068007..f20d679a5 100644 --- a/app/src/main/res/menu/feedinfo.xml +++ b/app/src/main/res/menu/feedinfo.xml @@ -11,7 +11,7 @@ <item android:id="@+id/share_link_item" custom:showAsAction="collapseActionView" - android:title="@string/share_link_label"> + android:title="@string/share_website_url_label"> </item> <item android:id="@+id/share_download_url_item" diff --git a/app/src/main/res/menu/feeditem_options.xml b/app/src/main/res/menu/feeditem_options.xml index 0801b79a1..e415ff85a 100644 --- a/app/src/main/res/menu/feeditem_options.xml +++ b/app/src/main/res/menu/feeditem_options.xml @@ -9,6 +9,11 @@ </item> <item + android:id="@+id/remove_new_flag_item" + custom:showAsAction="collapseActionView" + android:title="@string/remove_new_flag_label" /> + + <item android:id="@+id/mark_read_item" custom:showAsAction="collapseActionView" android:title="@string/mark_read_label"> diff --git a/app/src/main/res/menu/feeditemlist_context.xml b/app/src/main/res/menu/feeditemlist_context.xml index df13cb027..6e4966206 100644 --- a/app/src/main/res/menu/feeditemlist_context.xml +++ b/app/src/main/res/menu/feeditemlist_context.xml @@ -8,6 +8,11 @@ android:title="@string/skip_episode_label" /> <item + android:id="@+id/remove_new_flag_item" + android:menuCategory="container" + android:title="@string/remove_new_flag_label" /> + + <item android:id="@+id/mark_read_item" android:menuCategory="container" android:title="@string/mark_read_label" /> diff --git a/app/src/main/res/menu/feedlist.xml b/app/src/main/res/menu/feedlist.xml index 0cc8addfe..fdd0e01bc 100644 --- a/app/src/main/res/menu/feedlist.xml +++ b/app/src/main/res/menu/feedlist.xml @@ -34,7 +34,7 @@ android:id="@+id/action_search" android:icon="?attr/action_search" custom:showAsAction="always|collapseActionView" - custom:actionViewClass="android.support.v7.widget.SearchView" + custom:actionViewClass="androidx.appcompat.widget.SearchView" android:title="@string/search_label"/> <item @@ -49,7 +49,7 @@ android:id="@+id/share_link_item" android:menuCategory="container" custom:showAsAction="collapseActionView" - android:title="@string/share_link_label"> + android:title="@string/share_website_url_label"> </item> <item android:id="@+id/share_download_url_item" diff --git a/app/src/main/res/menu/gpodder_podcasts.xml b/app/src/main/res/menu/gpodder_podcasts.xml index 88fa36a4a..93d93157a 100644 --- a/app/src/main/res/menu/gpodder_podcasts.xml +++ b/app/src/main/res/menu/gpodder_podcasts.xml @@ -7,7 +7,7 @@ android:id="@+id/action_search" android:icon="?attr/action_search" custom:showAsAction="collapseActionView|ifRoom" - custom:actionViewClass="android.support.v7.widget.SearchView" + custom:actionViewClass="androidx.appcompat.widget.SearchView" android:title="@string/search_label"/> </menu> diff --git a/app/src/main/res/menu/itunes_search.xml b/app/src/main/res/menu/itunes_search.xml index 88fa36a4a..93d93157a 100644 --- a/app/src/main/res/menu/itunes_search.xml +++ b/app/src/main/res/menu/itunes_search.xml @@ -7,7 +7,7 @@ android:id="@+id/action_search" android:icon="?attr/action_search" custom:showAsAction="collapseActionView|ifRoom" - custom:actionViewClass="android.support.v7.widget.SearchView" + custom:actionViewClass="androidx.appcompat.widget.SearchView" android:title="@string/search_label"/> </menu> diff --git a/app/src/main/res/menu/mediaplayer.xml b/app/src/main/res/menu/mediaplayer.xml index 44d511ee4..055951760 100644 --- a/app/src/main/res/menu/mediaplayer.xml +++ b/app/src/main/res/menu/mediaplayer.xml @@ -35,6 +35,14 @@ </item> <item + android:id="@+id/open_feed_item" + android:icon="?attr/feed" + custom:showAsAction="collapseActionView" + android:title="@string/open_podcast" + android:visible="false"> + </item> + + <item android:id="@+id/visit_website_item" android:icon="?attr/location_web_site" custom:showAsAction="collapseActionView" diff --git a/app/src/main/res/menu/queue.xml b/app/src/main/res/menu/queue.xml index a4e511eb8..b86ad5d63 100644 --- a/app/src/main/res/menu/queue.xml +++ b/app/src/main/res/menu/queue.xml @@ -20,7 +20,7 @@ android:id="@+id/action_search" android:icon="?attr/action_search" custom:showAsAction="collapseActionView|ifRoom" - custom:actionViewClass="android.support.v7.widget.SearchView" + custom:actionViewClass="androidx.appcompat.widget.SearchView" android:title="@string/search_label"/> <item diff --git a/app/src/main/res/menu/queue_context.xml b/app/src/main/res/menu/queue_context.xml index e1c3e6216..522e712e2 100644 --- a/app/src/main/res/menu/queue_context.xml +++ b/app/src/main/res/menu/queue_context.xml @@ -11,77 +11,6 @@ android:id="@+id/move_to_bottom_item" android:menuCategory="container" android:title="@string/move_to_bottom_label" /> + <!-- rest of the menu items can be found in the generic feeditemlist_context.xml --> - <item - android:id="@+id/mark_read_item" - android:menuCategory="container" - android:title="@string/mark_read_label" /> - - <item - android:id="@+id/mark_unread_item" - android:menuCategory="container" - android:title="@string/mark_unread_label" /> - - <item - android:id="@+id/remove_from_queue_item" - android:menuCategory="container" - android:title="@string/remove_from_queue_label" /> - <item - android:id="@+id/remove_item" - android:menuCategory="container" - android:title="@string/delete_label" /> - - <item - android:id="@+id/add_to_favorites_item" - android:menuCategory="container" - android:title="@string/add_to_favorite_label" /> - <item - android:id="@+id/remove_from_favorites_item" - android:menuCategory="container" - android:title="@string/remove_from_favorite_label" /> - <item - android:id="@+id/reset_position" - android:menuCategory="container" - android:title="@string/reset_position" /> - - <item - android:id="@+id/activate_auto_download" - android:menuCategory="container" - android:title="@string/activate_auto_download" /> - <item - android:id="@+id/deactivate_auto_download" - android:menuCategory="container" - android:title="@string/deactivate_auto_download" /> - - <item - android:id="@+id/visit_website_item" - android:menuCategory="container" - android:title="@string/visit_website_label" /> - <item - android:id="@+id/share_item" - android:menuCategory="container" - android:title="@string/share_label"> - <menu> - <item - android:id="@+id/share_link_item" - android:menuCategory="container" - android:title="@string/share_link_label" /> - <item - android:id="@+id/share_link_with_position_item" - android:menuCategory="container" - android:title="@string/share_link_with_position_label" /> - <item - android:id="@+id/share_download_url_item" - android:menuCategory="container" - android:title="@string/share_item_url_label" /> - <item - android:id="@+id/share_download_url_with_position_item" - android:menuCategory="container" - android:title="@string/share_item_url_with_position_label" /> - <item - android:id="@+id/share_file" - android:menuCategory="container" - android:title="@string/share_file_label" /> - </menu> - </item> </menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/subscriptions.xml b/app/src/main/res/menu/subscriptions.xml index f39e0ac97..1780592d5 100644 --- a/app/src/main/res/menu/subscriptions.xml +++ b/app/src/main/res/menu/subscriptions.xml @@ -3,6 +3,13 @@ xmlns:custom="http://schemas.android.com/apk/res-auto"> <item + android:id="@+id/refresh_item" + android:title="@string/refresh_label" + android:menuCategory="container" + custom:showAsAction="always" + android:icon="?attr/navigation_refresh"/> + + <item android:id="@+id/subscription_num_columns" android:title="@string/subscription_num_columns" custom:showAsAction="never"> diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml new file mode 100644 index 000000000..8c444ee8b --- /dev/null +++ b/app/src/main/res/values/integers.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <integer name="card_flip_time_full">400</integer> + <integer name="card_flip_time_half">200</integer> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6c1e470c0..37707ead6 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -43,16 +43,14 @@ <Preference android:key="prefFaq" android:title="@string/pref_faq" - android:icon="?attr/ic_question_answer" /> - + android:icon="?attr/ic_questionmark" /> <Preference - android:key="prefKnownIssues" - android:title="@string/pref_known_issues" - android:icon="?attr/ic_known_issues" /> + android:key="prefViewMailingList" + android:title="@string/view_mailing_list" + android:icon="?attr/ic_chat" /> <Preference - android:key="prefSendCrashReport" - android:title="@string/crash_report_title" - android:summary="@string/crash_report_sum" + android:key="prefSendBugReport" + android:title="@string/bug_report_title" android:icon="?attr/ic_bug" /> <Preference android:key="prefAbout" diff --git a/app/src/main/res/xml/preferences_user_interface.xml b/app/src/main/res/xml/preferences_user_interface.xml index 1d970a5f7..c48e9adc8 100644 --- a/app/src/main/res/xml/preferences_user_interface.xml +++ b/app/src/main/res/xml/preferences_user_interface.xml @@ -10,7 +10,7 @@ android:title="@string/pref_set_theme_title" android:key="prefTheme" android:summary="@string/pref_set_theme_sum" - android:defaultValue="0" + android:defaultValue="system" app:useStockLayout="true"/> <Preference android:key="prefHiddenDrawerItems" diff --git a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java index 87304b3d6..912571a7e 100644 --- a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java +++ b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java @@ -4,9 +4,9 @@ import android.content.SharedPreferences; import android.media.AudioManager; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.annotation.CallSuper; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.CallSuper; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -29,17 +29,34 @@ public abstract class CastEnabledActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String TAG = "CastEnabledActivity"; - protected CastManager castManager; - protected SwitchableMediaRouteActionProvider mediaRouteActionProvider; + private CastConsumer castConsumer; + private CastManager castManager; + + private SwitchableMediaRouteActionProvider mediaRouteActionProvider; private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (!CastManager.isInitialized()) { + return; + } + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()). registerOnSharedPreferenceChangeListener(this); + castConsumer = new DefaultCastConsumer() { + @Override + public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { + onCastConnectionChanged(true); + } + + @Override + public void onDisconnected() { + onCastConnectionChanged(false); + } + }; castManager = CastManager.getInstance(); castManager.addCastConsumer(castConsumer); castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled()); @@ -48,6 +65,10 @@ public abstract class CastEnabledActivity extends AppCompatActivity @Override protected void onDestroy() { + if (!CastManager.isInitialized()) { + super.onDestroy(); + return; + } PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) .unregisterOnSharedPreferenceChangeListener(this); castManager.removeCastConsumer(castConsumer); @@ -58,6 +79,9 @@ public abstract class CastEnabledActivity extends AppCompatActivity @CallSuper public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); + if (!CastManager.isInitialized()) { + return true; + } getMenuInflater().inflate(R.menu.cast_enabled, menu); castButtonVisibilityManager.setMenu(menu); return true; @@ -67,6 +91,10 @@ public abstract class CastEnabledActivity extends AppCompatActivity @CallSuper public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); + if (!CastManager.isInitialized()) { + return true; + } + MenuItem mediaRouteButton = menu.findItem(R.id.media_route_menu_item); if (mediaRouteButton == null) { Log.wtf(TAG, "MediaRoute item could not be found on the menu!", new Exception()); @@ -83,15 +111,22 @@ public abstract class CastEnabledActivity extends AppCompatActivity @Override protected void onResume() { super.onResume(); + if (!CastManager.isInitialized()) { + return; + } castButtonVisibilityManager.setResumed(true); } @Override protected void onPause() { super.onPause(); + if (!CastManager.isInitialized()) { + return; + } castButtonVisibilityManager.setResumed(false); } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { @@ -105,18 +140,6 @@ public abstract class CastEnabledActivity extends AppCompatActivity } } - CastConsumer castConsumer = new DefaultCastConsumer() { - @Override - public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { - onCastConnectionChanged(true); - } - - @Override - public void onDisconnected() { - onCastConnectionChanged(false); - } - }; - private void onCastConnectionChanged(boolean connected) { if (connected) { castButtonVisibilityManager.onConnected(); @@ -133,6 +156,9 @@ public abstract class CastEnabledActivity extends AppCompatActivity * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)} */ public final void requestCastButton(int showAsAction) { + if (!CastManager.isInitialized()) { + return; + } castButtonVisibilityManager.requestCastButton(showAsAction); } diff --git a/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java b/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java index 916b13a38..2a879c62d 100644 --- a/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java +++ b/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.config; -import android.support.annotation.NonNull; -import android.support.v7.app.MediaRouteControllerDialogFragment; -import android.support.v7.app.MediaRouteDialogFactory; +import androidx.annotation.NonNull; +import androidx.mediarouter.app.MediaRouteControllerDialogFragment; +import androidx.mediarouter.app.MediaRouteDialogFactory; import de.danoeh.antennapod.core.CastCallbacks; import de.danoeh.antennapod.fragment.CustomMRControllerDialogFragment; diff --git a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java index ae8e20575..bf5b85c82 100644 --- a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java +++ b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java @@ -8,19 +8,19 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; -import android.support.v4.util.Pair; -import android.support.v4.view.MarginLayoutParamsCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v7.app.MediaRouteControllerDialog; -import android.support.v7.graphics.Palette; -import android.support.v7.media.MediaRouter; -import android.support.v7.widget.AppCompatImageView; +import androidx.core.util.Pair; +import androidx.core.view.MarginLayoutParamsCompat; +import androidx.core.view.accessibility.AccessibilityEventCompat; +import androidx.mediarouter.app.MediaRouteControllerDialog; +import androidx.palette.graphics.Palette; +import androidx.mediarouter.media.MediaRouter; +import androidx.appcompat.widget.AppCompatImageView; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; @@ -311,9 +311,7 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { AccessibilityEventCompat.TYPE_ANNOUNCEMENT); event.setPackageName(getContext().getPackageName()); event.setClassName(getClass().getName()); - int resId = isPlaying ? - android.support.v7.mediarouter.R.string.mr_controller_pause : - android.support.v7.mediarouter.R.string.mr_controller_play; + int resId = isPlaying ? R.string.mr_controller_pause : R.string.mr_controller_play; event.getText().add(getContext().getString(resId)); accessibilityManager.sendAccessibilityEvent(event); } @@ -359,17 +357,17 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { if (route.getPresentationDisplay() != null && route.getPresentationDisplay().getDisplayId() != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) { // The user is currently casting screen. - titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_casting_screen); + titleView.setText(R.string.mr_controller_casting_screen); showTitle = true; } else if (state == null || state.getState() == PlaybackStateCompat.STATE_NONE) { // Show "No media selected" as we don't yet know the playback state. // (Only exception is bluetooth where we don't show anything.) if (!route.isBluetooth()) { - titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_no_media_selected); + titleView.setText(R.string.mr_controller_no_media_selected); showTitle = true; } } else if (!hasTitle && !hasSubtitle) { - titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_no_info_available); + titleView.setText(R.string.mr_controller_no_info_available); showTitle = true; } else { if (hasTitle) { @@ -435,16 +433,12 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0; if (isPlaying && supportsPause) { playPauseButton.setVisibility(View.VISIBLE); - playPauseButton.setImageResource(getThemeResource(getContext(), - android.support.v7.mediarouter.R.attr.mediaRoutePauseDrawable)); - playPauseButton.setContentDescription(getContext().getResources() - .getText(android.support.v7.mediarouter.R.string.mr_controller_pause)); + playPauseButton.setImageResource(getThemeResource(getContext(), R.attr.mediaRoutePauseDrawable)); + playPauseButton.setContentDescription(getContext().getResources().getText(R.string.mr_controller_pause)); } else if (!isPlaying && supportsPlay) { playPauseButton.setVisibility(View.VISIBLE); - playPauseButton.setImageResource(getThemeResource(getContext(), - android.support.v7.mediarouter.R.attr.mediaRoutePlayDrawable)); - playPauseButton.setContentDescription(getContext().getResources() - .getText(android.support.v7.mediarouter.R.string.mr_controller_play)); + playPauseButton.setImageResource(getThemeResource(getContext(), R.attr.mediaRoutePlayDrawable)); + playPauseButton.setContentDescription(getContext().getResources().getText(R.string.mr_controller_play)); } else { playPauseButton.setVisibility(View.GONE); } diff --git a/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java b/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java index a960ec998..dad7b0bfd 100644 --- a/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java +++ b/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java @@ -2,8 +2,8 @@ package de.danoeh.antennapod.fragment; import android.content.Context; import android.os.Bundle; -import android.support.v7.app.MediaRouteControllerDialog; -import android.support.v7.app.MediaRouteControllerDialogFragment; +import androidx.mediarouter.app.MediaRouteControllerDialog; +import androidx.mediarouter.app.MediaRouteControllerDialogFragment; import de.danoeh.antennapod.dialog.CustomMRControllerDialog; diff --git a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java index c9d52df0c..810074bbe 100644 --- a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java +++ b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java @@ -1,8 +1,13 @@ package de.danoeh.antennapod.preferences; +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; +import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.fragment.preferences.PlaybackPreferencesFragment; @@ -18,6 +23,7 @@ public class PreferenceControllerFlavorHelper { final int googlePlayServicesCheck = GoogleApiAvailability.getInstance() .isGooglePlayServicesAvailable(ui.getActivity()); if (googlePlayServicesCheck == ConnectionResult.SUCCESS) { + displayRestartRequiredDialog(ui.requireContext()); return true; } else { GoogleApiAvailability.getInstance() @@ -29,4 +35,13 @@ public class PreferenceControllerFlavorHelper { return true; }); } + + private static void displayRestartRequiredDialog(@NonNull Context context) { + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + dialog.setTitle(android.R.string.dialog_alert_title); + dialog.setMessage(R.string.pref_restart_required); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.setCancelable(false); + dialog.show(); + } } diff --git a/build.gradle b/build.gradle index 2ca680ccb..d8e1a6c32 100644 --- a/build.gradle +++ b/build.gradle @@ -44,9 +44,8 @@ project.ext { minSdkVersion = 14 targetSdkVersion = 28 - supportVersion = "27.1.1" - lifecycle_version = "1.1.1" workManagerVersion = "1.0.1" + espressoVersion = "3.2.0" awaitilityVersion = "3.1.2" commonsioVersion = "2.5" commonslangVersion = "3.6" @@ -57,8 +56,8 @@ project.ext { iconifyVersion = "2.2.2" jsoupVersion = "1.11.2" materialDialogsVersion = "0.9.0.2" - okhttpVersion = "3.9.0" - okioVersion = "1.14.0" + okhttpVersion = "3.12.5" + okioVersion = "1.17.4" recyclerviewFlexibledividerVersion = "1.4.0" robotiumSoloVersion = "5.6.3" rxAndroidVersion = "2.1.0" @@ -71,7 +70,7 @@ project.ext { castCompanionLibVer = "2.9.1" playServicesVersion = "8.4.0" - wearableSupportVersion = "2.2.0" + wearableSupportVersion = "2.4.0" } wrapper { @@ -82,3 +81,14 @@ wrapper { def doFreeBuild() { return hasProperty("freeBuild") } + +apply plugin: "checkstyle" +checkstyle { + toolVersion '8.24' +} + +task checkstyle(type: Checkstyle) { + classpath = files() + source "${project.rootDir}" + exclude("**/gen/**") +} diff --git a/config/checkstyle/checkstyle-best-practice.xml b/config/checkstyle/checkstyle-best-practice.xml new file mode 100644 index 000000000..66ce0dcc4 --- /dev/null +++ b/config/checkstyle/checkstyle-best-practice.xml @@ -0,0 +1,252 @@ +<?xml version="1.0"?> +<!DOCTYPE module PUBLIC + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> + +<!-- + Checkstyle configuration that checks the Google coding conventions from Google Java Style + that can be found at https://google.github.io/styleguide/javaguide.html. + Checkstyle is very configurable. Be sure to read the documentation at + http://checkstyle.sf.net (or in your downloaded distribution). + To completely disable a check, just comment it out or delete it from the file. + Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov. + --> + +<module name = "Checker"> + <property name="charset" value="UTF-8"/> + + <property name="severity" value="warning"/> + + <property name="fileExtensions" value="java, properties, xml"/> + <!-- Checks for whitespace --> + <!-- See http://checkstyle.sf.net/config_whitespace.html --> + <module name="FileTabCharacter"> + <property name="eachLine" value="true"/> + </module> + + <module name="TreeWalker"> + <module name="OuterTypeFilename"/> + <module name="IllegalTokenText"> + <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/> + <property name="format" + value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/> + <property name="message" + value="Consider using special escape sequence instead of octal value or Unicode escaped value."/> + </module> + <module name="AvoidEscapedUnicodeCharacters"> + <property name="allowEscapesForControlCharacters" value="true"/> + <property name="allowByTailComment" value="true"/> + <property name="allowNonPrintableEscapes" value="true"/> + </module> + <module name="LineLength"> + <property name="max" value="120"/> + <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/> + </module> + <module name="AvoidStarImport"/> + <module name="OneTopLevelClass"/> + <module name="NoLineWrap"/> + <module name="EmptyBlock"> + <property name="option" value="TEXT"/> + <property name="tokens" + value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> + </module> + <module name="NeedBraces"/> + <module name="LeftCurly"/> + <module name="RightCurly"> + <property name="id" value="RightCurlySame"/> + <property name="tokens" + value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, + LITERAL_DO"/> + </module> + <module name="RightCurly"> + <property name="id" value="RightCurlyAlone"/> + <property name="option" value="alone"/> + <property name="tokens" + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, + INSTANCE_INIT"/> + </module> + <module name="WhitespaceAround"> + <property name="allowEmptyConstructors" value="true"/> + <property name="allowEmptyMethods" value="true"/> + <property name="allowEmptyTypes" value="true"/> + <property name="allowEmptyLoops" value="true"/> + <message key="ws.notFollowed" + value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/> + <message key="ws.notPreceded" + value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/> + </module> + <module name="OneStatementPerLine"/> + <module name="MultipleVariableDeclarations"/> + <module name="ArrayTypeStyle"/> + <module name="MissingSwitchDefault"/> + <module name="FallThrough"/> + <module name="UpperEll"/> + <module name="ModifierOrder"/> + <module name="EmptyLineSeparator"> + <property name="allowNoEmptyLineBetweenFields" value="true"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapDot"/> + <property name="tokens" value="DOT"/> + <property name="option" value="nl"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapComma"/> + <property name="tokens" value="COMMA"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 --> + <property name="id" value="SeparatorWrapEllipsis"/> + <property name="tokens" value="ELLIPSIS"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 --> + <property name="id" value="SeparatorWrapArrayDeclarator"/> + <property name="tokens" value="ARRAY_DECLARATOR"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapMethodRef"/> + <property name="tokens" value="METHOD_REF"/> + <property name="option" value="nl"/> + </module> + <module name="PackageName"> + <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> + <message key="name.invalidPattern" + value="Package name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="TypeName"> + <message key="name.invalidPattern" + value="Type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="MemberName"> + <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/> + <message key="name.invalidPattern" + value="Member name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="ParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="LambdaParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="CatchParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="LocalVariableName"> + <property name="tokens" value="VARIABLE_DEF"/> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Local variable name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="ClassTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Class type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="MethodTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Method type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="InterfaceTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Interface type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="NoFinalizer"/> + <module name="GenericWhitespace"> + <message key="ws.followed" + value="GenericWhitespace ''{0}'' is followed by whitespace."/> + <message key="ws.preceded" + value="GenericWhitespace ''{0}'' is preceded with whitespace."/> + <message key="ws.illegalFollow" + value="GenericWhitespace ''{0}'' should followed by whitespace."/> + <message key="ws.notPreceded" + value="GenericWhitespace ''{0}'' is not preceded with whitespace."/> + </module> + <module name="Indentation"> + <property name="basicOffset" value="4"/> + <property name="braceAdjustment" value="0"/> + <property name="caseIndent" value="4"/> + <property name="throwsIndent" value="8"/> + <property name="lineWrappingIndentation" value="8"/> + <property name="arrayInitIndent" value="4"/> + </module> + <module name="AbbreviationAsWordInName"> + <property name="ignoreFinal" value="false"/> + <property name="allowedAbbreviationLength" value="1"/> + </module> + <module name="OverloadMethodsDeclarationOrder"/> + <module name="VariableDeclarationUsageDistance"/> + <module name="CustomImportOrder"> + <property name="sortImportsInGroupAlphabetically" value="true"/> + <property name="separateLineBetweenGroups" value="true"/> + <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/> + </module> + <module name="MethodParamPad"/> + <module name="NoWhitespaceBefore"> + <property name="tokens" + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/> + <property name="allowLineBreaks" value="true"/> + </module> + <module name="ParenPad"/> + <module name="OperatorWrap"> + <property name="option" value="NL"/> + <property name="tokens" + value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, + LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationMostCases"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationVariables"/> + <property name="tokens" value="VARIABLE_DEF"/> + <property name="allowSamelineMultipleAnnotations" value="true"/> + </module> + <module name="NonEmptyAtclauseDescription"/> + <module name="JavadocTagContinuationIndentation"/> + <module name="SummaryJavadoc"> + <property name="forbiddenSummaryFragments" + value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/> + </module> + <module name="JavadocParagraph"/> + <module name="AtclauseOrder"> + <property name="tagOrder" value="@param, @return, @throws, @deprecated"/> + <property name="target" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/> + </module> + <module name="JavadocMethod"> + <property name="scope" value="public"/> + <property name="allowMissingParamTags" value="true"/> + <property name="allowMissingThrowsTags" value="true"/> + <property name="allowMissingReturnTag" value="true"/> + <property name="minLineCount" value="2"/> + <property name="allowedAnnotations" value="Override, Test"/> + <property name="allowThrowsTagsForSubclasses" value="true"/> + </module> + <module name="MethodName"> + <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/> + <message key="name.invalidPattern" + value="Method name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="SingleLineJavadoc"> + <property name="ignoreInlineTags" value="false"/> + </module> + <module name="EmptyCatchBlock"> + <property name="exceptionVariableName" value="expected"/> + </module> + <module name="CommentsIndentation"/> + </module> +</module> diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 000000000..0a5b47eb8 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<!DOCTYPE module PUBLIC + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> +<module name = "Checker"> + <property name="charset" value="UTF-8"/> + + <property name="severity" value="error"/> + + <property name="fileExtensions" value="java, xml"/> + + <module name="TreeWalker"> + <module name="OuterTypeFilename"/> + <module name="AvoidEscapedUnicodeCharacters"> + <property name="allowEscapesForControlCharacters" value="true"/> + <property name="allowByTailComment" value="true"/> + <property name="allowNonPrintableEscapes" value="true"/> + </module> + <module name="AvoidStarImport"/> + <module name="OneTopLevelClass"/> + <module name="NoLineWrap"/> + <module name="EmptyBlock"> + <property name="option" value="TEXT"/> + <property name="tokens" + value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> + </module> + <module name="OneStatementPerLine"/> + <module name="FallThrough"/> + <module name="UpperEll"/> + <module name="ModifierOrder"/> + <module name="PackageName"> + <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> + <message key="name.invalidPattern" + value="Package name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="TypeName"> + <message key="name.invalidPattern" + value="Type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="CatchParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="ClassTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Class type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="MethodTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Method type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="InterfaceTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Interface type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="NoFinalizer"/> + <module name="GenericWhitespace"> + <message key="ws.followed" + value="GenericWhitespace ''{0}'' is followed by whitespace."/> + <message key="ws.preceded" + value="GenericWhitespace ''{0}'' is preceded with whitespace."/> + <message key="ws.illegalFollow" + value="GenericWhitespace ''{0}'' should followed by whitespace."/> + <message key="ws.notPreceded" + value="GenericWhitespace ''{0}'' is not preceded with whitespace."/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationMostCases"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationVariables"/> + <property name="tokens" value="VARIABLE_DEF"/> + <property name="allowSamelineMultipleAnnotations" value="true"/> + </module> + </module> +</module> diff --git a/core/build.gradle b/core/build.gradle index 133f1b262..fe6ed824a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -10,7 +10,7 @@ android { versionCode 1 versionName "1.0" testApplicationId "de.danoeh.antennapod.core.tests" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions { annotationProcessorOptions { @@ -56,11 +56,11 @@ android { } dependencies { - implementation "com.android.support:support-v4:$supportVersion" - implementation "com.android.support:appcompat-v7:$supportVersion" - implementation "com.android.support:preference-v14:$supportVersion" + implementation "androidx.appcompat:appcompat:1.1.0" + implementation "androidx.preference:preference:1.1.0" + annotationProcessor "androidx.annotation:annotation:1.1.0" implementation "android.arch.work:work-runtime:$workManagerVersion" - + implementation "androidx.media:media:1.1.0" implementation "org.apache.commons:commons-lang3:$commonslangVersion" implementation "org.apache.commons:commons-text:$commonstextVersion" implementation "commons-io:commons-io:$commonsioVersion" @@ -76,7 +76,6 @@ dependencies { annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbusVersion" implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion" - implementation "org.awaitility:awaitility:$awaitilityVersion" implementation "com.google.android.exoplayer:exoplayer:$exoPlayerVersion" implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion" @@ -85,18 +84,19 @@ dependencies { // free build hack: skip some dependencies if (!doFreeBuild()) { playApi "com.google.android.libraries.cast.companionlibrary:ccl:$castCompanionLibVer" - api "com.android.support:mediarouter-v7:$supportVersion" + api "androidx.mediarouter:mediarouter:1.0.0" playApi "com.google.android.gms:play-services-cast:$playServicesVersion" api "com.google.android.support:wearable:$wearableSupportVersion" } else { System.out.println("core: free build hack, skipping some dependencies") } + testImplementation "org.awaitility:awaitility:$awaitilityVersion" testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:1.10.19' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' } diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java index ffba4e479..5d98f133c 100644 --- a/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java +++ b/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.util; -import android.support.test.filters.SmallTest; +import androidx.test.filters.SmallTest; import org.junit.Test; import java.util.Calendar; diff --git a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java index 37109ddca..837cb1bd0 100644 --- a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java +++ b/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.service.playback; import android.content.Context; -import android.support.annotation.StringRes; +import androidx.annotation.StringRes; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index 3b6edae42..7d84bdddb 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -9,6 +9,11 @@ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.VIBRATE" /> + <!-- ACCESS_FINE_LOCATION is needed only on Android 10+, + for Automatic Download Wifi filter's UI, which uses + WifiManager.WifiManager.getConfiguredNetworks() + --> + <uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" /> <application android:allowBackup="true" diff --git a/core/src/main/java/android/support/v4/app/SafeJobIntentService.java b/core/src/main/java/androidx/core/app/SafeJobIntentService.java index c07c409ee..aedc9418b 100644 --- a/core/src/main/java/android/support/v4/app/SafeJobIntentService.java +++ b/core/src/main/java/androidx/core/app/SafeJobIntentService.java @@ -1,4 +1,4 @@ -package android.support.v4.app; +package androidx.core.app; import android.app.job.JobParameters; import android.app.job.JobServiceEngine; @@ -6,7 +6,7 @@ import android.app.job.JobWorkItem; import android.content.Intent; import android.os.Build; import android.os.IBinder; -import android.support.annotation.RequiresApi; +import androidx.annotation.RequiresApi; import android.util.Log; diff --git a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java index c626a8189..c9fe886fb 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java +++ b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.dialog; import android.content.Context; import android.content.DialogInterface; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.util.Log; import de.danoeh.antennapod.core.R; diff --git a/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java b/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java index b1beac315..e38abb990 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java +++ b/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.dialog; import android.content.Context; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.core.R; diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java index 124fd3e64..24a71ec96 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java @@ -25,4 +25,8 @@ public class DownloadEvent { "update=" + update + '}'; } + + public boolean hasChangedFeedUpdateStatus(boolean oldStatus) { + return oldStatus != update.feedIds.length > 0; + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java index 7ca6f78de..9acd7728a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.event; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.Arrays; import java.util.List; diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java index 9db262857..89ecf271b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.event; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java deleted file mode 100644 index 4a591c996..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.danoeh.antennapod.core.event; - -import de.danoeh.antennapod.core.feed.FeedMedia; - -public class FeedMediaEvent { - - public enum Action { - UPDATE - } - - private final Action action; - private final FeedMedia media; - - private FeedMediaEvent(Action action, FeedMedia media) { - this.action = action; - this.media = media; - } - - public static FeedMediaEvent update(FeedMedia media) { - return new FeedMediaEvent(Action.UPDATE, media); - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/MessageEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/MessageEvent.java index 9fc488fbc..9fb22b8ea 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/MessageEvent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/MessageEvent.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.event; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; public class MessageEvent { diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackPositionEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackPositionEvent.java new file mode 100644 index 000000000..3327d8a02 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackPositionEvent.java @@ -0,0 +1,19 @@ +package de.danoeh.antennapod.core.event; + +public class PlaybackPositionEvent { + private final int position; + private final int duration; + + public PlaybackPositionEvent(int position, int duration) { + this.position = position; + this.duration = duration; + } + + public int getPosition() { + return position; + } + + public int getDuration() { + return duration; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/ProgressEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/ProgressEvent.java deleted file mode 100644 index 3769d6bb1..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/ProgressEvent.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.danoeh.antennapod.core.event; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -public class ProgressEvent { - - public enum Action { - START, END - } - - public final Action action; - public final String message; - - private ProgressEvent(Action action, String message) { - this.action = action; - this.message = message; - } - - public static ProgressEvent start(String message) { - return new ProgressEvent(Action.START, message); - } - - public static ProgressEvent end() { - return new ProgressEvent(Action.END, null); - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("action", action) - .append("message", message) - .toString(); - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java index a84e8456f..209dcba4d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.event; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java index 5718c06c2..6a7c97b7b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.feed; import android.database.Cursor; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.text.TextUtils; import java.util.ArrayList; diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java index 86d8f5294..0f9ca0445 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.feed; import android.database.Cursor; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.text.TextUtils; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -221,6 +221,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, ImageR return itemIdentifier; } else if (title != null && !title.isEmpty()) { return title; + } else if (hasMedia() && media.getDownload_url() != null) { + return media.getDownload_url(); } else { return link; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java index 05ca84bac..10cf1f5c8 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java @@ -7,7 +7,7 @@ import android.database.Cursor; import android.media.MediaMetadataRetriever; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaDescriptionCompat; diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java index b0512d844..8fdf2034f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.feed; import android.content.Context; import android.database.Cursor; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.TextUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java index 552c1b691..b2c809e90 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.glide; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.bumptech.glide.Glide; import com.bumptech.glide.GlideBuilder; import com.bumptech.glide.Registry; diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java index b75e1630c..f02334af5 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.glide; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -21,8 +21,12 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.service.download.HttpDownloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.NetworkUtils; -import okhttp3.*; -import okhttp3.internal.http.RealResponseBody; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; /** * @see com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader @@ -88,11 +92,9 @@ class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> { @Nullable @Override public LoadData<InputStream> buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) { - Log.d(TAG, "buildLoadData() called with: " + "model = [" + model + "], width = [" - + width + "], height = [" + height + "]"); - if(TextUtils.isEmpty(model)) { + if (TextUtils.isEmpty(model)) { return null; - } else if(model.startsWith("/")) { + } else if (model.startsWith("/")) { return new LoadData<>(new ObjectKey(model), new AudioCoverFetcher(model)); } else { GlideUrl url = new GlideUrl(model); diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java b/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java index 479846655..6a237573b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.glide; import android.media.MediaMetadataRetriever; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.bumptech.glide.Priority; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.data.DataFetcher; diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java index a740782d6..cbd22ceb0 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.glide; import android.graphics.Bitmap; import android.media.ThumbnailUtils; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java index 2588cfdee..9ee8c0fc1 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.gpoddernet; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.json.JSONArray; import org.json.JSONException; diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java index faf4264e5..e86b74164 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.gpoddernet.model; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; public class GpodnetDevice { diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java index 1e21efcda..7b28bba49 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.gpoddernet.model; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.List; diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java index b6efab016..10ea4cd9b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.gpoddernet.model; -import android.support.v4.util.ArrayMap; +import androidx.collection.ArrayMap; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java index 680dc1042..2c2d759c9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.gpoddernet.model; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; public class GpodnetPodcast { private final String url; diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java index 0f1961bef..56a64053f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.gpoddernet.model; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.List; diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java index 40543592e..dec3be7f2 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.gpoddernet.model; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; public class GpodnetTag implements Parcelable { diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java index 9f9c3bd74..ba3db8412 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.gpoddernet.model; -import android.support.v4.util.ArrayMap; +import androidx.collection.ArrayMap; import org.json.JSONArray; import org.json.JSONException; diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java index a4cc22d8b..f2c0c8fe3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java @@ -4,7 +4,12 @@ import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.util.Log; import de.danoeh.antennapod.core.feed.EventDistributor; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.service.playback.PlayerStatus; +import de.danoeh.antennapod.core.util.playback.Playable; /** * Provides access to preferences set by the playback service. A private @@ -19,35 +24,35 @@ public class PlaybackPreferences implements SharedPreferences.OnSharedPreference * Contains the feed id of the currently playing item if it is a FeedMedia * object. */ - public static final String PREF_CURRENTLY_PLAYING_FEED_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedId"; + private static final String PREF_CURRENTLY_PLAYING_FEED_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedId"; /** * Contains the id of the currently playing FeedMedia object or * NO_MEDIA_PLAYING if the currently playing media is no FeedMedia object. */ - public static final String PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedMediaId"; + private static final String PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedMediaId"; /** * Type of the media object that is currently being played. This preference * is set to NO_MEDIA_PLAYING after playback has been completed and is set * as soon as the 'play' button is pressed. */ - public static final String PREF_CURRENTLY_PLAYING_MEDIA = "de.danoeh.antennapod.preferences.currentlyPlayingMedia"; + private static final String PREF_CURRENTLY_PLAYING_MEDIA = "de.danoeh.antennapod.preferences.currentlyPlayingMedia"; /** * True if last played media was streamed. */ - public static final String PREF_CURRENT_EPISODE_IS_STREAM = "de.danoeh.antennapod.preferences.lastIsStream"; + private static final String PREF_CURRENT_EPISODE_IS_STREAM = "de.danoeh.antennapod.preferences.lastIsStream"; /** * True if last played media was a video. */ - public static final String PREF_CURRENT_EPISODE_IS_VIDEO = "de.danoeh.antennapod.preferences.lastIsVideo"; + private static final String PREF_CURRENT_EPISODE_IS_VIDEO = "de.danoeh.antennapod.preferences.lastIsVideo"; /** * The current player status as int. */ - public static final String PREF_CURRENT_PLAYER_STATUS = "de.danoeh.antennapod.preferences.currentPlayerStatus"; + private static final String PREF_CURRENT_PLAYER_STATUS = "de.danoeh.antennapod.preferences.currentPlayerStatus"; /** * Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing. @@ -87,10 +92,6 @@ public class PlaybackPreferences implements SharedPreferences.OnSharedPreference } } - public static long getLastPlayedFeedId() { - return prefs.getLong(PREF_CURRENTLY_PLAYING_FEED_ID, -1); - } - public static long getCurrentlyPlayingMedia() { return prefs.getLong(PREF_CURRENTLY_PLAYING_MEDIA, NO_MEDIA_PLAYING); } @@ -119,4 +120,52 @@ public class PlaybackPreferences implements SharedPreferences.OnSharedPreference editor.putInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER); editor.apply(); } + + public static void writeMediaPlaying(Playable playable, PlayerStatus playerStatus, boolean stream) { + Log.d(TAG, "Writing playback preferences"); + SharedPreferences.Editor editor = prefs.edit(); + + if (playable == null) { + writeNoMediaPlaying(); + } else { + editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA, playable.getPlayableType()); + editor.putBoolean(PREF_CURRENT_EPISODE_IS_STREAM, stream); + editor.putBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, playable.getMediaType() == MediaType.VIDEO); + if (playable instanceof FeedMedia) { + FeedMedia fMedia = (FeedMedia) playable; + editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, fMedia.getItem().getFeed().getId()); + editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, fMedia.getId()); + } else { + editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, NO_MEDIA_PLAYING); + editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); + } + playable.writeToPreferences(editor); + } + editor.putInt(PREF_CURRENT_PLAYER_STATUS, getCurrentPlayerStatusAsInt(playerStatus)); + + editor.apply(); + } + + public static void writePlayerStatus(PlayerStatus playerStatus) { + Log.d(TAG, "Writing player status playback preferences"); + + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PREF_CURRENT_PLAYER_STATUS, getCurrentPlayerStatusAsInt(playerStatus)); + editor.apply(); + } + + private static int getCurrentPlayerStatusAsInt(PlayerStatus playerStatus) { + int playerStatusAsInt; + switch (playerStatus) { + case PLAYING: + playerStatusAsInt = PLAYER_STATUS_PLAYING; + break; + case PAUSED: + playerStatusAsInt = PLAYER_STATUS_PAUSED; + break; + default: + playerStatusAsInt = PLAYER_STATUS_OTHER; + } + return playerStatusAsInt; + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java index 0f3a9fcb3..eb87acb5f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.preferences; import android.content.Context; import android.content.SharedPreferences; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import java.util.concurrent.TimeUnit; diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index 80f0fd454..fc4166eb4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -2,10 +2,11 @@ package de.danoeh.antennapod.core.preferences; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.preference.PreferenceManager; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.v4.app.NotificationCompat; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; @@ -169,7 +170,7 @@ public class UserPreferences { * @return R.style.Theme_AntennaPod_Light or R.style.Theme_AntennaPod_Dark */ public static int getTheme() { - return readThemeValue(prefs.getString(PREF_THEME, "0")); + return readThemeValue(prefs.getString(PREF_THEME, "system")); } public static int getNoTitleTheme() { @@ -645,7 +646,7 @@ public class UserPreferences { .apply(); // when updating with an interval, we assume the user wants // to update *now* and then every 'hours' interval thereafter. - restartUpdateAlarm(); + AutoUpdateManager.restartUpdateAlarm(); } /** @@ -655,7 +656,7 @@ public class UserPreferences { prefs.edit() .putString(PREF_UPDATE_INTERVAL, hourOfDay + ":" + minute) .apply(); - restartUpdateAlarm(); + AutoUpdateManager.restartUpdateAlarm(); } public static void disableAutoUpdate() { @@ -696,14 +697,18 @@ public class UserPreferences { } private static int readThemeValue(String valueFromPrefs) { - switch (Integer.parseInt(valueFromPrefs)) { - case 0: + switch (valueFromPrefs) { + case "0": return R.style.Theme_AntennaPod_Light; - case 1: + case "1": return R.style.Theme_AntennaPod_Dark; - case 2: + case "2": return R.style.Theme_AntennaPod_TrueBlack; default: + int nightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + if (nightMode == Configuration.UI_MODE_NIGHT_YES) { + return R.style.Theme_AntennaPod_Dark; + } return R.style.Theme_AntennaPod_Light; } } @@ -901,26 +906,6 @@ public class UserPreferences { return getUpdateTimeOfDay().length == 2; } - public static void restartUpdateAlarm() { - if (isAutoUpdateDisabled()) { - AutoUpdateManager.disableAutoUpdate(); - } else if (isAutoUpdateTimeOfDay()) { - int[] timeOfDay = getUpdateTimeOfDay(); - Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay)); - AutoUpdateManager.restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1]); - } else { - long milliseconds = getUpdateInterval(); - AutoUpdateManager.restartUpdateIntervalAlarm(milliseconds); - } - } - - /** - * Reads episode cache size as it is saved in the episode_cache_size_values array. - */ - public static int readEpisodeCacheSize(String valueFromPrefs) { - return readEpisodeCacheSizeInternal(valueFromPrefs); - } - /** * Evaluates whether Cast support (Chromecast, Audio Cast, etc) is enabled on the preferences. */ diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java index 126f12247..af0a0d426 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java +++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java @@ -6,8 +6,7 @@ import android.content.Intent; import android.util.Log; import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.FeedUpdateUtils; +import de.danoeh.antennapod.core.util.download.AutoUpdateManager; /** * Refreshes all feeds when it receives an intent @@ -20,7 +19,8 @@ public class FeedUpdateReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { Log.d(TAG, "Received intent"); ClientConfig.initialize(context); - FeedUpdateUtils.startAutoUpdate(context, null); + + AutoUpdateManager.runOnce(); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java index b191dbf8b..b683f849c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java +++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java @@ -3,7 +3,7 @@ package de.danoeh.antennapod.core.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; import android.util.Log; import android.view.KeyEvent; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java index efdb96dc1..4443319e9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java @@ -1,18 +1,24 @@ package de.danoeh.antennapod.core.service; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; +import android.util.Log; + import androidx.work.Worker; import androidx.work.WorkerParameters; + import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.FeedUpdateUtils; -import org.awaitility.Awaitility; - -import java.util.concurrent.atomic.AtomicBoolean; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.core.util.download.AutoUpdateManager; public class FeedUpdateWorker extends Worker { + private static final String TAG = "FeedUpdateWorker"; + + public static final String PARAM_RUN_ONCE = "runOnce"; + public FeedUpdateWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @@ -20,16 +26,20 @@ public class FeedUpdateWorker extends Worker { @Override @NonNull public Result doWork() { + final boolean isRunOnce = getInputData().getBoolean(PARAM_RUN_ONCE, false); + Log.d(TAG, "doWork() : isRunOnce = " + isRunOnce); ClientConfig.initialize(getApplicationContext()); - AtomicBoolean finished = new AtomicBoolean(false); - FeedUpdateUtils.startAutoUpdate(getApplicationContext(), () -> finished.set(true)); - Awaitility.await().until(finished::get); + if (NetworkUtils.networkAvailable() && NetworkUtils.isFeedRefreshAllowed()) { + DBTasks.refreshAllFeeds(getApplicationContext()); + } else { + Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed"); + } - if (UserPreferences.isAutoUpdateTimeOfDay()) { + if (!isRunOnce && UserPreferences.isAutoUpdateTimeOfDay()) { // WorkManager does not allow to set specific time for repeated tasks. // We repeatedly schedule a OneTimeWorkRequest instead. - UserPreferences.restartUpdateAlarm(); + AutoUpdateManager.restartUpdateAlarm(); } return Result.success(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java index 5584991ca..b254cfc59 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java @@ -5,10 +5,10 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.SafeJobIntentService; -import android.support.v4.util.ArrayMap; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.app.SafeJobIntentService; +import androidx.collection.ArrayMap; import android.util.Log; import android.util.Pair; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java index 7938e262d..a3eb92c0c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java @@ -10,8 +10,8 @@ import android.graphics.Bitmap; import android.os.Build; import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.NonNull; -import android.support.v4.app.SafeJobIntentService; +import androidx.annotation.NonNull; +import androidx.core.app.SafeJobIntentService; import android.util.Log; import android.view.KeyEvent; import android.view.View; @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.receiver.PlayerWidget; import de.danoeh.antennapod.core.service.playback.PlaybackService; @@ -69,9 +70,7 @@ public class PlayerWidgetJobService extends SafeJobIntentService { } synchronized (waitUsingService) { - if (playbackService != null) { - updateViews(); - } + updateViews(); } if (playbackService != null) { @@ -145,9 +144,10 @@ public class PlayerWidgetJobService extends SafeJobIntentService { String progressString; if (playbackService != null) { - progressString = getProgressString(playbackService.getCurrentPosition(), playbackService.getDuration()); + progressString = getProgressString(playbackService.getCurrentPosition(), + playbackService.getDuration(), playbackService.getCurrentPlaybackSpeed()); } else { - progressString = getProgressString(media.getPosition(), media.getDuration()); + progressString = getProgressString(media.getPosition(), media.getDuration(), UserPreferences.getPlaybackSpeed(media)); } if (progressString != null) { @@ -211,9 +211,9 @@ public class PlayerWidgetJobService extends SafeJobIntentService { return PendingIntent.getBroadcast(this, 0, startingIntent, 0); } - private String getProgressString(int position, int duration) { + private String getProgressString(int position, int duration, float speed) { if (position > 0 && duration > 0) { - TimeSpeedConverter converter = new TimeSpeedConverter(playbackService.getCurrentPlaybackSpeed()); + TimeSpeedConverter converter = new TimeSpeedConverter(speed); position = converter.convert(position); duration = converter.convert(duration); return Converter.getDurationStringLong(position) + " / " diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java index 97007a214..2082b95d1 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.service.download; import android.os.Build; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Log; @@ -16,7 +16,9 @@ import java.net.Socket; import java.net.SocketAddress; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; @@ -28,6 +30,8 @@ import javax.net.ssl.X509TrustManager; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBWriter; +import okhttp3.CipherSuite; +import okhttp3.ConnectionSpec; import okhttp3.Credentials; import okhttp3.HttpUrl; import okhttp3.JavaNetCookieJar; @@ -138,9 +142,24 @@ public class AntennapodHttpClient { }); } } - if(16 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 21) { + if (16 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 21) { builder.sslSocketFactory(new CustomSslSocketFactory(), trustManager()); } + + if (Build.VERSION.SDK_INT < 21) { + // workaround for Android 4.x for certain web sites. + // see: https://github.com/square/okhttp/issues/4053#issuecomment-402579554 + List<CipherSuite> cipherSuites = new ArrayList<>(); + cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites()); + cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA); + cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA); + + ConnectionSpec legacyTls = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .cipherSuites(cipherSuites.toArray(new CipherSuite[0])) + .build(); + builder.connectionSpecs(Arrays.asList(legacyTls, ConnectionSpec.CLEARTEXT)); + } + return builder; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java index 48234c387..60591899d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java @@ -3,8 +3,8 @@ package de.danoeh.antennapod.core.service.download; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import de.danoeh.antennapod.core.feed.FeedFile; import de.danoeh.antennapod.core.util.URLChecker; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java index 787d465d8..296046031 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java @@ -12,8 +12,10 @@ import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; -import android.support.annotation.NonNull; -import android.support.v4.app.NotificationCompat; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.core.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -190,10 +192,8 @@ public class DownloadService extends Service { handleFailedDownload(status, downloader.getDownloadRequest()); if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - long id = status.getFeedfileId(); - FeedMedia media = DBReader.getFeedMedia(id); - FeedItem item; - if (media == null || (item = media.getItem()) == null) { + FeedItem item = getFeedItemFromId(status.getFeedfileId()); + if (item == null) { return; } boolean httpNotFound = status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR @@ -213,9 +213,8 @@ public class DownloadService extends Service { // if FeedMedia download has been canceled, fake FeedItem update // so that lists reload that it if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - FeedMedia media = DBReader.getFeedMedia(status.getFeedfileId()); - FeedItem item; - if (media == null || (item = media.getItem()) == null) { + FeedItem item = getFeedItemFromId(status.getFeedfileId()); + if (item == null) { return; } EventBus.getDefault().post(FeedItemEvent.updated(item)); @@ -386,6 +385,12 @@ public class DownloadService extends Service { Downloader d = getDownloader(url); if (d != null) { d.cancel(); + DownloadRequester.getInstance().removeDownload(d.getDownloadRequest()); + + FeedItem item = getFeedItemFromId(d.getDownloadRequest().getFeedfileId()); + if (item != null) { + EventBus.getDefault().post(FeedItemEvent.updated(item)); + } } else { Log.e(TAG, "Could not cancel download with url " + url); } @@ -430,12 +435,40 @@ public class DownloadService extends Service { queryDownloads(); } - private Downloader getDownloader(DownloadRequest request) { - if (!URLUtil.isHttpUrl(request.getSource()) && !URLUtil.isHttpsUrl(request.getSource())) { - Log.e(TAG, "Could not find appropriate downloader for " + request.getSource()); - return null; + @VisibleForTesting + public interface DownloaderFactory { + @Nullable + Downloader create(@NonNull DownloadRequest request); + } + + private static class DefaultDownloaderFactory implements DownloaderFactory { + @Nullable + @Override + public Downloader create(@NonNull DownloadRequest request) { + if (!URLUtil.isHttpUrl(request.getSource()) && !URLUtil.isHttpsUrl(request.getSource())) { + Log.e(TAG, "Could not find appropriate downloader for " + request.getSource()); + return null; + } + return new HttpDownloader(request); } - return new HttpDownloader(request); + } + + private static DownloaderFactory downloaderFactory = new DefaultDownloaderFactory(); + + @VisibleForTesting + public static DownloaderFactory getDownloaderFactory() { + return downloaderFactory; + } + + // public scope rather than package private, + // because androidTest put classes in the non-standard de.test.antennapod hierarchy + @VisibleForTesting + public static void setDownloaderFactory(DownloaderFactory downloaderFactory) { + DownloadService.downloaderFactory = downloaderFactory; + } + + private Downloader getDownloader(@NonNull DownloadRequest request) { + return downloaderFactory.create(request); } /** @@ -546,7 +579,7 @@ public class DownloadService extends Service { .setContentText(getText(R.string.authentication_notification_msg)) .setStyle(new NotificationCompat.BigTextStyle().bigText(getText(R.string.authentication_notification_msg) + ": " + resourceTitle)) - .setSmallIcon(R.drawable.ic_stat_authentication) + .setSmallIcon(R.drawable.ic_notification_key) .setAutoCancel(true) .setContentIntent(ClientConfig.downloadServiceCallbacks.getAuthentificationNotificationContentIntent(DownloadService.this, downloadRequest)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -578,6 +611,16 @@ public class DownloadService extends Service { syncExecutor.execute(new FailedDownloadHandler(status, request)); } + @Nullable + private FeedItem getFeedItemFromId(long id) { + FeedMedia media = DBReader.getFeedMedia(id); + if (media != null) { + return media.getItem(); + } else { + return null; + } + } + /** * Takes a single Feed, parses the corresponding file and refreshes * information in the manager @@ -654,6 +697,7 @@ public class DownloadService extends Service { Log.e(TAG, "FeedSyncThread was interrupted"); } catch (ExecutionException e) { Log.e(TAG, "ExecutionException in FeedSyncThread: " + e.getMessage()); + e.printStackTrace(); } } @@ -688,6 +732,7 @@ public class DownloadService extends Service { Log.e(TAG, "FeedSyncThread was interrupted"); } catch (ExecutionException e) { Log.e(TAG, "ExecutionException in FeedSyncThread: " + e.getMessage()); + e.printStackTrace(); } } @@ -983,14 +1028,17 @@ public class DownloadService extends Service { final FeedItem item = media.getItem(); try { + DBWriter.setFeedMedia(media).get(); + // we've received the media, we don't want to autodownload it again if (item != null) { item.setAutoDownload(false); + // setFeedItem() signals (via EventBus) that the item has been updated, + // so we do it after the enclosing media has been updated above, + // to ensure subscribers will get the updated FeedMedia as well DBWriter.setFeedItem(item).get(); } - DBWriter.setFeedMedia(media).get(); - if (item != null && UserPreferences.enqueueDownloadedEpisodes() && !DBTasks.isInQueue(DownloadService.this, item.getId())) { DBWriter.addQueueItem(DownloadService.this, item).get(); @@ -1058,7 +1106,13 @@ public class DownloadService extends Service { private final Runnable postDownloaderTask = new Runnable() { @Override public void run() { - List<Downloader> list = Collections.unmodifiableList(downloads); + List<Downloader> runningDownloads = new ArrayList<>(); + for (Downloader downloader : downloads) { + if (!downloader.cancelled) { + runningDownloads.add(downloader); + } + } + List<Downloader> list = Collections.unmodifiableList(runningDownloads); EventBus.getDefault().postSticky(DownloadEvent.refresh(list)); postHandler.postDelayed(postDownloaderTask, 1500); } @@ -1076,7 +1130,10 @@ public class DownloadService extends Service { private static String compileNotificationString(List<Downloader> downloads) { List<String> lines = new ArrayList<>(downloads.size()); for (Downloader downloader : downloads) { - StringBuilder line = new StringBuilder("\u2022 "); + if (downloader.cancelled) { + continue; + } + StringBuilder line = new StringBuilder("• "); DownloadRequest request = downloader.getDownloadRequest(); switch (request.getFeedfileType()) { case Feed.FEEDFILETYPE_FEED: diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java index 5debc6d05..675115bc7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.service.download; import android.database.Cursor; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.Date; @@ -193,10 +193,6 @@ public class DownloadStatus { this.cancelled = true; } - public void setCompletionDate(Date completionDate) { - this.completionDate = (Date) completionDate.clone(); - } - public void setId(long id) { this.id = id; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java index 38b93eab8..02dc17301 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.service.download; import android.content.Context; import android.net.wifi.WifiManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.concurrent.Callable; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java index c27cefc10..8abfa3da3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.service.download; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Log; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java index c4096c3da..0d59a1b7e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.service.download; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import java.net.Proxy; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java index f20525f73..c988ef70e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.service.playback; import android.content.Context; import android.net.Uri; +import android.util.Log; import android.view.SurfaceHolder; import com.google.android.exoplayer2.C; @@ -22,28 +23,52 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.util.Util; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import org.antennapod.audio.MediaPlayer; import de.danoeh.antennapod.core.util.playback.IPlayer; +import java.util.concurrent.TimeUnit; public class ExoPlayerWrapper implements IPlayer { + private static final String TAG = "ExoPlayerWrapper"; private final Context mContext; + private final Disposable bufferingUpdateDisposable; private SimpleExoPlayer mExoPlayer; private MediaSource mediaSource; private MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener; private MediaPlayer.OnCompletionListener audioCompletionListener; private MediaPlayer.OnErrorListener audioErrorListener; + private MediaPlayer.OnBufferingUpdateListener bufferingUpdateListener; + ExoPlayerWrapper(Context context) { mContext = context; mExoPlayer = createPlayer(); + + bufferingUpdateDisposable = Observable.interval(2, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(aLong -> { + if (bufferingUpdateListener != null) { + bufferingUpdateListener.onBufferingUpdate(null, mExoPlayer.getBufferedPercentage()); + } + }); } private SimpleExoPlayer createPlayer() { + DefaultLoadControl.Builder loadControl = new DefaultLoadControl.Builder(); + loadControl.setBufferDurationsMs(30000, 120000, + DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS, + DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); + loadControl.setBackBuffer(UserPreferences.getRewindSecs() * 1000 + 500, true); SimpleExoPlayer p = ExoPlayerFactory.newSimpleInstance(mContext, new DefaultRenderersFactory(mContext), - new DefaultTrackSelector(), new DefaultLoadControl()); + new DefaultTrackSelector(), loadControl.createDefaultLoadControl()); p.setSeekParameters(SeekParameters.PREVIOUS_SYNC); p.addListener(new Player.EventListener() { @Override @@ -148,12 +173,14 @@ public class ExoPlayerWrapper implements IPlayer { @Override public void release() { + bufferingUpdateDisposable.dispose(); if (mExoPlayer != null) { mExoPlayer.release(); } audioSeekCompleteListener = null; audioCompletionListener = null; audioErrorListener = null; + bufferingUpdateListener = null; } @Override @@ -180,8 +207,13 @@ public class ExoPlayerWrapper implements IPlayer { @Override public void setDataSource(String s) throws IllegalArgumentException, IllegalStateException { - DataSource.Factory dataSourceFactory = - new DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext, mContext.getPackageName()), null); + Log.d(TAG, "setDataSource: " + s); + DefaultHttpDataSourceFactory httpDataSourceFactory = new DefaultHttpDataSourceFactory( + Util.getUserAgent(mContext, mContext.getPackageName()), null, + DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, + true); + DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(mContext, null, httpDataSourceFactory); ExtractorMediaSource.Factory f = new ExtractorMediaSource.Factory(dataSourceFactory); mediaSource = f.createMediaSource(Uri.parse(s)); } @@ -247,4 +279,8 @@ public class ExoPlayerWrapper implements IPlayer { } return mExoPlayer.getVideoFormat().height; } + + void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener bufferingUpdateListener) { + this.bufferingUpdateListener = bufferingUpdateListener; + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java index 72f5f183a..c982cb1e9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java @@ -6,7 +6,7 @@ import android.media.AudioFocusRequest; import android.media.AudioManager; import android.os.Build; import android.os.PowerManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.telephony.TelephonyManager; import android.util.Log; import android.util.Pair; @@ -585,7 +585,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { } playerLock.unlock(); - Log.d(TAG, "getPosition() -> " + retVal); return retVal; } @@ -1026,6 +1025,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { ExoPlayerWrapper ap = (ExoPlayerWrapper) mp; ap.setOnCompletionListener(audioCompletionListener); ap.setOnSeekCompleteListener(audioSeekCompleteListener); + ap.setOnBufferingUpdateListener(audioBufferingUpdateListener); ap.setOnErrorListener(audioErrorListener); } else { Log.w(TAG, "Unknown media player: " + mp); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index e479a7711..2eb9c92e4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -12,7 +12,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; @@ -22,12 +21,12 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Vibrator; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; import android.support.v4.media.MediaBrowserCompat; -import android.support.v4.media.MediaBrowserServiceCompat; +import androidx.media.MediaBrowserServiceCompat; import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; @@ -45,10 +44,12 @@ import com.bumptech.glide.request.target.Target; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.event.MessageEvent; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.ServiceEvent; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; @@ -66,7 +67,6 @@ 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.FeedSearcher; -import de.danoeh.antennapod.core.util.IntList; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.QueueAccess; @@ -74,6 +74,9 @@ import de.danoeh.antennapod.core.util.gui.NotificationUtils; import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import org.greenrobot.eventbus.EventBus; /** @@ -212,6 +215,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { private PlaybackServiceTaskManager taskManager; private PlaybackServiceFlavorHelper flavorHelper; private PlaybackServiceStateManager stateManager; + private Disposable positionEventTimer; /** * Used for Lollipop notifications, Android Wear, and Android Auto. @@ -331,8 +335,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { isRunning = false; currentMediaType = MediaType.UNKNOWN; - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(prefListener); + cancelPositionObserver(); + PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(prefListener); if (mediaSession != null) { mediaSession.release(); } @@ -451,7 +455,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { notificationBuilder.loadIcon(getPlayable()); } } - startForeground(NOTIFICATION_ID, notificationBuilder.build()); + stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build()); } NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); @@ -566,7 +570,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); + mediaPlayer.pause(!UserPreferences.isPersistNotify(), false); } else if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { mediaPlayer.resume(); } else if (status == PlayerStatus.PREPARING) { @@ -590,7 +594,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { return true; case KeyEvent.KEYCODE_MEDIA_PAUSE: if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); + mediaPlayer.pause(!UserPreferences.isPersistNotify(), false); } return true; @@ -717,7 +721,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { updateMediaSession(newInfo.playerStatus); switch (newInfo.playerStatus) { case INITIALIZED: - writePlaybackPreferences(); + PlaybackPreferences.writeMediaPlaying(mediaPlayer.getPSMPInfo().playable, + mediaPlayer.getPSMPInfo().playerStatus, mediaPlayer.isStreaming()); break; case PREPARED: @@ -734,7 +739,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { // remove notification on pause stateManager.stopForeground(true); } - writePlayerStatusPlaybackPreferences(); + cancelPositionObserver(); + PlaybackPreferences.writePlayerStatus(mediaPlayer.getPlayerStatus()); break; case STOPPED: @@ -743,8 +749,9 @@ public class PlaybackService extends MediaBrowserServiceCompat { break; case PLAYING: - writePlayerStatusPlaybackPreferences(); + PlaybackPreferences.writePlayerStatus(mediaPlayer.getPlayerStatus()); setupNotification(newInfo); + setupPositionUpdater(); stateManager.validStartCommandWasReceived(); // set sleep timer if auto-enabled if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING && @@ -1023,85 +1030,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { EventBus.getDefault().post(new MessageEvent(getString(R.string.sleep_timer_disabled_label))); } - private int getCurrentPlayerStatusAsInt(PlayerStatus playerStatus) { - int playerStatusAsInt; - switch (playerStatus) { - case PLAYING: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PLAYING; - break; - case PAUSED: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PAUSED; - break; - default: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_OTHER; - } - return playerStatusAsInt; - } - - private void writePlaybackPreferences() { - Log.d(TAG, "Writing playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); - MediaType mediaType = mediaPlayer.getCurrentMediaType(); - boolean stream = mediaPlayer.isStreaming(); - int playerStatus = getCurrentPlayerStatusAsInt(info.playerStatus); - - if (info.playable != null) { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - info.playable.getPlayableType()); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - stream); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, - mediaType == MediaType.VIDEO); - if (info.playable instanceof FeedMedia) { - FeedMedia fMedia = (FeedMedia) info.playable; - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - fMedia.getItem().getFeed().getId()); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - fMedia.getId()); - } else { - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - info.playable.writeToPreferences(editor); - } else { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus); - - editor.commit(); - } - - private void writePlayerStatusPlaybackPreferences() { - Log.d(TAG, "Writing player status playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - int playerStatus = getCurrentPlayerStatusAsInt(mediaPlayer.getPlayerStatus()); - - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus); - - editor.commit(); - } - private void sendNotificationBroadcast(int type, int code) { Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); @@ -1656,6 +1584,24 @@ public class PlaybackService extends MediaBrowserServiceCompat { return mediaPlayer.getVideoSize(); } + private void setupPositionUpdater() { + if (positionEventTimer != null) { + positionEventTimer.dispose(); + } + + Log.d(TAG, "Setting up position observer"); + positionEventTimer = Observable.interval(1, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(aLong -> + EventBus.getDefault().post(new PlaybackPositionEvent(getCurrentPosition(), getDuration()))); + } + + private void cancelPositionObserver() { + if (positionEventTimer != null) { + positionEventTimer.dispose(); + } + } + private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() { private static final String TAG = "MediaSessionCompat"; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java index b8198fa63..bc009e2bc 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java @@ -2,8 +2,8 @@ package de.danoeh.antennapod.core.service.playback; import android.content.Context; import android.net.wifi.WifiManager; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import android.util.Log; import android.util.Pair; import android.view.SurfaceHolder; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java index 0e7339ac4..b809ad659 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java @@ -1,13 +1,18 @@ package de.danoeh.antennapod.core.service.playback; +import android.annotation.TargetApi; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.v4.app.NotificationCompat; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.content.ContextCompat; import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; import android.view.KeyEvent; @@ -99,12 +104,32 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build public void loadDefaultIcon() { if (defaultIcon == null) { - defaultIcon = BitmapFactory.decodeResource(context.getResources(), - ClientConfig.playbackServiceCallbacks.getNotificationIconResource(context)); + defaultIcon = getBitmap(context, R.drawable.notification_default_large_icon); } setLargeIcon(defaultIcon); } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static Bitmap getBitmap(VectorDrawable vectorDrawable) { + Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), + vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + vectorDrawable.draw(canvas); + return bitmap; + } + + private static Bitmap getBitmap(Context context, int drawableId) { + Drawable drawable = ContextCompat.getDrawable(context, drawableId); + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable) { + return getBitmap((VectorDrawable) drawable); + } else { + return null; + } + } + private void addActions(MediaSessionCompat.Token mediaSessionToken, PlayerStatus playerStatus, boolean isCasting) { IntList compactActionList = new IntList(); @@ -115,7 +140,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build stopCastingIntent.putExtra(PlaybackService.EXTRA_CAST_DISCONNECT, true); PendingIntent stopCastingPendingIntent = PendingIntent.getService(context, numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT); - addAction(R.drawable.ic_media_cast_disconnect, + addAction(R.drawable.ic_notification_cast_off, context.getString(R.string.cast_disconnect_label), stopCastingPendingIntent); numActions++; @@ -124,7 +149,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build // always let them rewind PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_REWIND, numActions); - addAction(android.R.drawable.ic_media_rew, context.getString(R.string.rewind_label), rewindButtonPendingIntent); + addAction(R.drawable.ic_notification_fast_rewind, context.getString(R.string.rewind_label), rewindButtonPendingIntent); if (UserPreferences.showRewindOnCompactNotification()) { compactActionList.add(numActions); } @@ -133,14 +158,14 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build if (playerStatus == PlayerStatus.PLAYING) { PendingIntent pauseButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_PAUSE, numActions); - addAction(android.R.drawable.ic_media_pause, //pause action + addAction(R.drawable.ic_notification_pause, //pause action context.getString(R.string.pause_label), pauseButtonPendingIntent); compactActionList.add(numActions++); } else { PendingIntent playButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_PLAY, numActions); - addAction(android.R.drawable.ic_media_play, //play action + addAction(R.drawable.ic_notification_play, //play action context.getString(R.string.play_label), playButtonPendingIntent); compactActionList.add(numActions++); @@ -149,7 +174,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build // ff follows play, then we have skip (if it's present) PendingIntent ffButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions); - addAction(android.R.drawable.ic_media_ff, context.getString(R.string.fast_forward_label), ffButtonPendingIntent); + addAction(R.drawable.ic_notification_fast_forward, context.getString(R.string.fast_forward_label), ffButtonPendingIntent); if (UserPreferences.showFastForwardOnCompactNotification()) { compactActionList.add(numActions); } @@ -158,7 +183,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build if (UserPreferences.isFollowQueue()) { PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_NEXT, numActions); - addAction(android.R.drawable.ic_media_next, + addAction(R.drawable.ic_notification_skip, context.getString(R.string.skip_episode_label), skipButtonPendingIntent); if (UserPreferences.showSkipOnCompactNotification()) { @@ -169,7 +194,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_STOP, numActions); - setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle() + setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSessionToken) .setShowActionsInCompactView(compactActionList.toArray()) .setShowCancelButton(true) diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java index 68839023e..1cf271a29 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java @@ -4,9 +4,12 @@ import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.Vibrator; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; + import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -14,13 +17,12 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.playback.Playable; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; import io.reactivex.Completable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -102,6 +104,34 @@ public class PlaybackServiceTaskManager { } } + @Subscribe + public void onEvent(FeedItemEvent event) { + // Use case: when an item in the queue has been downloaded, + // listening to the event to ensure the downloaded item will be used. + Log.d(TAG, "onEvent(FeedItemEvent " + event + ")"); + + for (FeedItem item : event.items) { + if (isItemInQueue(item.getId())) { + Log.d(TAG, "onEvent(FeedItemEvent) - some item (" + item.getId() + ") in the queue has been updated (usually downloaded). Refresh the queue."); + cancelQueueLoader(); + loadQueue(); + return; + } + } + } + + private boolean isItemInQueue(long itemId) { + List<FeedItem> queue = getQueueIfLoaded(); + if (queue != null) { + for (FeedItem item : queue) { + if (item.getId() == itemId) { + return true; + } + } + } + return false; + } + /** * Returns the queue if it is already loaded or null if it hasn't been loaded yet. * In order to wait until the queue has been loaded, use getQueue() diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java index c63ac4416..b88793e87 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.core.storage; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import android.util.Log; import java.util.ArrayList; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java index 04b200699..dfe0b5201 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.storage; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import java.util.ArrayList; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index 70d3ba9dd..948a21efc 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -1,9 +1,9 @@ package de.danoeh.antennapod.core.storage; import android.database.Cursor; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.util.ArrayMap; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.ArrayMap; import android.text.TextUtils; import android.util.Log; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index e68bff16e..46fa4b99c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -3,7 +3,8 @@ package de.danoeh.antennapod.core.storage; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; -import android.support.annotation.Nullable; +import android.os.Looper; +import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; @@ -144,58 +145,36 @@ public final class DBTasks { private static final AtomicBoolean isRefreshing = new AtomicBoolean(false); /** - * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still + * Refreshes all feeds. + * It must not be from the main thread. + * This method might ignore subsequent calls if it is still * enqueuing Feeds for download from a previous call * * @param context Might be used for accessing the database - * @param feeds List of Feeds that should be refreshed. */ - public static void refreshAllFeeds(final Context context, final List<Feed> feeds) { - refreshAllFeeds(context, feeds, null); - } - - /** - * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still - * enqueuing Feeds for download from a previous call - * - * @param context Might be used for accessing the database - * @param feeds List of Feeds that should be refreshed. - * @param callback Called after everything was added enqueued for download. Might be null. - */ - public static void refreshAllFeeds(final Context context, final List<Feed> feeds, @Nullable Runnable callback) { + public static void refreshAllFeeds(final Context context) { if (!isRefreshing.compareAndSet(false, true)) { Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked"); return; } - new Thread(() -> { - if (feeds != null) { - refreshFeeds(context, feeds); - } else { - refreshFeeds(context, DBReader.getFeedList()); - } - isRefreshing.set(false); + if (Looper.myLooper() == Looper.getMainLooper()) { + throw new IllegalStateException("DBTasks.refreshAllFeeds() must not be called from the main thread."); + } - SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE); - prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply(); + refreshFeeds(context, DBReader.getFeedList()); + isRefreshing.set(false); - if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) { - GpodnetSyncService.sendSyncIntent(context); - } - // Note: automatic download of episodes will be done but not here. - // Instead it is done after all feeds have been refreshed (asynchronously), - // in DownloadService.onDestroy() - // See Issue #2577 for the details of the rationale + SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE); + prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply(); - if (callback != null) { - callback.run(); - } - }).start(); - } - - public static long getLastRefreshAllFeedsTimeMillis(final Context context) { - SharedPreferences prefs = context.getSharedPreferences(DBTasks.PREF_NAME, MODE_PRIVATE); - return prefs.getLong(DBTasks.PREF_LAST_REFRESH, 0); + if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) { + GpodnetSyncService.sendSyncIntent(context); + } + // Note: automatic download of episodes will be done but not here. + // Instead it is done after all feeds have been refreshed (asynchronously), + // in DownloadService.onDestroy() + // See Issue #2577 for the details of the rationale } /** @@ -462,10 +441,9 @@ public final class DBTasks { /** * Get a FeedItem by its identifying value. */ - private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, - String identifier) { + private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, String identifier) { for (FeedItem item : feed.getItems()) { - if (item.getIdentifyingValue().equals(identifier)) { + if (TextUtils.equals(item.getIdentifyingValue(), identifier)) { return item; } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java index 4f0ee70ef..11d90e57c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java @@ -2,10 +2,7 @@ package de.danoeh.antennapod.core.storage; import android.app.backup.BackupManager; import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import org.greenrobot.eventbus.EventBus; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java index 892a4675a..71f6845c5 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java @@ -3,8 +3,8 @@ package de.danoeh.antennapod.core.storage; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; import android.text.TextUtils; import android.util.Log; import android.webkit.URLUtil; @@ -34,7 +34,6 @@ import de.danoeh.antennapod.core.util.URLChecker; public class DownloadRequester { private static final String TAG = "DownloadRequester"; - public static final String IMAGE_DOWNLOADPATH = "images/"; private static final String FEED_DOWNLOADPATH = "cache/"; private static final String MEDIA_DOWNLOADPATH = "media/"; @@ -274,10 +273,6 @@ public class DownloadRequester { return item.getDownload_url() != null && downloads.containsKey(item.getDownload_url()); } - public synchronized DownloadRequest getDownload(String downloadUrl) { - return downloads.get(downloadUrl); - } - /** * Checks if feedfile with the given download url is in the downloads list */ diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java index c6620a90e..0c8d20007 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.storage; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.Collections; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 0c93590e2..4de83f548 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -13,6 +13,8 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -25,8 +27,6 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import de.danoeh.antennapod.core.R; -import de.danoeh.antennapod.core.event.ProgressEvent; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; @@ -35,7 +35,6 @@ import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.util.LongIntMap; -import org.greenrobot.eventbus.EventBus; import static de.danoeh.antennapod.core.feed.FeedMedia.LAST_PLAYBACK_SPEED_UNSET; import static de.danoeh.antennapod.core.feed.FeedPreferences.SPEED_USE_GLOBAL; @@ -55,16 +54,10 @@ public class PodDBAdapter { */ private static final int IN_OPERATOR_MAXIMUM = 800; - /** - * Maximum number of entries per search request. - */ - public static final int SEARCH_LIMIT = 30; - // Key-constants public static final String KEY_ID = "id"; public static final String KEY_TITLE = "title"; public static final String KEY_CUSTOM_TITLE = "custom_title"; - public static final String KEY_NAME = "name"; public static final String KEY_LINK = "link"; public static final String KEY_DESCRIPTION = "description"; public static final String KEY_FILE_URL = "file_url"; @@ -1410,13 +1403,6 @@ public class PodDBAdapter { return db.rawQuery(query, null); } - - public static final int IDX_FEEDSTATISTICS_FEED = 0; - public static final int IDX_FEEDSTATISTICS_NUM_ITEMS = 1; - public static final int IDX_FEEDSTATISTICS_NEW_ITEMS = 2; - public static final int IDX_FEEDSTATISTICS_LATEST_EPISODE = 3; - public static final int IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES = 4; - /** * Select number of items, new items, the date of the latest episode and the number of episodes in progress. The result * is sorted by the title of the feed. @@ -1501,11 +1487,9 @@ public class PodDBAdapter { @Override public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - EventBus.getDefault().post(ProgressEvent.start(context.getString(R.string.progress_upgrading_database))); Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " + newVersion + "."); DBUpgrader.upgrade(db, oldVersion, newVersion); - EventBus.getDefault().post(ProgressEvent.end()); } } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java index 1cd05aa26..608cade88 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java +++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.syndication.handler; -import android.support.v4.util.ArrayMap; +import androidx.collection.ArrayMap; import java.util.ArrayList; import java.util.Map; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java index e6d4ed6b7..300e0038a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.util; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.Log; import org.apache.commons.io.IOUtils; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java b/core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java deleted file mode 100644 index 69dc38895..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java +++ /dev/null @@ -1,117 +0,0 @@ -/* Adapted from: http://thinking-in-code.blogspot.com/2008/11/duck-typing-in-java-using-dynamic.html */ - -package de.danoeh.antennapod.core.util; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -import de.danoeh.antennapod.core.BuildConfig; - -/** - * Allows "duck typing" or dynamic invocation based on method signature rather - * than type hierarchy. In other words, rather than checking whether something - * IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck. - * - * To use first use the coerce static method to indicate the object you want to - * do Duck Typing for, then specify an interface to the to method which you want - * to coerce the type to, e.g: - * - * public interface Foo { void aMethod(); } class Bar { ... public void - * aMethod() { ... } ... } Bar bar = ...; Foo foo = - * DuckType.coerce(bar).to(Foo.class); foo.aMethod(); - * - * - */ -public class DuckType { - - private final Object objectToCoerce; - - private DuckType(Object objectToCoerce) { - this.objectToCoerce = objectToCoerce; - } - - private class CoercedProxy implements InvocationHandler { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Method delegateMethod = findMethodBySignature(method); - assert delegateMethod != null; - return delegateMethod.invoke(DuckType.this.objectToCoerce, args); - } - } - - /** - * Specify the duck typed object to coerce. - * - * @param object - * the object to coerce - * @return - */ - public static DuckType coerce(Object object) { - return new DuckType(object); - } - - /** - * Coerce the Duck Typed object to the given interface providing it - * implements all the necessary methods. - * - * @param - * @param iface - * @return an instance of the given interface that wraps the duck typed - * class - * @throws ClassCastException - * if the object being coerced does not implement all the - * methods in the given interface. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public <T> T to(Class iface) { - if (BuildConfig.DEBUG && !iface.isInterface()) throw new AssertionError("cannot coerce object to a class, must be an interface"); - if (isA(iface)) { - return (T) iface.cast(objectToCoerce); - } - if (quacksLikeA(iface)) { - return generateProxy(iface); - } - throw new ClassCastException("Could not coerce object of type " + objectToCoerce.getClass() + " to " + iface); - } - - @SuppressWarnings("rawtypes") - private boolean isA(Class iface) { - return objectToCoerce.getClass().isInstance(iface); - } - - /** - * Determine whether the duck typed object can be used with the given - * interface. - * - * @param Type - * of the interface to check. - * @param iface - * Interface class to check - * @return true if the object will support all the methods in the interface, - * false otherwise. - */ - @SuppressWarnings("rawtypes") - private boolean quacksLikeA(Class iface) { - for (Method method : iface.getMethods()) { - if (findMethodBySignature(method) == null) { - return false; - } - } - return true; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private <T> T generateProxy(Class iface) { - return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new CoercedProxy()); - } - - private Method findMethodBySignature(Method method) { - try { - return objectToCoerce.getClass().getMethod(method.getName(), method.getParameterTypes()); - } catch (NoSuchMethodException e) { - return null; - } - } - -}
\ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java index 826c06822..a8206d3bd 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java @@ -7,19 +7,6 @@ import de.danoeh.antennapod.core.feed.FeedItem; public class FeedItemUtil { private FeedItemUtil(){} - public static int indexOfItemWithDownloadUrl(List<FeedItem> items, String downloadUrl) { - if(items == null) { - return -1; - } - for(int i=0; i < items.size(); i++) { - FeedItem item = items.get(i); - if(item.hasMedia() && item.getMedia().getDownload_url().equals(downloadUrl)) { - return i; - } - } - return -1; - } - public static int indexOfItemWithId(List<FeedItem> items, long id) { for(int i=0; i < items.size(); i++) { FeedItem item = items.get(i); @@ -40,17 +27,6 @@ public class FeedItemUtil { return -1; } - public static long[] getIds(FeedItem... items) { - if(items == null || items.length == 0) { - return new long[0]; - } - long[] result = new long[items.length]; - for(int i=0; i < items.length; i++) { - result[i] = items[i].getId(); - } - return result; - } - public static long[] getIds(List<FeedItem> items) { if(items == null || items.size() == 0) { return new long[0]; @@ -62,20 +38,6 @@ public class FeedItemUtil { return result; } - public static boolean containsAnyId(List<FeedItem> items, long[] ids) { - if(items == null || items.size() == 0) { - return false; - } - for(FeedItem item : items) { - for(long id : ids) { - if(item.getId() == id) { - return true; - } - } - } - return false; - } - /** * Get the link for the feed item for the purpose of Share. It fallbacks to * use the feed's link if the named feed item has no link. diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java deleted file mode 100644 index b425687ae..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.content.Context; -import android.util.Log; - -import org.awaitility.core.ConditionTimeoutException; - -import java.util.concurrent.TimeUnit; - -import de.danoeh.antennapod.core.storage.DBTasks; - -import static org.awaitility.Awaitility.with; - -public class FeedUpdateUtils { - private static final String TAG = "FeedUpdateUtils"; - - private FeedUpdateUtils() {} - - public static void startAutoUpdate(Context context, Runnable callback) { - // the network check is blocking for possibly a long time: so run the logic - // in a separate thread to prevent the code blocking the callers - final Runnable runnable = () -> { - try { - with().pollInterval(1, TimeUnit.SECONDS) - .await() - .atMost(10, TimeUnit.SECONDS) - .until(() -> NetworkUtils.networkAvailable() && NetworkUtils.isFeedRefreshAllowed()); - DBTasks.refreshAllFeeds(context, null, callback); - } catch (ConditionTimeoutException ignore) { - Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed"); - } - }; - new Thread(runnable).start(); - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java deleted file mode 100644 index 29d095cd2..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import java.util.Comparator; - -import de.danoeh.antennapod.core.feed.Feed; - -/** Compares the title of two feeds for sorting. */ -class FeedtitleComparator implements Comparator<Feed> { - - @Override - public int compare(Feed lhs, Feed rhs) { - return lhs.getTitle().compareToIgnoreCase(rhs.getTitle()); - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java index e81ab47ed..656b518bf 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java @@ -1,13 +1,20 @@ package de.danoeh.antennapod.core.util; +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.util.Log; +import android.widget.Toast; +import de.danoeh.antennapod.core.R; import java.util.List; public class IntentUtils { + private static final String TAG = "IntentUtils"; + private IntentUtils(){} /* @@ -28,4 +35,13 @@ public class IntentUtils { context.sendBroadcast(new Intent(action).setPackage(context.getPackageName())); } + public static void openInBrowser(Context context, String url) { + try { + Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(myIntent); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, R.string.pref_no_browser_found, Toast.LENGTH_LONG).show(); + Log.e(TAG, Log.getStackTraceString(e)); + } + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java index 90e0b0981..20af6415e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.util; -import android.support.v4.util.ArrayMap; +import androidx.collection.ArrayMap; import java.nio.charset.Charset; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java index ca48c9bc9..a9e46e42c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java @@ -5,7 +5,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.support.v4.net.ConnectivityManagerCompat; +import androidx.core.net.ConnectivityManagerCompat; import android.text.TextUtils; import android.util.Log; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java b/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java index 0fe11ec53..37f12c01c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java @@ -77,7 +77,7 @@ public final class Optional<T> { * @param <T> Type of the non-existent value * @return an empty {@code Optional} */ - public static<T> Optional<T> empty() { + public static <T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java index 37172d042..0c21ca393 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java @@ -164,7 +164,7 @@ public class QueueSorter { Collections.sort(feedItems, itemComparator); if (spread == 0) { spread = feedItems.size(); - } else if (feedItems.size() % spread != 0){ + } else if (spread % feedItems.size() != 0){ spread *= feedItems.size(); } } @@ -180,6 +180,9 @@ public class QueueSorter { Map<Long, List<FeedItem>> spreadItems = new HashMap<>(); for (List<FeedItem> feedItems : feeds) { long thisSpread = spread / feedItems.size(); + if (thisSpread == 0) { + thisSpread = 1; + } // Starting from 0 ensures we front-load, so the queue starts with one episode from // each feed in the queue long itemSpread = 0; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java index 5ae00460e..3e9e8327e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java @@ -6,7 +6,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; -import android.support.v4.content.FileProvider; +import androidx.core.content.FileProvider; import android.util.Log; import java.io.File; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java index 1da7a5c50..eabaaa828 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.core.util; import android.content.Context; -import android.support.annotation.AttrRes; -import android.support.annotation.ColorInt; +import androidx.annotation.AttrRes; +import androidx.annotation.ColorInt; import android.util.TypedValue; public class ThemeUtils { diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java index ffc6a6e28..7e3870a20 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.util; import android.net.Uri; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import de.danoeh.antennapod.core.BuildConfig; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java deleted file mode 100644 index 56a684475..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.danoeh.antennapod.core.util.comparator; - -import java.util.Comparator; - -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.SearchResult; - -public class SearchResultValueComparator implements Comparator<SearchResult> { - - /** - * Compare items based, first, on where they were found (ie. title, chapters, or show notes). - * If they were found in the same section, then compare based on the title, in lexicographic - * order. This is still not ideal since, for example, "#12 Example A" would be considered - * before "#8 Example B" due to the fact that "8" has a larger unicode value than "1" - */ - @Override - public int compare(SearchResult lhs, SearchResult rhs) { - int value = rhs.getValue() - lhs.getValue(); - if (value == 0 && lhs.getComponent() instanceof FeedItem && rhs.getComponent() instanceof FeedItem) { - String lhsTitle = ((FeedItem) lhs.getComponent()).getTitle(); - String rhsTitle = ((FeedItem) rhs.getComponent()).getTitle(); - return lhsTitle.compareTo(rhsTitle); - } - return value; - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java index 412b150fa..e093a7e00 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java @@ -1,21 +1,29 @@ package de.danoeh.antennapod.core.util.download; +import android.content.Context; +import androidx.annotation.NonNull; import android.util.Log; + import androidx.work.Constraints; +import androidx.work.Data; import androidx.work.ExistingPeriodicWorkPolicy; import androidx.work.ExistingWorkPolicy; import androidx.work.NetworkType; import androidx.work.OneTimeWorkRequest; import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.FeedUpdateWorker; +import java.util.Arrays; import java.util.Calendar; import java.util.concurrent.TimeUnit; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.FeedUpdateWorker; +import de.danoeh.antennapod.core.storage.DBTasks; + public class AutoUpdateManager { - private static final String WORK_ID_FEED_UPDATE = FeedUpdateWorker.class.getName(); + private static final String WORK_ID_FEED_UPDATE = "de.danoeh.antennapod.core.service.FeedUpdateWorker"; + private static final String WORK_ID_FEED_UPDATE_ONCE = WORK_ID_FEED_UPDATE + "Once"; private static final String TAG = "AutoUpdateManager"; private AutoUpdateManager() { @@ -23,9 +31,25 @@ public class AutoUpdateManager { } /** + * Start / restart periodic auto feed refresh + */ + public static void restartUpdateAlarm() { + if (UserPreferences.isAutoUpdateDisabled()) { + disableAutoUpdate(); + } else if (UserPreferences.isAutoUpdateTimeOfDay()) { + int[] timeOfDay = UserPreferences.getUpdateTimeOfDay(); + Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay)); + restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1]); + } else { + long milliseconds = UserPreferences.getUpdateInterval(); + restartUpdateIntervalAlarm(milliseconds); + } + } + + /** * Sets the interval in which the feeds are refreshed automatically */ - public static void restartUpdateIntervalAlarm(long intervalMillis) { + private static void restartUpdateIntervalAlarm(long intervalMillis) { Log.d(TAG, "Restarting update alarm."); PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(FeedUpdateWorker.class, @@ -40,7 +64,7 @@ public class AutoUpdateManager { /** * Sets time of day the feeds are refreshed automatically */ - public static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute) { + private static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute) { Log.d(TAG, "Restarting update alarm."); Calendar now = Calendar.getInstance(); @@ -60,6 +84,41 @@ public class AutoUpdateManager { WorkManager.getInstance().enqueueUniqueWork(WORK_ID_FEED_UPDATE, ExistingWorkPolicy.REPLACE, workRequest); } + /** + * Run auto feed refresh once in background, as soon as what OS scheduling allows. + * + * Callers from UI should use {@link #runImmediate(Context)}, as it will guarantee + * the refresh be run immediately. + */ + public static void runOnce() { + Log.d(TAG, "Run auto update once, as soon as OS allows."); + + OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class) + .setConstraints(getConstraints()) + .setInitialDelay(0L, TimeUnit.MILLISECONDS) + .setInputData(new Data.Builder() + .putBoolean(FeedUpdateWorker.PARAM_RUN_ONCE, true) + .build() + ) + .build(); + + WorkManager.getInstance().enqueueUniqueWork(WORK_ID_FEED_UPDATE_ONCE, ExistingWorkPolicy.REPLACE, workRequest); + + } + + /** + /** + * Run auto feed refresh once in background immediately, using its own thread. + * + * Callers where the additional threads is not suitable should use {@link #runOnce()} + */ + public static void runImmediate(@NonNull Context context) { + Log.d(TAG, "Run auto update immediately in background."); + new Thread(() -> { + DBTasks.refreshAllFeeds(context.getApplicationContext()); + }, "ManualRefreshAllFeeds").start(); + } + public static void disableAutoUpdate() { WorkManager.getInstance().cancelUniqueWork(WORK_ID_FEED_UPDATE); } @@ -74,4 +133,5 @@ public class AutoUpdateManager { } return constraints.build(); } + } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java b/core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java index 12f0c1c6e..223104d2e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java @@ -1,12 +1,9 @@ package de.danoeh.antennapod.core.util.exception; import android.util.Log; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; import io.reactivex.exceptions.UndeliverableException; import io.reactivex.plugins.RxJavaPlugins; -import java.io.InterruptedIOException; - public class RxJavaErrorHandlerSetup { private RxJavaErrorHandlerSetup() { @@ -14,21 +11,14 @@ public class RxJavaErrorHandlerSetup { } public static void setupRxJavaErrorHandler() { - RxJavaPlugins.setErrorHandler(originalCause -> { - Throwable e = originalCause; + RxJavaPlugins.setErrorHandler(e -> { if (e instanceof UndeliverableException) { - e = e.getCause(); - } - if (e instanceof GpodnetServiceException) { - e = e.getCause(); - } - if (e instanceof InterruptedException || e instanceof InterruptedIOException) { - // fine, some blocking code was interrupted by a dispose call - Log.d("RxJavaErrorHandler", "Ignored exception: " + Log.getStackTraceString(originalCause)); + // Probably just disposed because the fragment was left + Log.d("RxJavaErrorHandler", "Ignored exception: " + Log.getStackTraceString(e)); return; } Thread.currentThread().getUncaughtExceptionHandler() - .uncaughtException(Thread.currentThread(), originalCause); + .uncaughtException(Thread.currentThread(), e); }); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java index 52a43aab2..02e98ba84 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java @@ -5,7 +5,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; -import android.support.annotation.RequiresApi; +import androidx.annotation.RequiresApi; import de.danoeh.antennapod.core.R; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java index d22d08e09..a3f747e09 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java @@ -72,7 +72,7 @@ public class ChapterReader extends ID3Reader { String decodedLink = URLDecoder.decode(link.toString(), "UTF-8"); currentChapter.setLink(decodedLink); Log.d(TAG, "Found link: " + currentChapter.getLink()); - } catch (IllegalArgumentException _iae) { + } catch (IllegalArgumentException iae) { Log.w(TAG, "Bad URL found in ID3 data"); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java index 645bae5f3..9b644c3ba 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java @@ -182,7 +182,7 @@ public class ExternalMedia implements Playable { editor.putLong(PREF_LAST_PLAYED_TIME, timestamp); position = newPosition; lastPlayedTime = timestamp; - editor.commit(); + editor.apply(); } @Override diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java index da9b96430..378c47faf 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java @@ -4,7 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Parcelable; import android.preference.PreferenceManager; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.util.Log; import java.util.List; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index 9caa2156d..8328ea577 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -11,8 +11,8 @@ import android.content.res.TypedArray; import android.media.MediaPlayer; import android.os.Build; import android.os.IBinder; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -21,9 +21,7 @@ import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.TextView; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.event.ServiceEvent; @@ -69,9 +67,6 @@ public class PlaybackController { private final ScheduledThreadPoolExecutor schedExecutor; private static final int SCHED_EX_POOLSIZE = 1; - private MediaPositionObserver positionObserver; - private ScheduledFuture<?> positionObserverFuture; - private boolean mediaInfoLoaded = false; private boolean released = false; private boolean initialized = false; @@ -177,7 +172,6 @@ public class PlaybackController { } catch (IllegalArgumentException e) { // ignore } - cancelPositionObserver(); schedExecutor.shutdownNow(); media = null; released = true; @@ -254,29 +248,6 @@ public class PlaybackController { .getIntent()); } - - - private void setupPositionObserver() { - if (positionObserverFuture == null || - positionObserverFuture.isCancelled() || - positionObserverFuture.isDone()) { - - Log.d(TAG, "Setting up position observer"); - positionObserver = new MediaPositionObserver(); - positionObserverFuture = schedExecutor.scheduleWithFixedDelay( - positionObserver, MediaPositionObserver.WAITING_INTERVALL, - MediaPositionObserver.WAITING_INTERVALL, - TimeUnit.MILLISECONDS); - } - } - - private void cancelPositionObserver() { - if (positionObserverFuture != null) { - boolean result = positionObserverFuture.cancel(true); - Log.d(TAG, "PositionObserver cancelled. Result: " + result); - } - } - private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if(service instanceof PlaybackService.LocalBinder) { @@ -337,7 +308,6 @@ public class PlaybackController { onBufferUpdate(progress); break; case PlaybackService.NOTIFICATION_TYPE_RELOAD: - cancelPositionObserver(); mediaInfoLoaded = false; queryService(); onReloadNotification(intent.getIntExtra( @@ -447,7 +417,6 @@ public class PlaybackController { case PAUSED: clearStatusMsg(); checkMediaInfoLoaded(); - cancelPositionObserver(); onPositionObserverUpdate(); updatePlayButtonAppearance(playResource, playText); if (!PlaybackService.isCasting() && @@ -463,7 +432,6 @@ public class PlaybackController { onAwaitingVideoSurface(); setScreenOn(true); } - setupPositionObserver(); updatePlayButtonAppearance(pauseResource, pauseText); break; case PREPARING: @@ -581,7 +549,6 @@ public class PlaybackController { */ public void onSeekBarStartTrackingTouch(SeekBar seekBar) { // interrupt position Observer, restart later - cancelPositionObserver(); } /** @@ -590,7 +557,6 @@ public class PlaybackController { public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) { if (playbackService != null && media != null) { playbackService.seekTo((int) (prog * media.getDuration())); - setupPositionObserver(); } } @@ -728,6 +694,8 @@ public class PlaybackController { public void setPlaybackSpeed(float speed) { if (playbackService != null) { playbackService.setSpeed(speed); + } else { + onPlaybackSpeedChange(); } } public void setSkipSilence(boolean skipSilence) { @@ -834,19 +802,4 @@ public class PlaybackController { } }, error -> Log.e(TAG, Log.getStackTraceString(error))); } - - /** - * Refreshes the current position of the media file that is playing. - */ - public class MediaPositionObserver implements Runnable { - - static final int WAITING_INTERVALL = 1000; - - @Override - public void run() { - if (playbackService != null && playbackService.getStatus() == PlayerStatus.PLAYING) { - activity.runOnUiThread(PlaybackController.this::onPositionObserverUpdate); - } - } - } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java index f7d2ee409..01ca97134 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java @@ -2,9 +2,8 @@ package de.danoeh.antennapod.core.util.playback; import android.content.Context; import android.content.Intent; -import android.media.MediaPlayer; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 75229b9cf..0fe1f0036 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -3,11 +3,10 @@ package de.danoeh.antennapod.core.util.playback; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import android.util.TypedValue; import org.jsoup.Jsoup; @@ -15,7 +14,6 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import java.util.ArrayList; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java index c5ad9cfd6..a474756e9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core.util.syndication; import android.net.Uri; -import android.support.v4.util.ArrayMap; +import androidx.collection.ArrayMap; import android.text.TextUtils; import org.jsoup.Jsoup; diff --git a/core/src/main/res/drawable-hdpi/ic_baseline_question_answer_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_baseline_question_answer_white_24dp.png Binary files differdeleted file mode 100755 index 67924a5a2..000000000 --- a/core/src/main/res/drawable-hdpi/ic_baseline_question_answer_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-hdpi/ic_cast_disabled_light.png Binary files differdeleted file mode 100644 index c0a55d555..000000000 --- a/core/src/main/res/drawable-hdpi/ic_cast_disabled_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_cast_light.png b/core/src/main/res/drawable-hdpi/ic_cast_light.png Binary files differdeleted file mode 100644 index b0c581a0e..000000000 --- a/core/src/main/res/drawable-hdpi/ic_cast_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_cast_off_light.png b/core/src/main/res/drawable-hdpi/ic_cast_off_light.png Binary files differdeleted file mode 100644 index 5f3c0179c..000000000 --- a/core/src/main/res/drawable-hdpi/ic_cast_off_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_0_light.png Binary files differdeleted file mode 100644 index e872693a4..000000000 --- a/core/src/main/res/drawable-hdpi/ic_cast_on_0_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_1_light.png Binary files differdeleted file mode 100644 index d8be1ebc6..000000000 --- a/core/src/main/res/drawable-hdpi/ic_cast_on_1_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_2_light.png Binary files differdeleted file mode 100644 index 27cda9e9d..000000000 --- a/core/src/main/res/drawable-hdpi/ic_cast_on_2_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_light.png Binary files differdeleted file mode 100644 index 4ee525875..000000000 --- a/core/src/main/res/drawable-hdpi/ic_cast_on_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_close_light.png b/core/src/main/res/drawable-hdpi/ic_close_light.png Binary files differdeleted file mode 100644 index 93187e450..000000000 --- a/core/src/main/res/drawable-hdpi/ic_close_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_format_list_bulleted_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_format_list_bulleted_grey600_24dp.png Binary files differdeleted file mode 100644 index 3668c9a00..000000000 --- a/core/src/main/res/drawable-hdpi/ic_format_list_bulleted_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_format_list_bulleted_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_format_list_bulleted_white_24dp.png Binary files differdeleted file mode 100644 index a1a2c5b68..000000000 --- a/core/src/main/res/drawable-hdpi/ic_format_list_bulleted_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_forum_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_forum_grey600_24dp.png Binary files differdeleted file mode 100644 index da5398d15..000000000 --- a/core/src/main/res/drawable-hdpi/ic_forum_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-hdpi/ic_media_cast_disconnect.png Binary files differdeleted file mode 100644 index 700c116e5..000000000 --- a/core/src/main/res/drawable-hdpi/ic_media_cast_disconnect.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_pause_light.png b/core/src/main/res/drawable-hdpi/ic_pause_light.png Binary files differdeleted file mode 100644 index 0c505d1c8..000000000 --- a/core/src/main/res/drawable-hdpi/ic_pause_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_play_light.png b/core/src/main/res/drawable-hdpi/ic_play_light.png Binary files differdeleted file mode 100644 index 7957dff5b..000000000 --- a/core/src/main/res/drawable-hdpi/ic_play_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_stat_antenna_default.png b/core/src/main/res/drawable-hdpi/ic_stat_antenna_default.png Binary files differdeleted file mode 100644 index af99f4b3b..000000000 --- a/core/src/main/res/drawable-hdpi/ic_stat_antenna_default.png +++ /dev/null diff --git a/core/src/main/res/drawable-hdpi/ic_stat_authentication.png b/core/src/main/res/drawable-hdpi/ic_stat_authentication.png Binary files differdeleted file mode 100644 index 398dffa4b..000000000 --- a/core/src/main/res/drawable-hdpi/ic_stat_authentication.png +++ /dev/null diff --git a/core/src/main/res/drawable-ldpi/ic_stat_antenna_default.png b/core/src/main/res/drawable-ldpi/ic_stat_antenna_default.png Binary files differdeleted file mode 100644 index ddf545c0b..000000000 --- a/core/src/main/res/drawable-ldpi/ic_stat_antenna_default.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_baseline_question_answer_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_baseline_question_answer_white_24dp.png Binary files differdeleted file mode 100755 index e87df752e..000000000 --- a/core/src/main/res/drawable-mdpi/ic_baseline_question_answer_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-mdpi/ic_cast_disabled_light.png Binary files differdeleted file mode 100644 index 7940a0332..000000000 --- a/core/src/main/res/drawable-mdpi/ic_cast_disabled_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_cast_light.png b/core/src/main/res/drawable-mdpi/ic_cast_light.png Binary files differdeleted file mode 100644 index 1f5bec20b..000000000 --- a/core/src/main/res/drawable-mdpi/ic_cast_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_cast_off_light.png b/core/src/main/res/drawable-mdpi/ic_cast_off_light.png Binary files differdeleted file mode 100644 index 963db27d4..000000000 --- a/core/src/main/res/drawable-mdpi/ic_cast_off_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_0_light.png Binary files differdeleted file mode 100644 index a90d9e305..000000000 --- a/core/src/main/res/drawable-mdpi/ic_cast_on_0_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_1_light.png Binary files differdeleted file mode 100644 index bb2cf30bf..000000000 --- a/core/src/main/res/drawable-mdpi/ic_cast_on_1_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_2_light.png Binary files differdeleted file mode 100644 index 3ed59e55b..000000000 --- a/core/src/main/res/drawable-mdpi/ic_cast_on_2_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_light.png Binary files differdeleted file mode 100644 index 713427b97..000000000 --- a/core/src/main/res/drawable-mdpi/ic_cast_on_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_close_light.png b/core/src/main/res/drawable-mdpi/ic_close_light.png Binary files differdeleted file mode 100644 index 2c52c9b0f..000000000 --- a/core/src/main/res/drawable-mdpi/ic_close_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_format_list_bulleted_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_format_list_bulleted_grey600_24dp.png Binary files differdeleted file mode 100644 index 726eae499..000000000 --- a/core/src/main/res/drawable-mdpi/ic_format_list_bulleted_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_format_list_bulleted_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_format_list_bulleted_white_24dp.png Binary files differdeleted file mode 100644 index 0cc401dff..000000000 --- a/core/src/main/res/drawable-mdpi/ic_format_list_bulleted_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_forum_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_forum_grey600_24dp.png Binary files differdeleted file mode 100644 index d3bcfe7b6..000000000 --- a/core/src/main/res/drawable-mdpi/ic_forum_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-mdpi/ic_media_cast_disconnect.png Binary files differdeleted file mode 100644 index 767f420df..000000000 --- a/core/src/main/res/drawable-mdpi/ic_media_cast_disconnect.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_pause_light.png b/core/src/main/res/drawable-mdpi/ic_pause_light.png Binary files differdeleted file mode 100644 index 6218a774f..000000000 --- a/core/src/main/res/drawable-mdpi/ic_pause_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_play_light.png b/core/src/main/res/drawable-mdpi/ic_play_light.png Binary files differdeleted file mode 100644 index 1e0ccaf80..000000000 --- a/core/src/main/res/drawable-mdpi/ic_play_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_stat_antenna_default.png b/core/src/main/res/drawable-mdpi/ic_stat_antenna_default.png Binary files differdeleted file mode 100644 index 41fd20655..000000000 --- a/core/src/main/res/drawable-mdpi/ic_stat_antenna_default.png +++ /dev/null diff --git a/core/src/main/res/drawable-mdpi/ic_stat_authentication.png b/core/src/main/res/drawable-mdpi/ic_stat_authentication.png Binary files differdeleted file mode 100644 index 550b56b33..000000000 --- a/core/src/main/res/drawable-mdpi/ic_stat_authentication.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_baseline_question_answer_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_baseline_question_answer_white_24dp.png Binary files differdeleted file mode 100755 index 731f89c83..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_baseline_question_answer_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_disabled_light.png Binary files differdeleted file mode 100644 index fbb3e062c..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_cast_disabled_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_light.png Binary files differdeleted file mode 100644 index f2713e20e..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_cast_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_off_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_off_light.png Binary files differdeleted file mode 100644 index f4f8aaea8..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_cast_off_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_0_light.png Binary files differdeleted file mode 100644 index 247fc95ba..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_cast_on_0_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_1_light.png Binary files differdeleted file mode 100644 index ecf4b4723..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_cast_on_1_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_2_light.png Binary files differdeleted file mode 100644 index 60e3afa5d..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_cast_on_2_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_light.png Binary files differdeleted file mode 100644 index 40ce9d4f2..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_cast_on_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_close_light.png b/core/src/main/res/drawable-xhdpi/ic_close_light.png Binary files differdeleted file mode 100644 index 49faa429a..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_close_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_format_list_bulleted_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_format_list_bulleted_grey600_24dp.png Binary files differdeleted file mode 100644 index 322adb6e0..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_format_list_bulleted_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_format_list_bulleted_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_format_list_bulleted_white_24dp.png Binary files differdeleted file mode 100644 index c25860017..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_format_list_bulleted_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_forum_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_forum_grey600_24dp.png Binary files differdeleted file mode 100644 index ac6876140..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_forum_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-xhdpi/ic_media_cast_disconnect.png Binary files differdeleted file mode 100644 index 740867129..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_media_cast_disconnect.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_pause_light.png b/core/src/main/res/drawable-xhdpi/ic_pause_light.png Binary files differdeleted file mode 100644 index 40cd79f14..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_pause_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_play_light.png b/core/src/main/res/drawable-xhdpi/ic_play_light.png Binary files differdeleted file mode 100644 index 33f6a5919..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_play_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.png b/core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.png Binary files differdeleted file mode 100644 index 30431ed6a..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.png +++ /dev/null diff --git a/core/src/main/res/drawable-xhdpi/ic_stat_authentication.png b/core/src/main/res/drawable-xhdpi/ic_stat_authentication.png Binary files differdeleted file mode 100644 index e83cbc333..000000000 --- a/core/src/main/res/drawable-xhdpi/ic_stat_authentication.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_baseline_question_answer_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_baseline_question_answer_white_24dp.png Binary files differdeleted file mode 100755 index 255b82707..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_baseline_question_answer_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_disabled_light.png Binary files differdeleted file mode 100644 index e94df3889..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_cast_disabled_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_light.png Binary files differdeleted file mode 100644 index c5722a6eb..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_cast_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_off_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_off_light.png Binary files differdeleted file mode 100644 index 92ac67b34..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_cast_off_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_0_light.png Binary files differdeleted file mode 100644 index 2742fcb4a..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_cast_on_0_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_1_light.png Binary files differdeleted file mode 100644 index 405178e64..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_cast_on_1_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_2_light.png Binary files differdeleted file mode 100644 index dfe52428d..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_cast_on_2_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_light.png Binary files differdeleted file mode 100644 index 7e69a0864..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_cast_on_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_close_light.png b/core/src/main/res/drawable-xxhdpi/ic_close_light.png Binary files differdeleted file mode 100644 index be519bfcb..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_close_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_format_list_bulleted_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_format_list_bulleted_grey600_24dp.png Binary files differdeleted file mode 100644 index 87f8073ea..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_format_list_bulleted_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_format_list_bulleted_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_format_list_bulleted_white_24dp.png Binary files differdeleted file mode 100644 index da3433c61..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_format_list_bulleted_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_forum_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_forum_grey600_24dp.png Binary files differdeleted file mode 100644 index 7a3204693..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_forum_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-xxhdpi/ic_media_cast_disconnect.png Binary files differdeleted file mode 100644 index 2d2ec9035..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_media_cast_disconnect.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_pause_light.png b/core/src/main/res/drawable-xxhdpi/ic_pause_light.png Binary files differdeleted file mode 100644 index a36d4d11e..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_pause_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_play_light.png b/core/src/main/res/drawable-xxhdpi/ic_play_light.png Binary files differdeleted file mode 100644 index b1424874a..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_play_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxhdpi/ic_stat_authentication.png b/core/src/main/res/drawable-xxhdpi/ic_stat_authentication.png Binary files differdeleted file mode 100755 index 965fabc57..000000000 --- a/core/src/main/res/drawable-xxhdpi/ic_stat_authentication.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxxhdpi/ic_baseline_question_answer_white_24db.png b/core/src/main/res/drawable-xxxhdpi/ic_baseline_question_answer_white_24db.png Binary files differdeleted file mode 100755 index 0d697e0f9..000000000 --- a/core/src/main/res/drawable-xxxhdpi/ic_baseline_question_answer_white_24db.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxxhdpi/ic_close_light.png b/core/src/main/res/drawable-xxxhdpi/ic_close_light.png Binary files differdeleted file mode 100644 index 679c2a4d5..000000000 --- a/core/src/main/res/drawable-xxxhdpi/ic_close_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxxhdpi/ic_format_list_bulleted_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_format_list_bulleted_grey600_24dp.png Binary files differdeleted file mode 100644 index c56590fe0..000000000 --- a/core/src/main/res/drawable-xxxhdpi/ic_format_list_bulleted_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxxhdpi/ic_format_list_bulleted_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_format_list_bulleted_white_24dp.png Binary files differdeleted file mode 100644 index 5deea3286..000000000 --- a/core/src/main/res/drawable-xxxhdpi/ic_format_list_bulleted_white_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxxhdpi/ic_forum_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_forum_grey600_24dp.png Binary files differdeleted file mode 100644 index 0ae33696b..000000000 --- a/core/src/main/res/drawable-xxxhdpi/ic_forum_grey600_24dp.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxxhdpi/ic_pause_light.png b/core/src/main/res/drawable-xxxhdpi/ic_pause_light.png Binary files differdeleted file mode 100644 index 7de2ef4ed..000000000 --- a/core/src/main/res/drawable-xxxhdpi/ic_pause_light.png +++ /dev/null diff --git a/core/src/main/res/drawable-xxxhdpi/ic_play_light.png b/core/src/main/res/drawable-xxxhdpi/ic_play_light.png Binary files differdeleted file mode 100644 index 4428c8477..000000000 --- a/core/src/main/res/drawable-xxxhdpi/ic_play_light.png +++ /dev/null diff --git a/core/src/main/res/drawable/ic_antenna.xml b/core/src/main/res/drawable/ic_antenna.xml new file mode 100644 index 000000000..9fcfab000 --- /dev/null +++ b/core/src/main/res/drawable/ic_antenna.xml @@ -0,0 +1,6 @@ +<vector android:height="24dp" android:viewportHeight="12.7" + android:viewportWidth="12.7" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillAlpha="1" android:fillColor="#ffffff" + android:pathData="m6.0631,0.4728v0.3274c1.1582,0.0249 1.911,0.4225 2.5991,1.1189 0.6881,0.6964 1.0924,1.7043 1.1125,2.9246h0.3211c0.0078,-1.3792 -0.5291,-2.4905 -1.1981,-3.1576C8.2288,1.019 7.3415,0.4734 6.0631,0.4728ZM6.0631,1.4283v0.3453c0.859,0.0361 1.3465,0.2123 1.9398,0.8081 0.5933,0.5957 0.843,1.3669 0.8598,2.2621L9.2029,4.8438c-0.0088,-1.2333 -0.5414,-2.0907 -0.9568,-2.5047 -0.4154,-0.4139 -0.9948,-0.9065 -2.183,-0.9108zM6.0625,2.4323 L6.0631,2.7495c0.3968,0.007 0.8308,0.1395 1.2089,0.5642 0.3781,0.4247 0.495,1.0244 0.51,1.53h0.3255c-0.0016,-0.669 -0.2787,-1.3891 -0.6153,-1.747 -0.3366,-0.358 -0.7368,-0.6621 -1.4298,-0.6645zM6.0906,3.7766c-0.4059,0.0002 -0.7349,0.3294 -0.7347,0.7353 0.0001,0.2677 0.1459,0.5142 0.3804,0.6434l-3.0102,6.2227 0.5151,0.3351 0.607,-1.2485 5.3821,1.5453 0.083,0.1609 0.5732,-0.2508 -3.4927,-6.7397c0.2624,-0.1189 0.4311,-0.3802 0.4315,-0.6683 0.0002,-0.4059 -0.3287,-0.7352 -0.7347,-0.7353zM6.065,5.8631 L6.5929,6.8882 5.2882,7.4761zM6.6976,7.0918 L7.6065,8.8561 5.137,7.8016zM5.0259,8.0199 L7.611,9.1184 4.0314,10.0854zM7.8395,9.3086 L9.081,11.7201 4.1489,10.3069z" + android:strokeAlpha="1" android:strokeColor="#00000000" android:strokeWidth="0.32680494"/> +</vector> diff --git a/core/src/main/res/drawable/ic_chat_grey600.xml b/core/src/main/res/drawable/ic_chat_grey600.xml new file mode 100644 index 000000000..ebae2dbed --- /dev/null +++ b/core/src/main/res/drawable/ic_chat_grey600.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF757575" android:pathData="M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_chat_white.xml b/core/src/main/res/drawable/ic_chat_white.xml new file mode 100644 index 000000000..45691c525 --- /dev/null +++ b/core/src/main/res/drawable/ic_chat_white.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFFFF" android:pathData="M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_notification.png b/core/src/main/res/drawable/ic_notification.png Binary files differdeleted file mode 100644 index 8bd22b54a..000000000 --- a/core/src/main/res/drawable/ic_notification.png +++ /dev/null diff --git a/core/src/main/res/drawable/ic_notification_cast_off.xml b/core/src/main/res/drawable/ic_notification_cast_off.xml new file mode 100644 index 000000000..63a21fbe2 --- /dev/null +++ b/core/src/main/res/drawable/ic_notification_cast_off.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="24dp"> + <path android:fillColor="#FFFFFFFF" android:pathData="M1.6,1.27L0.25,2.75L1.41,3.8C1.16,4.13 1,4.55 1,5V8H3V5.23L18.2,19H14V21H20.41L22.31,22.72L23.65,21.24M6.5,3L8.7,5H21V16.14L23,17.95V5C23,3.89 22.1,3 21,3M1,10V12A9,9 0 0,1 10,21H12C12,14.92 7.08,10 1,10M1,14V16A5,5 0 0,1 6,21H8A7,7 0 0,0 1,14M1,18V21H4A3,3 0 0,0 1,18Z" /> +</vector>
\ No newline at end of file diff --git a/core/src/main/res/drawable/ic_notification_fast_forward.xml b/core/src/main/res/drawable/ic_notification_fast_forward.xml new file mode 100644 index 000000000..bf564977c --- /dev/null +++ b/core/src/main/res/drawable/ic_notification_fast_forward.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="24dp"> + <path android:fillColor="#FFFFFFFF" android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_notification_fast_rewind.xml b/core/src/main/res/drawable/ic_notification_fast_rewind.xml new file mode 100644 index 000000000..847159cc5 --- /dev/null +++ b/core/src/main/res/drawable/ic_notification_fast_rewind.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="24dp"> + <path android:fillColor="#FFFFFFFF" android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_notification_key.xml b/core/src/main/res/drawable/ic_notification_key.xml new file mode 100644 index 000000000..c8a817eeb --- /dev/null +++ b/core/src/main/res/drawable/ic_notification_key.xml @@ -0,0 +1,6 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="24dp"> + <path android:fillColor="#FFFFFFFF" + android:pathData="M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_notification_pause.xml b/core/src/main/res/drawable/ic_notification_pause.xml new file mode 100644 index 000000000..d46efb2f5 --- /dev/null +++ b/core/src/main/res/drawable/ic_notification_pause.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="24dp"> + <path android:fillColor="#FFFFFFFF" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_notification_play.xml b/core/src/main/res/drawable/ic_notification_play.xml new file mode 100644 index 000000000..d571460c6 --- /dev/null +++ b/core/src/main/res/drawable/ic_notification_play.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="24dp"> + <path android:fillColor="#FFFFFFFF" android:pathData="M8,5v14l11,-7z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_notification_skip.xml b/core/src/main/res/drawable/ic_notification_skip.xml new file mode 100644 index 000000000..0c65448cc --- /dev/null +++ b/core/src/main/res/drawable/ic_notification_skip.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="24dp"> + <path android:fillColor="#FFFFFFFF" android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_playback_speed_dark.xml b/core/src/main/res/drawable/ic_playback_speed_dark.xml new file mode 100644 index 000000000..7c7d1cf8c --- /dev/null +++ b/core/src/main/res/drawable/ic_playback_speed_dark.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" + android:width="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path android:fillColor="#FF757575" android:pathData="M 12,14.888154 A 2.2284437,2.2284437 0 0 1 9.7715563,12.659711 c 0,-0.831952 0.4531167,-1.559911 1.1142217,-1.938746 L 18.098507,6.5463469 13.990743,13.66251 C 13.619336,14.390469 12.869093,14.888154 12,14.888154 m 0,-9.6565888 c 1.344494,0 2.599851,0.3714073 3.691789,0.9805151 L 14.131878,7.110886 C 13.485629,6.858329 12.742815,6.7171943 12,6.7171943 A 5.9425165,5.9425165 0 0 0 6.0574835,12.659711 c 0,1.64162 0.661105,3.127249 1.7381861,4.196902 h 0.00743 c 0.2896977,0.289697 0.2896977,0.757671 0,1.047369 -0.2896977,0.289697 -0.7650991,0.289697 -1.0547967,0.0075 v 0 C 5.4038067,16.566915 4.5718544,14.709879 4.5718544,12.659711 A 7.4281456,7.4281456 0 0 1 12,5.2315652 m 7.428145,7.4281458 c 0,2.050168 -0.831952,3.907204 -2.176446,5.251699 v 0 c -0.289698,0.282269 -0.757671,0.282269 -1.047369,-0.0075 -0.289697,-0.289698 -0.289697,-0.757671 0,-1.047368 v 0 c 1.077082,-1.077082 1.738186,-2.555282 1.738186,-4.196902 0,-0.742815 -0.141134,-1.48563 -0.401119,-2.154163 l 0.898805,-1.5599106 c 0.616537,1.1142216 0.987943,2.3621496 0.987943,3.7140736 z" /> +</vector>
\ No newline at end of file diff --git a/core/src/main/res/drawable/ic_playback_speed_white.xml b/core/src/main/res/drawable/ic_playback_speed_white.xml new file mode 100644 index 000000000..cc6af0d55 --- /dev/null +++ b/core/src/main/res/drawable/ic_playback_speed_white.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="48dp" + android:width="48dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path android:fillColor="#ffffffff" android:pathData="M 12,14.888154 A 2.2284437,2.2284437 0 0 1 9.7715563,12.659711 c 0,-0.831952 0.4531167,-1.559911 1.1142217,-1.938746 L 18.098507,6.5463469 13.990743,13.66251 C 13.619336,14.390469 12.869093,14.888154 12,14.888154 m 0,-9.6565888 c 1.344494,0 2.599851,0.3714073 3.691789,0.9805151 L 14.131878,7.110886 C 13.485629,6.858329 12.742815,6.7171943 12,6.7171943 A 5.9425165,5.9425165 0 0 0 6.0574835,12.659711 c 0,1.64162 0.661105,3.127249 1.7381861,4.196902 h 0.00743 c 0.2896977,0.289697 0.2896977,0.757671 0,1.047369 -0.2896977,0.289697 -0.7650991,0.289697 -1.0547967,0.0075 v 0 C 5.4038067,16.566915 4.5718544,14.709879 4.5718544,12.659711 A 7.4281456,7.4281456 0 0 1 12,5.2315652 m 7.428145,7.4281458 c 0,2.050168 -0.831952,3.907204 -2.176446,5.251699 v 0 c -0.289698,0.282269 -0.757671,0.282269 -1.047369,-0.0075 -0.289697,-0.289698 -0.289697,-0.757671 0,-1.047368 v 0 c 1.077082,-1.077082 1.738186,-2.555282 1.738186,-4.196902 0,-0.742815 -0.141134,-1.48563 -0.401119,-2.154163 l 0.898805,-1.5599106 c 0.616537,1.1142216 0.987943,2.3621496 0.987943,3.7140736 z" /> +</vector>
\ No newline at end of file diff --git a/core/src/main/res/drawable/ic_questionmark_grey600.xml b/core/src/main/res/drawable/ic_questionmark_grey600.xml new file mode 100644 index 000000000..0907fcdec --- /dev/null +++ b/core/src/main/res/drawable/ic_questionmark_grey600.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF757575" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_questionmark_white.xml b/core/src/main/res/drawable/ic_questionmark_white.xml new file mode 100644 index 000000000..69b42fef8 --- /dev/null +++ b/core/src/main/res/drawable/ic_questionmark_white.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFFFF" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/> +</vector> diff --git a/core/src/main/res/drawable/ic_warning_red.xml b/core/src/main/res/drawable/ic_warning_red.xml new file mode 100644 index 000000000..475a41bbb --- /dev/null +++ b/core/src/main/res/drawable/ic_warning_red.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#FF0000" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF000000" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/> +</vector> diff --git a/core/src/main/res/drawable/notification_default_large_icon.xml b/core/src/main/res/drawable/notification_default_large_icon.xml new file mode 100644 index 000000000..6da31b1bb --- /dev/null +++ b/core/src/main/res/drawable/notification_default_large_icon.xml @@ -0,0 +1,15 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportHeight="12.7" + android:viewportWidth="12.7" + android:height="64dp" + android:width="64dp"> + + <path + android:fillColor="#ff007DBA" + android:pathData="M 0,0 L 12.7,0 12.7,12.7 0,12.7 z"/> + + <path + android:fillColor="#ffffffff" + android:pathData="m5.7552,2.2412l0,0.2651c0.9377,0.0202 1.5471,0.342 2.1043,0.9059 0.5571,0.5639 0.8845,1.3798 0.9007,2.3678l0.2599,0C9.0264,4.6634 8.5918,3.7637 8.0502,3.2236 7.5085,2.6834 6.7902,2.2418 5.7552,2.2412ZM5.7552,3.0148l0,0.2796c0.6954,0.0292 1.0901,0.1719 1.5704,0.6542 0.4803,0.4823 0.6825,1.1066 0.6961,1.8314l0.2754,0c-0.0071,-0.9985 -0.4383,-1.6927 -0.7746,-2.0278 -0.3363,-0.3351 -0.8054,-0.734 -1.7673,-0.7374zM5.7547,3.8277 L5.7552,4.0845c0.3213,0.006 0.6726,0.1129 0.9788,0.4568 0.3061,0.3439 0.4008,0.8294 0.4129,1.2387l0.2635,0C7.4091,5.2384 7.1848,4.6554 6.9122,4.3656 6.6397,4.0758 6.3157,3.8296 5.7547,3.8277ZM5.7774,4.916c-0.3286,0.0001 -0.595,0.2667 -0.5948,0.5953 0.0001,0.2168 0.1181,0.4163 0.308,0.5209l-2.4371,5.0379 0.417,0.2713 0.4914,-1.0108 4.3574,1.2511 0.0672,0.1302 0.4641,-0.2031L6.0229,6.0523c0.2124,-0.0963 0.349,-0.3078 0.3493,-0.5411 0.0002,-0.3286 -0.2661,-0.5952 -0.5948,-0.5953zM5.7567,6.6053 L6.1841,7.4352 5.1278,7.9111zM6.2689,7.6001 L7.0047,9.0284 5.0054,8.1747zM4.9154,8.3514 L7.0083,9.2408 4.1103,10.0237zM7.1934,9.3948 L8.1985,11.3471 4.2054,10.203Z" /> + +</vector> diff --git a/core/src/main/res/drawable/white_circle.xml b/core/src/main/res/drawable/white_circle.xml deleted file mode 100644 index 597b70a2d..000000000 --- a/core/src/main/res/drawable/white_circle.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval" > - - <solid android:color="@color/white" /> - - <size - android:height="4dp" - android:width="4dp" /> - -</shape>
\ No newline at end of file diff --git a/core/src/main/res/values-de/strings.xml b/core/src/main/res/values-de/strings.xml index b2b70575c..d191f4059 100644 --- a/core/src/main/res/values-de/strings.xml +++ b/core/src/main/res/values-de/strings.xml @@ -422,7 +422,7 @@ <string name="pref_gpodnet_full_sync_title">Jetzt komplett synchronisieren</string> <string name="pref_gpodnet_full_sync_sum">Kompletten Abonnement- und Episoden-Status mit gpodder.net synchronisieren.</string> <string name="pref_gpodnet_sync_sum_last_sync_line">Letzter Synchronisierungsversuch: %1$s (%2$s)</string> - <string name="pref_gpodnet_sync_started">Synchronisation starten</string> + <string name="pref_gpodnet_sync_started">Synchronisation gestartet</string> <string name="pref_gpodnet_full_sync_started">Komplette Synchronisierung gestartet</string> <string name="pref_gpodnet_login_status"><![CDATA[Eingeloggt als <i>%1$s</i> mit dem Gerät <i>%2$s</i>]]></string> <string name="pref_gpodnet_notifications_title">Zeige Benachrichtungen bei Synchronisierungsfehlern</string> @@ -617,7 +617,7 @@ <string name="media_type_audio_label">Audio</string> <string name="media_type_video_label">Video</string> <string name="navigate_upwards_label">Nach oben navigieren</string> - <string name="status_downloading_label">Episode wird gerade heruntergeladen</string> + <string name="status_downloading_label">Episode wird heruntergeladen</string> <string name="in_queue_label">Episode befindet sich in der Abspielliste</string> <string name="drag_handle_content_description">Ziehe, um die Position dieses Objekts zu verändern</string> <string name="load_next_page_label">Nächste Seite laden</string> @@ -726,7 +726,7 @@ <!--Notification channels--> <string name="notification_channel_user_action">Handlung notwendig</string> <string name="notification_channel_user_action_description">Wird gezeigt, wenn deine Handlung notwendig ist, zum Beispiel wenn du ein Passwort eingeben musst.</string> - <string name="notification_channel_downloading">Lädt herunter</string> + <string name="notification_channel_downloading">Herunterladen</string> <string name="notification_channel_downloading_description">Wird gezeigt beim Herunterladen.</string> <string name="notification_channel_playing">Jetzt spielt</string> <string name="notification_channel_playing_description">Erlaubt es, die Wiedergabe zu steuern. Dies ist die Hauptbenachrichtigung, die du siehst, während ein Podcast abgespielt wird.</string> diff --git a/core/src/main/res/values-hu/strings.xml b/core/src/main/res/values-hu/strings.xml index 4a42da4c3..706b13b79 100644 --- a/core/src/main/res/values-hu/strings.xml +++ b/core/src/main/res/values-hu/strings.xml @@ -107,6 +107,7 @@ <string name="podcastdirectories_label">Podcast keresése mappában</string> <string name="podcastdirectories_descr">Új podcastokat kereshetsz iTunes-on vagy fyyd-en, vagy a gpodder.net-en név, kategória vagy cím alapján.</string> <string name="browse_gpoddernet_label">gpodder.net böngészése</string> + <string name="discover">Felfedezés</string> <!--Actions on feeds--> <string name="mark_all_read_label">Az összes megjelölése lejátszottként</string> <string name="mark_all_read_msg">Az összes epizód lejátszottként megjelölve</string> diff --git a/core/src/main/res/values-id/strings.xml b/core/src/main/res/values-in/strings.xml index 2f1ffecea..2f1ffecea 100644 --- a/core/src/main/res/values-id/strings.xml +++ b/core/src/main/res/values-in/strings.xml diff --git a/core/src/main/res/values-land/styles.xml b/core/src/main/res/values-land/styles.xml deleted file mode 100644 index d964ef3d4..000000000 --- a/core/src/main/res/values-land/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <style name="Theme.MediaPlayer" parent="@style/Theme.AppCompat.Light"> - <item name="android:windowActionBarOverlay">true</item> - </style> -</resources>
\ No newline at end of file diff --git a/core/src/main/res/values-large/dimens.xml b/core/src/main/res/values-large/dimens.xml index 2da283c5b..2d107eef0 100644 --- a/core/src/main/res/values-large/dimens.xml +++ b/core/src/main/res/values-large/dimens.xml @@ -4,5 +4,4 @@ <dimen name="thumbnail_length">170dp</dimen> <dimen name="thumbnail_length_queue_item">64dp</dimen> <dimen name="thumbnail_length_downloaded_item">64dp</dimen> - <dimen name="queue_title_text_size">@dimen/text_size_medium</dimen> </resources>
\ No newline at end of file diff --git a/core/src/main/res/values-pl-rPL/strings.xml b/core/src/main/res/values-pl-rPL/strings.xml index 405bfeaa7..3ebdbe487 100644 --- a/core/src/main/res/values-pl-rPL/strings.xml +++ b/core/src/main/res/values-pl-rPL/strings.xml @@ -7,6 +7,7 @@ <string name="add_feed_label">Dodaj podcast</string> <string name="episodes_label">Odcinki</string> <string name="all_episodes_short_label">Wszystkie</string> + <string name="new_episodes_label">Nowe</string> <string name="favorite_episodes_label">Ulubione</string> <string name="new_label">Nowy</string> <string name="settings_label">Ustawienia</string> @@ -104,6 +105,7 @@ <string name="podcastdirectories_label">Znajdź podcast w folderze</string> <string name="podcastdirectories_descr">Możesz wyszukiwać nowe podcasty ze względu na nazwę, kategorię lub popularność na gpodder.net, Itunes lub fyyd</string> <string name="browse_gpoddernet_label">Przeglądaj gpodder.net</string> + <string name="discover_more">więcej »</string> <!--Actions on feeds--> <string name="mark_all_read_label">Oznacz wszystkie jako odtworzone</string> <string name="mark_all_read_msg">Wszystkie odcinki zaznaczono jako odtworzone</string> @@ -134,7 +136,7 @@ <string name="hide_has_media_label">Ma media</string> <string name="filtered_label">Przefiltrowany</string> <string name="refresh_failed_msg">{fa-exclamation-circle} Ostatnie odświerzanie nie powiodło się</string> - <string name="open_podcast">Otwarty Podcast</string> + <string name="open_podcast">Otwórz Podcast</string> <!--actions on feeditems--> <string name="download_label">Pobierz</string> <string name="play_label">Odtwórz</string> @@ -249,6 +251,8 @@ <!--Empty list labels--> <string name="no_feeds_label">Nie subskrybowałeś jeszcze żadnego podcastu.</string> <string name="no_shownotes_label">Ten epizod nie ma notatek.</string> + <string name="no_comp_downloads_head_label">Brak pobranych odcinków</string> + <string name="no_fav_episodes_head_label">Brak ulubionych odcinków</string> <string name="no_chapters_label">Ten odcinek nie ma rozdziałów.</string> <!--Preferences--> <string name="storage_pref">Pamięć</string> @@ -258,6 +262,8 @@ <string name="queue_label">Kolejka</string> <string name="download_pref_details">Szczegóły</string> <string name="appearance">Wygląd</string> + <string name="preference_search_no_results">Brak wyników</string> + <string name="preference_search_clear_history">Wyczyść historię</string> <string name="pref_episode_cleanup_title">Usuwanie odcinków</string> <string name="pref_episode_cleanup_summary">Odcinki niebędące w kolejce i niebędące na liście ulubiobych powinny nadawać się do usunięcia, jeśli Automatyczne Pobieranie potrzebuje miejsca na nowe odcinki.</string> <string name="pref_pauseOnDisconnect_sum">Wstrzymaj odtwarzanie po rozłączeniu słuchawek lub Bluetooth</string> @@ -292,6 +298,7 @@ <string name="pref_unpauseOnHeadsetReconnect_title">Słuchawki podłączone ponownie</string> <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth podłączony ponownie</string> <string name="pref_mobileUpdate_title">Aktualizacje mobilne</string> + <string name="pref_mobileUpdate_auto_download">Automatyczne pobieranie</string> <string name="refreshing_label">Odświeżanie</string> <string name="user_interface_label">Interfejs użytkownika</string> <string name="pref_set_theme_title">Wybierz motyw</string> @@ -374,6 +381,9 @@ <string name="pref_cast_message_free_flavor">Chromecast wymagadodatkowych bibliotek, które są zablokowane w tej wersji AntennaPod</string> <string name="pref_enqueue_downloaded_title">Rzeczy z kolejki pobrane</string> <string name="pref_enqueue_downloaded_summary">Dodaj pobrane odcinki do kolejki</string> + <string name="behavior">Zachowanie</string> + <string name="back_button_default">Domyślne</string> + <string name="back_button_open_drawer">Otwórz panel nawigacyjny</string> <!--Search--> <string name="search_hint">Szukaj odcinków</string> <string name="found_in_shownotes_label">Znalezione w notatkach dotyczących show</string> diff --git a/core/src/main/res/values-v16/styles.xml b/core/src/main/res/values-v16/styles.xml index a92790152..947e43f38 100644 --- a/core/src/main/res/values-v16/styles.xml +++ b/core/src/main/res/values-v16/styles.xml @@ -6,12 +6,4 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:fontFamily">sans-serif-light</item> </style> - - <style name="AntennaPod.Dialog.Title" parent="@android:style/TextAppearance.Medium"> - <item name="android:textSize">@dimen/text_size_medium</item> - <item name="android:textColor">@color/holo_blue_light</item> - <item name="android:maxLines">2</item> - <item name="android:ellipsize">end</item> - <item name="android:fontFamily">sans-serif-light</item> - </style> </resources>
\ No newline at end of file diff --git a/core/src/main/res/values-v19/colors.xml b/core/src/main/res/values-v19/colors.xml index 16c065d75..4154280e8 100644 --- a/core/src/main/res/values-v19/colors.xml +++ b/core/src/main/res/values-v19/colors.xml @@ -1,5 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <color name="selection_background_color_dark">#484B4D</color> - <color name="selection_background_color_light">#E3E3E3</color> </resources>
\ No newline at end of file diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml index 0eaf0e5e7..5e7eab1ea 100644 --- a/core/src/main/res/values/arrays.xml +++ b/core/src/main/res/values/arrays.xml @@ -156,19 +156,15 @@ <item>4.00</item> </string-array> - <string-array name="autodl_select_networks_default_entries"> - <item>N/A</item> - </string-array> - <string-array name="autodl_select_networks_default_values"> - <item>0</item> - </string-array> - <string-array name="theme_options"> + <item>@string/pref_theme_title_use_system</item> <item>@string/pref_theme_title_light</item> <item>@string/pref_theme_title_dark</item> <item>@string/pref_theme_title_trueblack</item> </string-array> + <string-array name="theme_values"> + <item>system</item> <item>0</item> <item>1</item> <item>2</item> diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml index 5b0bf5717..530b40d46 100644 --- a/core/src/main/res/values/attrs.xml +++ b/core/src/main/res/values/attrs.xml @@ -9,6 +9,7 @@ <attr name="av_fast_forward" format="reference"/> <attr name="av_pause" format="reference"/> <attr name="av_play" format="reference"/> + <attr name="av_speed" format="reference"/> <attr name="av_rewind" format="reference"/> <attr name="content_discard" format="reference"/> <attr name="content_new" format="reference"/> @@ -56,9 +57,9 @@ <attr name="ic_cast_disconnect" format="reference"/> <attr name="ic_swap" format="reference"/> <attr name="ic_cellphone_text" format="reference"/> - <attr name="ic_question_answer" format="reference" /> + <attr name="ic_questionmark" format="reference" /> + <attr name="ic_chat" format="reference"/> <attr name="ic_bug" format="reference" /> - <attr name="ic_known_issues" format="reference" /> <attr name="master_switch_background" format="color"/> <attr name="currently_playing_background" format="color"/> <attr name="ic_bookmark" format="reference"/> diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml index 0e4533977..fea7da4a4 100644 --- a/core/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -2,22 +2,16 @@ <resources> <color name="white">#FFFFFF</color> - <color name="gray">#808080</color> <color name="grey600">#757575</color> <color name="light_gray">#bfbfbf</color> <color name="black">#000000</color> <color name="holo_blue_light">#33B5E5</color> <color name="holo_blue_dark">#0099CC</color> - <color name="ics_gray">#858585</color> - <color name="actionbar_gray">#DDDDDD</color> <color name="download_success_green">#669900</color> <color name="download_failed_red">#CC0000</color> <color name="status_progress">#E033B5E5</color> - <color name="status_playing">#E0EE5F52</color> <color name="overlay_dark">#2C2C2C</color> <color name="overlay_light">#FFFFFF</color> - <color name="swipe_refresh_secondary_color_light">#EDEDED</color> - <color name="swipe_refresh_secondary_color_dark">#060708</color> <color name="new_indicator_green">#669900</color> <color name="image_readability_tint">#80000000</color> <color name="feed_image_bg">#50000000</color> diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml index cdde0027d..02c398b62 100644 --- a/core/src/main/res/values/dimens.xml +++ b/core/src/main/res/values/dimens.xml @@ -10,7 +10,6 @@ <dimen name="text_size_navdrawer">16sp</dimen> <dimen name="text_size_medium">18sp</dimen> <dimen name="text_size_large">22sp</dimen> - <dimen name="status_indicator_width">32dp</dimen> <dimen name="thumbnail_length_itemlist">64dp</dimen> <dimen name="thumbnail_length_queue_item">64dp</dimen> <dimen name="thumbnail_length_downloaded_item">64dp</dimen> @@ -21,7 +20,6 @@ <dimen name="drawer_width">280dp</dimen> <dimen name="listitem_iconwithtext_height">48dp</dimen> <dimen name="listitem_iconwithtext_textleftpadding">16dp</dimen> - <dimen name="listitem_iconwithtext_textverticalpadding">16dp</dimen> <dimen name="listitem_threeline_textleftpadding">16dp</dimen> <dimen name="listitem_threeline_textrightpadding">8dp</dimen> @@ -29,7 +27,6 @@ <dimen name="listitem_threeline_horizontalpadding">16dp</dimen> <dimen name="list_vertical_padding">8dp</dimen> - <dimen name="minimum_text_margin">8dp</dimen> <dimen name="listitem_icon_leftpadding">16dp</dimen> <dimen name="listitem_icon_rightpadding">16dp</dimen> diff --git a/core/src/main/res/values/integers.xml b/core/src/main/res/values/integers.xml index 33501d9fb..73d90cf98 100644 --- a/core/src/main/res/values/integers.xml +++ b/core/src/main/res/values/integers.xml @@ -1,4 +1,3 @@ <resources> - <integer name="undobar_hide_delay">5000</integer> <integer name="episode_cache_size_unlimited">-1</integer> </resources> diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 1cd4a7836..0d61e30cd 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -27,10 +27,8 @@ <string name="gpodnet_main_label">gpodder.net</string> <string name="gpodnet_summary">Synchronize with other devices</string> <string name="gpodnet_auth_label">gpodder.net Login</string> - <string name="free_space_label">%1$s free</string> <string name="episode_cache_full_title">Episode cache full</string> <string name="episode_cache_full_message">The episode cache limit has been reached. You can increase the cache size in the Settings.</string> - <string name="synchronizing">Synchronizing…</string> <!-- Statistics fragment --> <string name="total_time_listened_to_podcasts">Total time of podcasts played:</string> @@ -73,7 +71,6 @@ <string name="author_label">Author(s)</string> <string name="language_label">Language</string> <string name="url_label">URL</string> - <string name="podcast_settings_label">Settings</string> <string name="cover_label">Picture</string> <string name="error_label">Error</string> <string name="error_msg_prefix">An error occurred:</string> @@ -82,14 +79,9 @@ <string name="external_storage_error_msg">No external storage is available. Please make sure that external storage is mounted so that the app can work properly.</string> <string name="chapters_label">Chapters</string> <string name="chapter_duration">Duration: %1$s</string> - <string name="shownotes_label">Shownotes</string> <string name="description_label">Description</string> - <string name="most_recent_prefix">Most recent episode:\u0020</string> <string name="episodes_suffix">\u0020episodes</string> - <string name="length_prefix">Length:\u0020</string> - <string name="size_prefix">Size:\u0020</string> <string name="processing_label">Processing</string> - <string name="loading_label">Loading…</string> <string name="save_username_password_label">Save username and password</string> <string name="close_label">Close</string> <string name="retry_label">Retry</string> @@ -119,8 +111,6 @@ <string name="feedurl_label">Feed URL</string> <string name="etxtFeedurlHint">www.example.com/feed</string> <string name="txtvfeedurl_label">Add Podcast by URL</string> - <string name="podcastdirectories_label">Find Podcast in Directory</string> - <string name="podcastdirectories_descr">For new podcasts, you can search iTunes or fyyd, or browse gpodder.net by name, category or popularity.</string> <string name="browse_gpoddernet_label">Browse gpodder.net</string> <string name="discover">Discover</string> <string name="discover_more">more »</string> @@ -143,13 +133,13 @@ <string name="share_link_label">Share Episode URL</string> <string name="share_link_with_position_label">Share Episode URL with Position</string> <string name="share_file_label">Share File</string> + <string name="share_website_url_label">Share Website URL</string> <string name="share_feed_url_label">Share Feed URL</string> <string name="share_item_url_label">Share Media File URL</string> <string name="share_item_url_with_position_label">Share Media File URL with Position</string> <string name="feed_delete_confirmation_msg">Please confirm that you want to delete the podcast \"%1$s\" and ALL its episodes (including downloaded episodes).</string> <string name="feed_remover_msg">Removing podcast</string> <string name="load_complete_feed">Refresh complete podcast</string> - <string name="hide_episodes_title">Hide Episodes</string> <string name="batch_edit">Batch edit</string> <string name="select_all_above">Select all above</string> <string name="select_all_below">Select all below</string> @@ -174,9 +164,7 @@ </plurals> <string name="play_label">Play</string> <string name="pause_label">Pause</string> - <string name="stop_label">Stop</string> <string name="stream_label">Stream</string> - <string name="remove_label">Remove</string> <string name="delete_label">Delete</string> <string name="delete_failed">Unable to delete file. Rebooting the device could help.</string> <string name="delete_episode_label">Delete Episode</string> @@ -221,14 +209,12 @@ <!-- Download messages and labels --> <string name="download_successful">successful</string> - <string name="download_failed">failed</string> <string name="download_pending">Download pending</string> <string name="download_running">Download running</string> <string name="download_error_details">Details</string> <string name="download_error_details_message">%1$s \n\nFile URL:\n%2$s</string> <string name="download_error_device_not_found">Storage Device not found</string> <string name="download_error_insufficient_space">Insufficient Space</string> - <string name="download_error_file_error">File Error</string> <string name="download_error_http_data_error">HTTP Data Error</string> <string name="download_error_error_unknown">Unknown Error</string> <string name="download_error_parser_exception">Parser Exception</string> @@ -238,7 +224,6 @@ <string name="download_error_unauthorized">Authentication Error</string> <string name="download_error_file_type_type">File Type Error</string> <string name="download_error_forbidden">Forbidden</string> - <string name="cancel_all_downloads_label">Cancel all downloads</string> <string name="download_canceled_msg">Download canceled</string> <string name="download_canceled_autodownload_enabled_msg">Download canceled\nDisabled <i>Auto Download</i> for this item</string> <string name="download_report_title">Downloads completed with error(s)</string> @@ -257,7 +242,6 @@ <string name="download_log_title_unknown">Unknown Title</string> <string name="download_type_feed">Feed</string> <string name="download_type_media">Media file</string> - <string name="download_type_image">Image</string> <string name="download_request_error_dialog_message_prefix">An error occurred when trying to download the file:\u0020</string> <string name="null_value_podcast_error">No podcast was provided that could be shown.</string> <string name="authentication_notification_title">Authentication required</string> @@ -285,7 +269,6 @@ <string name="position_default_label" translate="false">00:00:00</string> <string name="player_buffering_msg">Buffering</string> <string name="player_go_to_picture_in_picture">Picture-in-picture mode</string> - <string name="playbackservice_notification_title">Playing podcast</string> <string name="unknown_media_key">AntennaPod - Unknown media key: %1$d</string> <!-- Queue operations --> @@ -293,9 +276,10 @@ <string name="unlock_queue">Unlock Queue</string> <string name="queue_locked">Queue locked</string> <string name="queue_unlocked">Queue unlocked</string> + <string name="queue_lock_warning">If you lock the queue, you can no longer swipe or reorder episodes.</string> + <string name="checkbox_do_not_show_again">Do not show again</string> <string name="clear_queue_label">Clear Queue</string> <string name="undo">Undo</string> - <string name="removed_from_queue">Item removed</string> <string name="move_to_top_label">Move to top</string> <string name="move_to_bottom_label">Move to bottom</string> <string name="sort">Sort</string> @@ -322,7 +306,6 @@ <!-- Empty list labels --> <string name="no_items_header_label">No queued episodes</string> <string name="no_items_label">Add an episode by downloading it, or long press an episode and select \"Add to queue\".</string> - <string name="no_feeds_label">You haven\'t subscribed to any podcasts yet.</string> <string name="no_shownotes_label">This episode has no shownotes.</string> <string name="no_run_downloads_head_label">No downloads running</string> <string name="no_run_downloads_label">You can download episodes on the podcast details screen.</string> @@ -344,7 +327,6 @@ <!-- Preferences --> <string name="storage_pref">Storage</string> <string name="project_pref">Project</string> - <string name="other_pref">Other</string> <string name="about_pref">About</string> <string name="queue_label">Queue</string> <string name="integrations_label">Integrations</string> @@ -388,9 +370,7 @@ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Set Time of Day</string> <string name="pref_autoUpdateIntervallOrTime_every">every %1$s</string> <string name="pref_autoUpdateIntervallOrTime_at">at %1$s</string> - <string name="pref_downloadMediaOnWifiOnly_sum">Download media files only over WiFi</string> <string name="pref_followQueue_title">Continuous Playback</string> - <string name="pref_downloadMediaOnWifiOnly_title">WiFi media download</string> <string name="pref_pauseOnHeadsetDisconnect_title">Headphones Disconnect</string> <string name="pref_unpauseOnHeadsetReconnect_title">Headphones Reconnect</string> <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth Reconnect</string> @@ -401,11 +381,8 @@ <string name="pref_mobileUpdate_auto_download">Auto download</string> <string name="pref_mobileUpdate_episode_download">Episode download</string> <string name="pref_mobileUpdate_streaming">Streaming</string> - <string name="refreshing_label">Refreshing</string> <string name="user_interface_label">User Interface</string> <string name="pref_set_theme_title">Select Theme</string> - <string name="pref_nav_drawer_title">Customize Navigation Drawer</string> - <string name="pref_nav_drawer_sum">Customize the appearance of the navigation drawer.</string> <string name="pref_nav_drawer_items_title">Set Navigation Drawer items</string> <string name="pref_nav_drawer_items_sum">Change which items appear in the navigation drawer.</string> <string name="pref_nav_drawer_feed_order_title">Set Subscription Order</string> @@ -417,13 +394,14 @@ <string name="pref_automatic_download_sum">Configure the automatic download of episodes.</string> <string name="pref_autodl_wifi_filter_title">Enable Wi-Fi filter</string> <string name="pref_autodl_wifi_filter_sum">Allow automatic download only for selected Wi-Fi networks.</string> - <string name="pref_autodl_allow_on_mobile_title">Download on mobile connection</string> - <string name="pref_autodl_allow_on_mobile_sum">Allow automatic download over the mobile data connection.</string> + <string name="autodl_wifi_filter_permission_title">Permission required</string> + <string name="autodl_wifi_filter_permission_message">Location permission is required for Wi-Fi filter. Tap to grant the permission.</string> <string name="pref_automatic_download_on_battery_title">Download when not charging</string> <string name="pref_automatic_download_on_battery_sum">Allow automatic download when the battery is not charging</string> <string name="pref_parallel_downloads_title">Parallel Downloads</string> <string name="pref_episode_cache_title">Episode Cache</string> <string name="pref_episode_cache_summary">Total number of downloaded episodes cached on the device. Automatic download will be suspended if this number is reached.</string> + <string name="pref_theme_title_use_system">Use system theme</string> <string name="pref_theme_title_light">Light</string> <string name="pref_theme_title_dark">Dark</string> <string name="pref_theme_title_trueblack">Black (AMOLED ready)</string> @@ -443,7 +421,6 @@ <string name="pref_gpodnet_full_sync_sum">Sync all subscriptions and episode states with gpodder.net.</string> <string name="pref_gpodnet_sync_sum_last_sync_line">Last sync attempt: %1$s (%2$s)</string> <string name="pref_gpodnet_sync_started">Sync started</string> - <string name="pref_gpodnet_full_sync_started">Full sync started</string> <string name="pref_gpodnet_login_status"><![CDATA[Logged in as <i>%1$s</i> with device <i>%2$s</i>]]></string> <string name="pref_gpodnet_notifications_title">Show sync error notifications</string> <string name="pref_gpodnet_notifications_sum">This setting does not apply to authentication errors.</string> @@ -476,16 +453,17 @@ <string name="pref_smart_mark_as_played_disabled">Disabled</string> <string name="pref_image_cache_size_title">Image Cache Size</string> <string name="pref_image_cache_size_sum">Size of the disk cache for images.</string> - <string name="crash_report_title">Crash Report</string> - <string name="crash_report_sum">Send the latest crash report via e-mail</string> - <string name="send_email">Send e-mail</string> + <string name="view_mailing_list">View mailing list</string> + <string name="bug_report_title">Report bug</string> + <string name="open_bug_tracker">Open bug tracker</string> + <string name="copy_to_clipboard">Copy to clipboard</string> + <string name="copied_to_clipboard">Copied to clipboard</string> <string name="experimental_pref">Experimental</string> <string name="pref_media_player_message">Select which media player to use to play files</string> <string name="pref_current_value">Current value: %1$s</string> <string name="pref_proxy_title">Proxy</string> <string name="pref_proxy_sum">Set a network proxy</string> - <string name="pref_faq">FAQ</string> - <string name="pref_known_issues">Known issues</string> + <string name="pref_faq">Frequently Asked Questions</string> <string name="pref_no_browser_found">No web browser found.</string> <string name="pref_cast_title">Chromecast support</string> <string name="pref_cast_message_play_flavor">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string> @@ -526,21 +504,16 @@ <string name="no_results_for_query">No results were found for \"%1$s\"</string> <!-- OPML import and export --> - <string name="opml_import_txtv_button_lable">OPML files allow you to move your podcasts from one podcatcher to another.</string> <string name="opml_import_option">Option %1$d</string> <string name="opml_import_explanation_1">Choose a specific file path from the local filesystem.</string> - <string name="opml_import_explanation_2">Use an external applications like Dropbox, Google Drive or your favourite file manager to open an OPML file.</string> - <string name="opml_import_explanation_3">Many applications like Google Mail, Dropbox, Google Drive and most file managers can <i>open</i> OPML files <i>with</i> AntennaPod.</string> <string name="start_import_label">Start import</string> + <string name="opml_import_explanation_3">Many applications like Google Mail, Dropbox, Google Drive and most file managers can <i>open</i> OPML files <i>with</i> AntennaPod.</string> <string name="opml_import_label">OPML Import</string> - <string name="opml_directory_error">ERROR!</string> <string name="reading_opml_label">Reading OPML file</string> <string name="opml_reader_error">An error has occurred while reading the OPML document:</string> <string name="opml_import_error_no_file">No file selected!</string> <string name="select_all_label">Select all</string> <string name="deselect_all_label">Deselect all</string> - <string name="select_options_label">Select…</string> <string name="choose_file_from_filesystem">From local filesystem</string> - <string name="choose_file_from_external_application">Use external application</string> <string name="opml_export_label">OPML export</string> <string name="html_export_label">HTML export</string> <string name="exporting_label">Exporting…</string> @@ -639,7 +612,6 @@ <!-- Online feed view --> <string name="subscribe_label">Subscribe</string> - <string name="subscribed_label">Subscribed</string> <string name="downloading_label">Downloading…</string> <!-- Content descriptions for image buttons --> @@ -762,7 +734,6 @@ <string name="cast_failed_setting_volume">Failed to set the volume</string> <string name="cast_failed_no_connection">No connection to the cast device is present</string> <string name="cast_failed_no_connection_trans">Connection to the cast device has been lost. Application is trying to re-establish the connection, if possible. Please wait for a few seconds and try again.</string> - <string name="cast_failed_perform_action">Failed to perform the action</string> <string name="cast_failed_status_request">Failed to sync up with the cast device</string> <string name="cast_failed_seek">Failed to seek to the new position on the cast device</string> <string name="cast_failed_receiver_player_error">Receiver player has encountered a severe error</string> diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index fb69e54e7..d2ba4bb50 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -23,6 +23,7 @@ <item name="av_fast_forward">@drawable/ic_fast_forward_grey600_24dp</item> <item name="av_pause">@drawable/ic_pause_grey600_24dp</item> <item name="av_play">@drawable/ic_play_arrow_grey600_24dp</item> + <item name="av_speed">@drawable/ic_playback_speed_dark</item> <item name="av_rewind">@drawable/ic_fast_rewind_grey600_24dp</item> <item name="content_discard">@drawable/ic_delete_grey600_24dp</item> <item name="content_new">@drawable/ic_add_grey600_24dp</item> @@ -68,9 +69,9 @@ <item name="ic_create_new_folder">@drawable/ic_create_new_folder_grey600_24dp</item> <item name="ic_cast_disconnect">@drawable/ic_cast_disconnect_grey600_36dp</item> <item name="ic_cellphone_text">@drawable/ic_cellphone_text_grey600_24dp</item> - <item name="ic_question_answer">@drawable/ic_forum_grey600_24dp</item> + <item name="ic_questionmark">@drawable/ic_questionmark_grey600</item> + <item name="ic_chat">@drawable/ic_chat_grey600</item> <item name="ic_bug">@drawable/ic_bug_grey600_24dp</item> - <item name="ic_known_issues">@drawable/ic_format_list_bulleted_grey600_24dp</item> <item name="ic_bookmark">@drawable/ic_bookmark_grey600_24dp</item> <item name="batch_edit_fab_icon">@drawable/ic_fab_edit_white</item> <item name="master_switch_background">@color/master_switch_background_light</item> @@ -108,6 +109,7 @@ <item name="av_fast_forward">@drawable/ic_fast_forward_white_24dp</item> <item name="av_pause">@drawable/ic_pause_white_24dp</item> <item name="av_play">@drawable/ic_play_arrow_white_24dp</item> + <item name="av_speed">@drawable/ic_playback_speed_white</item> <item name="av_rewind">@drawable/ic_fast_rewind_white_24dp</item> <item name="content_discard">@drawable/ic_delete_white_24dp</item> <item name="content_new">@drawable/ic_add_white_24dp</item> @@ -153,9 +155,9 @@ <item name="ic_create_new_folder">@drawable/ic_create_new_folder_white_24dp</item> <item name="ic_cast_disconnect">@drawable/ic_cast_disconnect_white_36dp</item> <item name="ic_cellphone_text">@drawable/ic_cellphone_text_white_24dp</item> - <item name="ic_question_answer">@drawable/ic_baseline_question_answer_white_24dp</item> + <item name="ic_questionmark">@drawable/ic_questionmark_white</item> + <item name="ic_chat">@drawable/ic_chat_white</item> <item name="ic_bug">@drawable/ic_bug_white_24dp</item> - <item name="ic_known_issues">@drawable/ic_format_list_bulleted_white_24dp</item> <item name="ic_bookmark">@drawable/ic_bookmark_white_24dp</item> <item name="batch_edit_fab_icon">@drawable/ic_fab_edit_white</item> <item name="master_switch_background">@color/master_switch_background_dark</item> @@ -258,13 +260,6 @@ <item name="android:ellipsize">end</item> </style> - <style name="AntennaPod.Dialog.Title" parent="@android:style/TextAppearance.Medium"> - <item name="android:textSize">@dimen/text_size_medium</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> - <item name="android:maxLines">2</item> - <item name="android:ellipsize">end</item> - </style> - <style name="AntennaPod.TextView.UnreadIndicator" parent="@android:style/TextAppearance.Small"> <item name="android:textSize">@dimen/text_size_micro</item> <item name="android:textColor">@color/new_indicator_green</item> @@ -276,16 +271,6 @@ <item name="textAllCaps">false</item> </style> - <style name="Divider"> - <item name="android:layout_width">match_parent</item> - <item name="android:layout_height">1dp</item> - <item name="android:layout_marginTop">8dp</item> - <item name="android:layout_marginLeft">16dp</item> - <item name="android:layout_marginRight">16dp</item> - <item name="android:layout_marginBottom">8dp</item> - <item name="android:background">?android:attr/listDivider</item> - </style> - <style name="AntennaPod.Dialog.Light" parent="Theme.AppCompat.Light.Dialog"> <item name="colorAccent">@color/holo_blue_light</item> </style> diff --git a/core/src/play/java/de/danoeh/antennapod/core/CastCallbacks.java b/core/src/play/java/de/danoeh/antennapod/core/CastCallbacks.java index 770fee9b9..27f985a4c 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/CastCallbacks.java +++ b/core/src/play/java/de/danoeh/antennapod/core/CastCallbacks.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.core; -import android.support.annotation.Nullable; -import android.support.v7.app.MediaRouteDialogFactory; +import androidx.annotation.Nullable; +import androidx.mediarouter.app.MediaRouteDialogFactory; /** * Callbacks for Chromecast support on the core module diff --git a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java index 244fb0254..800222ada 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java +++ b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.core; import android.content.Context; +import android.util.Log; import de.danoeh.antennapod.core.cast.CastManager; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; @@ -15,6 +16,8 @@ import de.danoeh.antennapod.core.util.exception.RxJavaErrorHandlerSetup; * Apps using the core module of AntennaPod should register implementations of all interfaces here. */ public class ClientConfig { + private static final String TAG = "ClientConfig"; + private ClientConfig(){} /** @@ -44,7 +47,15 @@ public class ClientConfig { UserPreferences.init(context); PlaybackPreferences.init(context); NetworkUtils.init(context); - CastManager.init(context); + // Don't initialize Cast-related logic unless it is enabled, to avoid the unnecessary + // Google Play Service usage. + // Down side: when the user decides to enable casting, AntennaPod needs to be restarted + // for it to take effect. + if (UserPreferences.isCastEnabled()) { + CastManager.init(context); + } else { + Log.v(TAG, "Cast is disabled. All Cast-related initialization will be skipped."); + } SleepTimerPreferences.init(context); RxJavaErrorHandlerSetup.setupRxJavaErrorHandler(); initialized = true; diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/CastManager.java b/core/src/play/java/de/danoeh/antennapod/core/cast/CastManager.java index 5198a76bd..bf9ef3f5b 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/cast/CastManager.java +++ b/core/src/play/java/de/danoeh/antennapod/core/cast/CastManager.java @@ -24,10 +24,10 @@ package de.danoeh.antennapod.core.cast; import android.content.Context; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.v4.view.ActionProvider; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.media.MediaRouter; +import androidx.annotation.NonNull; +import androidx.core.view.ActionProvider; +import androidx.core.view.MenuItemCompat; +import androidx.mediarouter.media.MediaRouter; import android.util.Log; import android.view.KeyEvent; import android.view.MenuItem; @@ -163,6 +163,10 @@ public class CastManager extends BaseCastManager implements OnFailedListener { return INSTANCE; } + public static boolean isInitialized() { + return INSTANCE != null; + } + /** * Returns the active {@link RemoteMediaPlayer} instance. Since there are a number of media * control APIs that this library do not provide a wrapper for, client applications can call @@ -1731,7 +1735,7 @@ public class CastManager extends BaseCastManager implements OnFailedListener { * {@link SwitchableMediaRouteActionProvider} associated with the button if the caller needs * such reference. It is assumed that the enclosing * {@link android.app.Activity} inherits (directly or indirectly) from - * {@link android.support.v7.app.AppCompatActivity}. + * {@link androidx.appcompat.app.AppCompatActivity}. * * @param menuItem MenuItem of the Media Router cast button. */ diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/RemoteMedia.java b/core/src/play/java/de/danoeh/antennapod/core/cast/RemoteMedia.java index 1c6dd30c4..0614e5952 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/cast/RemoteMedia.java +++ b/core/src/play/java/de/danoeh/antennapod/core/cast/RemoteMedia.java @@ -5,7 +5,7 @@ import android.content.SharedPreferences; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.text.TextUtils; import com.google.android.gms.cast.MediaInfo; diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java b/core/src/play/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java index f063cf5e3..5a6a0aa2b 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java +++ b/core/src/play/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java @@ -3,12 +3,12 @@ package de.danoeh.antennapod.core.cast; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.MediaRouteActionProvider; -import android.support.v7.app.MediaRouteChooserDialogFragment; -import android.support.v7.app.MediaRouteControllerDialogFragment; -import android.support.v7.media.MediaRouter; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.mediarouter.app.MediaRouteActionProvider; +import androidx.mediarouter.app.MediaRouteChooserDialogFragment; +import androidx.mediarouter.app.MediaRouteControllerDialogFragment; +import androidx.mediarouter.media.MediaRouter; import android.util.Log; /** diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java index 7ab1be380..c32ba2385 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -7,11 +7,11 @@ import android.content.IntentFilter; import android.net.NetworkInfo; import android.net.wifi.WifiManager; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; -import android.support.v7.media.MediaRouter; +import androidx.mediarouter.media.MediaRouter; import android.support.wearable.media.MediaControlConstants; import android.util.Log; import android.widget.Toast; @@ -56,11 +56,18 @@ public class PlaybackServiceFlavorHelper { PlaybackServiceFlavorHelper(Context context, PlaybackService.FlavorHelperCallback callback) { this.callback = callback; + if (!CastManager.isInitialized()) { + return; + } mediaRouter = MediaRouter.getInstance(context.getApplicationContext()); setCastConsumer(context); } void initializeMediaPlayer(Context context) { + if (!CastManager.isInitialized()) { + callback.setMediaPlayer(new LocalPSMP(context, callback.getMediaPlayerCallback())); + return; + } castManager = CastManager.getInstance(); castManager.addCastConsumer(castConsumer); boolean isCasting = castManager.isConnected(); @@ -77,10 +84,16 @@ public class PlaybackServiceFlavorHelper { } void removeCastConsumer() { + if (!CastManager.isInitialized()) { + return; + } castManager.removeCastConsumer(castConsumer); } boolean castDisconnect(boolean castDisconnect) { + if (!CastManager.isInitialized()) { + return false; + } if (castDisconnect) { castManager.disconnect(); } @@ -88,6 +101,9 @@ public class PlaybackServiceFlavorHelper { } boolean onMediaPlayerInfo(Context context, int code, @StringRes int resourceId) { + if (!CastManager.isInitialized()) { + return false; + } switch (code) { case RemotePSMP.CAST_ERROR: callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_SHOW_TOAST, resourceId); @@ -218,6 +234,9 @@ public class PlaybackServiceFlavorHelper { } void registerWifiBroadcastReceiver() { + if (!CastManager.isInitialized()) { + return; + } if (wifiBroadcastReceiver != null) { return; } @@ -243,6 +262,9 @@ public class PlaybackServiceFlavorHelper { } void unregisterWifiBroadcastReceiver() { + if (!CastManager.isInitialized()) { + return; + } if (wifiBroadcastReceiver != null) { callback.unregisterReceiver(wifiBroadcastReceiver); wifiBroadcastReceiver = null; @@ -250,6 +272,9 @@ public class PlaybackServiceFlavorHelper { } boolean onSharedPreference(String key) { + if (!CastManager.isInitialized()) { + return false; + } if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { if (!UserPreferences.isCastEnabled()) { if (castManager.isConnecting() || castManager.isConnected()) { @@ -263,6 +288,9 @@ public class PlaybackServiceFlavorHelper { } void sessionStateAddActionForWear(PlaybackStateCompat.Builder sessionState, String actionName, CharSequence name, int icon) { + if (!CastManager.isInitialized()) { + return; + } PlaybackStateCompat.CustomAction.Builder actionBuilder = new PlaybackStateCompat.CustomAction.Builder(actionName, name, icon); Bundle actionExtras = new Bundle(); @@ -273,6 +301,9 @@ public class PlaybackServiceFlavorHelper { } void mediaSessionSetExtraForWear(MediaSessionCompat mediaSession) { + if (!CastManager.isInitialized()) { + return; + } Bundle sessionExtras = new Bundle(); sessionExtras.putBoolean(MediaControlConstants.EXTRA_RESERVE_SLOT_SKIP_TO_PREVIOUS, true); sessionExtras.putBoolean(MediaControlConstants.EXTRA_RESERVE_SLOT_SKIP_TO_NEXT, true); diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java index f91264c59..63fc05cb8 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java @@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.service.playback; import android.content.Context; import android.media.MediaPlayer; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import android.util.Pair; import android.view.SurfaceHolder; diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..431e485f2 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +android.useAndroidX=true +android.enableJetifier=true +org.gradle.jvmargs=-Xmx4096m |