Merge branch 'develop' into feedinfo-fragment

This commit is contained in:
H. Lehmann 2019-09-24 17:50:26 +02:00 committed by GitHub
commit 049771d67c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
170 changed files with 917 additions and 1063 deletions

View File

@ -1,7 +1,7 @@
version: 2
jobs:
build:
test:
docker:
- image: circleci/android:api-28
@ -21,11 +21,24 @@ jobs:
- 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
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 debug
command: ./gradlew assembleDebug -PdisablePreDex
- run:
name: Build release
command: ./gradlew assembleRelease -PdisablePreDex
- run:
name: Execute unit tests
command: ./gradlew :core:testPlayReleaseUnitTest -PdisablePreDex
- run:
name: Build integration tests
command: ./gradlew :app:assemblePlayDebugAndroidTest -PdisablePreDex
- store_artifacts:
path: app/build/outputs/apk
@ -37,3 +50,26 @@ jobs:
- ~/.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
test:
jobs:
- test
checkstyle:
jobs:
- checkstyle

View File

@ -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

View File

@ -1,6 +1,17 @@
Change Log
==========
Version 1.7.3
-------------
* Display episode image on widget (by @brad)
* Added checkbox to keep queue sorted (by @damoasda)
* New UI for "Add podcast" screen (by @ByteHamster)
* Added batch editing to the queue (by @ByteHamster)
* Added option to adapt remaining time to playback speed (by @CedricCabessa)
* Removed broken Flattr integration (by @ByteHamster)
* Added filter to "All episodes" list (by @jhunnius)
* Tons of bug fixes and performance improvements
Version 1.7.2
-------------
* Added configurable behavior of the back button

View File

@ -18,8 +18,8 @@ android {
// "1.2.3-SNAPSHOT" -> 1020300
// "1.2.3-RC4" -> 1020304
// "1.2.3" -> 1020395
versionCode 1070307
versionName "1.7.3-RC7"
versionCode 1070395
versionName "1.7.3"
testApplicationId "de.test.antennapod"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
generatedDensities = []

View File

@ -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;

View File

@ -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);
// }
// }
}

View File

@ -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"/>
@ -204,6 +204,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"

View File

@ -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";
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,64 @@
package de.danoeh.antennapod.activity;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;
import de.danoeh.antennapod.CrashReportWriter;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
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 -> {
try {
Intent myIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse("https://github.com/AntennaPod/AntennaPod/issues"));
startActivity(myIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.pref_no_browser_found, Toast.LENGTH_LONG).show();
}
});
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();
});
}
}

View File

@ -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());

View File

