Merge branch 'develop' into smart-spread-fix
|
@ -1,39 +1,103 @@
|
|||
version: 2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test-debug:
|
||||
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: Build debug
|
||||
command: ./gradlew assembleDebug -PdisablePreDex
|
||||
- run:
|
||||
name: Execute debug unit tests
|
||||
command: ./gradlew :core:testPlayDebugUnitTest -PdisablePreDex
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.android
|
||||
- ~/.gradle
|
||||
- ~/android
|
||||
key: v1-android-{{ checksum "build.gradle" }}
|
||||
|
||||
test-release:
|
||||
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" }}
|
||||
- v1-android-
|
||||
- run:
|
||||
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"
|
||||
- run:
|
||||
name: Build release
|
||||
command: ./gradlew assembleRelease -PdisablePreDex
|
||||
- run:
|
||||
name: Execute release unit tests
|
||||
command: ./gradlew :core:testPlayReleaseUnitTest -PdisablePreDex
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.android
|
||||
- ~/.gradle
|
||||
- ~/android
|
||||
key: v1-android-{{ checksum "build.gradle" }}
|
||||
|
||||
build-androidtest:
|
||||
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" }}
|
||||
- v1-android-
|
||||
- run:
|
||||
name: Build integration tests
|
||||
command: ./gradlew :app:assemblePlayDebugAndroidTest -PdisablePreDex
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.android
|
||||
- ~/.gradle
|
||||
- ~/android
|
||||
key: v1-android-{{ checksum "build.gradle" }}
|
||||
|
||||
checkstyle:
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
working_directory: ~/AntennaPod
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Checkstyle
|
||||
command: ./gradlew checkstyle
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
unit-tests:
|
||||
jobs:
|
||||
- test-debug
|
||||
- test-release
|
||||
- build-androidtest
|
||||
|
||||
static-analysis:
|
||||
jobs:
|
||||
- checkstyle
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -173,8 +173,8 @@ dependencies {
|
|||
|
||||
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'
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -7,212 +7,9 @@ import android.support.v7.app.AppCompatActivity;
|
|||
* 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);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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"/>
|
||||
|
@ -209,6 +209,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"
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,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 +58,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/", "");
|
||||
|
|
|
@ -60,11 +60,13 @@ public class AudioplayerActivity extends MediaplayerInfoActivity {
|
|||
}
|
||||
if (controller == null) {
|
||||
butPlaybackSpeed.setVisibility(View.GONE);
|
||||
txtvPlaybackSpeed.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
updatePlaybackSpeedButtonText();
|
||||
ViewCompat.setAlpha(butPlaybackSpeed, controller.canSetPlaybackSpeed() ? 1.0f : 0.5f);
|
||||
butPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -74,14 +76,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();
|
||||
}
|
||||
String speedStr = new DecimalFormat("0.00x").format(speed);
|
||||
butPlaybackSpeed.setText(speedStr);
|
||||
String speedStr = new DecimalFormat("0.00").format(speed);
|
||||
txtvPlaybackSpeed.setText(speedStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -136,6 +139,7 @@ public class AudioplayerActivity extends MediaplayerInfoActivity {
|
|||
return true;
|
||||
});
|
||||
butPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 android.support.design.widget.Snackbar;
|
||||
import android.support.v7.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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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,6 +11,7 @@ import android.database.DataSetObserver;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -67,13 +71,11 @@ import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
|||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -322,6 +322,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);
|
||||
|
||||
|
@ -389,29 +391,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:
|
||||
|
@ -448,28 +445,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:
|
||||
|
@ -635,7 +637,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
}
|
||||
}
|
||||
|
||||
static public void showSkipPreference(Activity activity, SkipDirection direction) {
|
||||
public static void showSkipPreference(Activity activity, SkipDirection direction) {
|
||||
int checked = 0;
|
||||
int skipSecs = direction.getPrefSkipSeconds();
|
||||
final int[] values = activity.getResources().getIntArray(R.array.seek_delta_values);
|
||||
|
@ -813,11 +815,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;
|
||||
}
|
||||
|
@ -872,4 +870,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ 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;
|
||||
|
@ -92,7 +93,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;
|
||||
|
@ -258,6 +260,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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -262,7 +262,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 +277,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
@ -169,7 +168,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 +184,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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package de.danoeh.antennapod.asynctask;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,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 +60,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();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,7 @@ 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;
|
||||
|
@ -23,8 +21,16 @@ 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 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 +39,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 +56,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 +143,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 +156,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 +196,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 +253,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
|
||||
|
@ -412,7 +393,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 +434,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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -323,7 +323,7 @@ public class FeedItemlistFragment extends ListFragment {
|
|||
|
||||
contextMenu = menu;
|
||||
lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null);
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -340,7 +340,7 @@ public class FeedItemlistFragment extends ListFragment {
|
|||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
|
||||
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -390,10 +390,10 @@ 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,13 +96,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 +153,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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.support.v7.widget.RecyclerView;
|
|||
import android.support.v7.widget.helper.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
|
||||
|
|
|
@ -7,6 +7,7 @@ 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.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
|
@ -19,11 +20,16 @@ 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 org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
|
@ -35,14 +41,12 @@ import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
|||
import de.danoeh.antennapod.core.event.FeedItemEvent;
|
||||
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.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 +54,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.dialog.EpisodesApplyActionFragment.ACTION_DELETE;
|
||||
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE;
|
||||
|
@ -91,10 +92,12 @@ public class QueueFragment extends Fragment {
|
|||
private static final String PREFS = "QueueFragment";
|
||||
private static final String PREF_SCROLL_POSITION = "scroll_position";
|
||||
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
|
||||
private static final String PREF_SHOW_LOCK_WARNING = "show_lock_warning";
|
||||
|
||||
private Disposable disposable;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private ItemTouchHelper itemTouchHelper;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -102,6 +105,7 @@ public class QueueFragment extends Fragment {
|
|||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setHasOptionsMenu(true);
|
||||
prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -196,8 +200,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) {
|
||||
|
@ -219,15 +223,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) {
|
||||
|
@ -299,25 +301,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
|
||||
|
@ -394,6 +381,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.
|
||||
*
|
||||
|
@ -430,7 +459,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -606,7 +635,7 @@ public class QueueFragment extends Fragment {
|
|||
/ playbackSpeed);
|
||||
}
|
||||
}
|
||||
info += " \u2022 ";
|
||||
info += " • ";
|
||||
info += getString(R.string.time_left_label);
|
||||
info += Converter.getDurationStringLocalized(getActivity(), timeLeft);
|
||||
}
|
||||
|
|
|
@ -25,19 +25,28 @@ import de.danoeh.antennapod.activity.MainActivity;
|
|||
import de.danoeh.antennapod.adapter.SubscriptionsAdapter;
|
||||
import de.danoeh.antennapod.core.asynctask.FeedRemover;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
||||
import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
||||
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 +65,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 +99,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 +109,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 +151,7 @@ public class SubscriptionFragment extends Fragment {
|
|||
public void onStart() {
|
||||
super.onStart();
|
||||
EventDistributor.getInstance().register(contentUpdate);
|
||||
EventBus.getDefault().register(this);
|
||||
loadSubscriptions();
|
||||
}
|
||||
|
||||
|
@ -143,6 +159,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 +295,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() {
|
||||
|
|
|
@ -1,29 +1,41 @@
|
|||
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.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
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 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.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);
|
||||
|
@ -175,10 +187,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
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 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 +25,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";
|
||||
|
||||
|
@ -78,47 +72,18 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
|||
return true;
|
||||
}
|
||||
);
|
||||
findPreference(PREF_KNOWN_ISSUES).setOnPreferenceClickListener(preference -> {
|
||||
openInBrowser("https://github.com/AntennaPod/AntennaPod/labels/bug");
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> {
|
||||
openInBrowser("https://antennapod.org/faq.html");
|
||||
IntentUtils.openInBrowser(getContext(), "https://antennapod.org/faq.html");
|
||||
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_VIEW_MAILING_LIST).setOnPreferenceClickListener(preference -> {
|
||||
IntentUtils.openInBrowser(getContext(), "https://groups.google.com/forum/#!forum/antennapod");
|
||||
return true;
|
||||
});
|
||||
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() {
|
||||
|
|
|
@ -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;
|
||||
|
@ -13,13 +14,16 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.provider.DocumentFile;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.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.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 +49,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
|
||||
|
@ -59,6 +69,14 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
|||
setDataFolderText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupStorageScreen() {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
|
@ -69,9 +87,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 +154,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 +222,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 +248,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 +279,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() {
|
||||
|
|
|
@ -3,7 +3,10 @@ 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 android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -18,7 +21,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 +53,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 +75,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 +92,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 +103,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 +130,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 +140,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 +157,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 +214,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 +240,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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -86,14 +86,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);
|
||||
|
|
|
@ -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();
|
||||
|
@ -62,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"/>
|
||||
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
android:contentDescription="@string/cover_label"
|
||||
android:gravity="center_vertical"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
tools:src="@drawable/ic_stat_antenna_default"
|
||||
tools:src="@drawable/ic_antenna"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"/>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -23,7 +23,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
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
custom:showAsAction="collapseActionView">
|
||||
</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"
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -34,6 +34,14 @@
|
|||
custom:showAsAction="always">
|
||||
</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"
|
||||
|
|
|
@ -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>
|
|
@ -2,6 +2,13 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package de.danoeh.antennapod.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.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();
|
||||
}
|
||||
}
|
||||
|
|
15
build.gradle
|
@ -57,8 +57,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"
|
||||
|
@ -82,3 +82,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/**")
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
@ -92,6 +91,7 @@ dependencies {
|
|||
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'
|
||||
|
|
|
@ -25,4 +25,8 @@ public class DownloadEvent {
|
|||
"update=" + update +
|
||||
'}';
|
||||
}
|
||||
|
||||
public boolean hasChangedFeedUpdateStatus(boolean oldStatus) {
|
||||
return oldStatus != update.feedIds.length > 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -2,6 +2,7 @@ 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;
|
||||
|
@ -164,7 +165,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() {
|
||||
|
@ -621,7 +622,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -631,7 +632,7 @@ public class UserPreferences {
|
|||
prefs.edit()
|
||||
.putString(PREF_UPDATE_INTERVAL, hourOfDay + ":" + minute)
|
||||
.apply();
|
||||
restartUpdateAlarm();
|
||||
AutoUpdateManager.restartUpdateAlarm();
|
||||
}
|
||||
|
||||
public static void disableAutoUpdate() {
|
||||
|
@ -672,14 +673,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;
|
||||
}
|
||||
}
|
||||
|
@ -877,26 +882,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.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,17 +2,23 @@ package de.danoeh.antennapod.core.service;
|
|||
|
||||
import android.content.Context;
|
||||
import android.support.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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.os.Build;
|
|||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
@ -190,10 +191,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 +212,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 +384,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);
|
||||
}
|
||||
|
@ -546,7 +550,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 +582,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
|
||||
|
@ -1058,7 +1072,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 +1096,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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -26,26 +26,45 @@ import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
|||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
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 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);
|
||||
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
|
||||
|
@ -150,12 +169,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
|
||||
|
@ -253,4 +274,8 @@ public class ExoPlayerWrapper implements IPlayer {
|
|||
}
|
||||
return mExoPlayer.getVideoFormat().height;
|
||||
}
|
||||
|
||||
void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener bufferingUpdateListener) {
|
||||
this.bufferingUpdateListener = bufferingUpdateListener;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1025,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);
|
||||
|
|
|
@ -1086,7 +1086,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
editor.putInt(
|
||||
PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus);
|
||||
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private void writePlayerStatusPlaybackPreferences() {
|
||||
|
@ -1095,11 +1095,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
SharedPreferences.Editor editor = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext()).edit();
|
||||
int playerStatus = getCurrentPlayerStatusAsInt(mediaPlayer.getPlayerStatus());
|
||||
|
||||
editor.putInt(
|
||||
PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus);
|
||||
|
||||
editor.commit();
|
||||
editor.putInt(PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private void sendNotificationBroadcast(int type, int code) {
|
||||
|
|
|
@ -115,7 +115,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 +124,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 +133,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 +149,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 +158,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()) {
|
||||
|
|
|
@ -3,7 +3,7 @@ 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.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -144,58 +144,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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -55,16 +55,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";
|
||||
|
@ -1400,13 +1394,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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +1,55 @@
|
|||
package de.danoeh.antennapod.core.util.download;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.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() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -728,6 +728,8 @@ public class PlaybackController {
|
|||
public void setPlaybackSpeed(float speed) {
|
||||
if (playbackService != null) {
|
||||
playbackService.setSpeed(speed);
|
||||
} else {
|
||||
onPlaybackSpeedChange();
|
||||
}
|
||||
}
|
||||
public void setSkipSilence(boolean skipSilence) {
|
||||
|
|
Before Width: | Height: | Size: 187 B |
Before Width: | Height: | Size: 770 B |
Before Width: | Height: | Size: 975 B |
Before Width: | Height: | Size: 867 B |
Before Width: | Height: | Size: 961 B |
Before Width: | Height: | Size: 979 B |
Before Width: | Height: | Size: 976 B |
Before Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 493 B |