@ -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,10 +34,11 @@ import android.widget.Toast;
import com.bumptech.glide.Glide;
import de.danoeh.antennapod.fragment.TransitionEffect;
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,14 +70,13 @@ import de.danoeh.antennapod.fragment.FeedItemlistFragment;
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
import de.danoeh.antennapod.fragment.QueueFragment;
import de.danoeh.antennapod.fragment.SubscriptionFragment;
import de.danoeh.antennapod.fragment.TransitionEffect;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
/**
* The activity that is shown when the user launches the app.
@ -94,7 +96,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";
@ -128,6 +130,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());
@ -241,7 +251,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(PREF_IS_FIRST_LAUNCH, false);
edit.commit();
edit.apply();
}
}

View File

@ -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,34 @@ 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));
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 +638,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 +816,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 +871,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;
}
}
}

View File

@ -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);

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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();
}
});
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
@ -35,19 +41,15 @@ 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.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.EmptyViewHandler;
@ -55,13 +57,6 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Shows unread or recently published episodes
@ -149,7 +144,7 @@ public abstract class EpisodesListFragment extends Fragment {
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(PREF_SCROLL_POSITION, firstItem);
editor.putFloat(PREF_SCROLL_OFFSET, topOffset);
editor.commit();
editor.apply();
}
private void restoreScrollPosition() {
@ -162,7 +157,7 @@ public abstract class EpisodesListFragment extends Fragment {
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(PREF_SCROLL_POSITION, 0);
editor.putFloat(PREF_SCROLL_OFFSET, 0.0f);
editor.commit();
editor.apply();
}
}
@ -262,17 +257,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
@ -453,36 +438,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));
}
}

View File

@ -322,7 +322,7 @@ public class FeedItemlistFragment extends ListFragment {
contextMenu = menu;
lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null);
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item);
}
@Override
@ -339,7 +339,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

View File

@ -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;
@ -336,10 +335,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 +350,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);
}
}

View File

@ -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

View File

@ -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;
@ -53,15 +59,11 @@ import de.danoeh.antennapod.core.util.SortOrder;
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 +93,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 +106,7 @@ public class QueueFragment extends Fragment {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
}
@Override
@ -219,15 +224,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,19 +302,7 @@ 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();
@ -394,6 +385,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 +463,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 +639,7 @@ public class QueueFragment extends Fragment {
/ playbackSpeed);
}
}
info += " \u2022 ";
info += " ";
info += getString(R.string.time_left_label);
info += Converter.getDurationStringLocalized(getActivity(), timeLeft);
}

View File

@ -1,28 +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;
public class MainPreferencesFragment extends PreferenceFragmentCompat {
private static final String TAG = "MainPreferencesFragment";
@ -31,9 +24,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,35 +71,16 @@ 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");
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 -> {
openInBrowser("https://groups.google.com/forum/#!forum/antennapod");
return true;
});
findPreference(PREF_SEND_BUG_REPORT).setOnPreferenceClickListener(preference -> {
startActivity(new Intent(getActivity(), BugReportActivity.class));
return true;
});
}

View File

@ -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() {

View File

@ -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);
@ -249,4 +247,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));
}
}

View File

@ -4,6 +4,7 @@ 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.gui.NotificationUtils;
@ -62,5 +63,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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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"/>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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"/>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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>

View File

@ -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"

View File

@ -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"

View File

@ -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/**")
}

View File

@ -0,0 +1,28 @@
<?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="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="OneStatementPerLine"/>
<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>
</module>

View File

@ -9,6 +9,11 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- ACCESS_FINE_LOCATION is needed only on Android 10+,
for Automatic Download Wifi filter's UI, which uses
WifiManager.WifiManager.getConfiguredNetworks()
-->
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"

View File

@ -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);
}
}

View File

@ -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

View File

@ -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() {
@ -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;
}
}
@ -890,13 +895,6 @@ public class UserPreferences {
}
}
/**
* 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.
*/

View File

@ -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;
}

View File

@ -546,7 +546,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) {
@ -1076,7 +1076,7 @@ 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 ");
StringBuilder line = new StringBuilder(" ");
DownloadRequest request = downloader.getDownloadRequest();
switch (request.getFeedfileType()) {
case Feed.FEEDFILETYPE_FEED:

View File

@ -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;
}

View File

@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.view.SurfaceHolder;
import com.google.android.exoplayer2.C;
@ -27,7 +28,6 @@ import com.google.android.exoplayer2.util.Util;
import org.antennapod.audio.MediaPlayer;
import de.danoeh.antennapod.core.util.playback.IPlayer;
public class ExoPlayerWrapper implements IPlayer {
private final Context mContext;
private SimpleExoPlayer mExoPlayer;
@ -35,15 +35,35 @@ public class ExoPlayerWrapper implements IPlayer {
private MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener;
private MediaPlayer.OnCompletionListener audioCompletionListener;
private MediaPlayer.OnErrorListener audioErrorListener;
private MediaPlayer.OnBufferingUpdateListener bufferingUpdateListener;
private boolean shouldCheckBufferingUpdates = true;
ExoPlayerWrapper(Context context) {
mContext = context;
mExoPlayer = createPlayer();
Handler handler = new Handler(); // Main thread
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (bufferingUpdateListener != null) {
bufferingUpdateListener.onBufferingUpdate(null, mExoPlayer.getBufferedPercentage());
}
if (shouldCheckBufferingUpdates) {
handler.postDelayed(this, 2000);
}
}
}, 2000);
}
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
@ -148,12 +168,14 @@ public class ExoPlayerWrapper implements IPlayer {
@Override
public void release() {
shouldCheckBufferingUpdates = false;
if (mExoPlayer != null) {
mExoPlayer.release();
}
audioSeekCompleteListener = null;
audioCompletionListener = null;
audioErrorListener = null;
bufferingUpdateListener = null;
}
@Override
@ -247,4 +269,8 @@ public class ExoPlayerWrapper implements IPlayer {
}
return mExoPlayer.getVideoFormat().height;
}
void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener bufferingUpdateListener) {
this.bufferingUpdateListener = bufferingUpdateListener;
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -193,11 +193,6 @@ public final class DBTasks {
}).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);
}
/**
* @param context
* @param feedList the list of feeds to refresh

View File

@ -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
*/

View File

@ -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.

View File

@ -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;
}
}
}

View File

@ -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.

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

Some files were not shown because too many files have changed in this diff Show More