Merge branch 'develop' into Queue-Sort-Order

This commit is contained in:
damoasda 2019-08-04 09:59:32 +02:00 committed by GitHub
commit 51f348caa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 2024 additions and 3080 deletions

View File

@ -3,7 +3,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/android:api-26-alpha
- image: circleci/android:api-28
working_directory: ~/AntennaPod
@ -13,7 +13,7 @@ jobs:
steps:
- checkout
- restore_cache:
keys:
- v1-android-{{ checksum "build.gradle" }}
@ -24,7 +24,7 @@ jobs:
# 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 assemblePlayRelease :core:testPlayReleaseUnitTest -PdisablePreDex
./gradlew assembleRelease :core:testPlayReleaseUnitTest -PdisablePreDex
no_output_timeout: 1800
- store_artifacts:

1
.gitignore vendored
View File

@ -39,7 +39,6 @@ contributers.py
proguard
libs
*.DS_Store
src/de/danoeh/antennapod/util/flattr/FlattrConfig.java
gradle.properties
*.keystore
*.p12

View File

@ -59,16 +59,10 @@ android {
buildTypes {
def STRING = "String"
def FLATTR_APP_KEY = "FLATTR_APP_KEY"
def FLATTR_APP_SECRET = "FLATTR_APP_SECRET"
def mFlattrAppKey = (project.hasProperty("flattrAppKey")) ? flattrAppKey : "\"\""
def mFlattrAppSecret = (project.hasProperty("flattrAppSecret")) ? flattrAppSecret : "\"\""
debug {
applicationIdSuffix ".debug"
resValue "string", "provider_authority", "de.danoeh.antennapod.debug.provider"
buildConfigField STRING, FLATTR_APP_KEY, mFlattrAppKey
buildConfigField STRING, FLATTR_APP_SECRET, mFlattrAppSecret
dexcount {
if (project.hasProperty("enableDexcountInDebug")) {
runOnEachPackage enableDexcountInDebug.toBoolean()
@ -82,8 +76,6 @@ android {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard.cfg"
signingConfig signingConfigs.releaseConfig
buildConfigField STRING, FLATTR_APP_KEY, mFlattrAppKey
buildConfigField STRING, FLATTR_APP_SECRET, mFlattrAppSecret
}
}
@ -145,9 +137,6 @@ dependencies {
annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
implementation("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") {
exclude group: "org.json", module: "json"
}
implementation "commons-io:commons-io:$commonsioVersion"
implementation "org.jsoup:jsoup:$jsoupVersion"
implementation "com.github.bumptech.glide:glide:$glideVersion"
@ -176,7 +165,7 @@ dependencies {
implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion"
implementation 'com.github.mfietz:fyydlin:v0.4.2'
implementation 'com.github.ByteHamster:SearchPreference:v1.2.5'
implementation 'com.github.ByteHamster:SearchPreference:v1.2.6'
implementation "org.awaitility:awaitility:$awaitilityVersion"
androidTestImplementation "com.jayway.android.robotium:robotium-solo:$robotiumSoloVersion"

View File

@ -75,9 +75,6 @@
-dontwarn android.support.v7.**
-dontwarn com.google.android.wearable.**
-keep class org.shredzone.flattr4j.** { *; }
-dontwarn org.shredzone.flattr4j.**
-keep class org.apache.commons.** { *; }
-dontskipnonpubliclibraryclassmembers

View File

@ -14,7 +14,6 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
/**
* Utility methods for DB* tests.
@ -46,7 +45,7 @@ class DBTestUtils {
adapter.open();
for (int i = 0; i < numFeeds; i++) {
Feed f = new Feed(0, null, "feed " + i, null, "link" + i, "descr", null, null,
null, null, "id" + i, null, null, "url" + i, false, new FlattrStatus(), false, null, null, false);
null, null, "id" + i, null, null, "url" + i, false, false, null, null, false);
f.setItems(new ArrayList<>());
for (int j = 0; j < numItems; j++) {
FeedItem item = new FeedItem(0, "item " + j, "id" + j, "link" + j, new Date(),

View File

@ -14,7 +14,6 @@ class GeneratorUtil {
String ns = (withNamespace) ? "http://www.w3.org/2005/Atom" : null;
xml.startTag(ns, "link");
xml.attribute(null, "rel", "payment");
xml.attribute(null, "title", "Flattr this!");
xml.attribute(null, "href", paymentLink);
xml.attribute(null, "type", "text/html");
xml.endTag(ns, "link");

View File

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<supports-screens
android:anyDensity="true"
@ -141,25 +142,6 @@
<activity android:name=".activity.StorageErrorActivity">
</activity>
<activity
android:name=".activity.FlattrAuthActivity"
android:label="@string/flattr_auth_label">
<intent-filter>
<action android:name=".activities.FlattrAuthActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="de.danoeh.antennapod"
android:scheme="flattr4j"/>
</intent-filter>
</activity>
<activity
android:name=".activity.AboutActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
@ -337,8 +319,7 @@
<activity
android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"
android:configChanges="orientation"
android:label="@string/gpodnet_auth_label"
android:screenOrientation="portrait">
android:label="@string/gpodnet_auth_label">
<intent-filter>
<action android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
<category android:name="android.intent.category.DEFAULT"/>

View File

@ -199,8 +199,6 @@ public class FeedInfoActivity extends AppCompatActivity {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.support_item).setVisible(
feed != null && feed.getPaymentLink() != null);
menu.findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null);
menu.findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null &&
IntentUtils.isCallable(this, new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));

View File

@ -1,120 +0,0 @@
package de.danoeh.antennapod.activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.shredzone.flattr4j.exception.FlattrException;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
/** Guides the user through the authentication process */
public class FlattrAuthActivity extends AppCompatActivity {
private static final String TAG = "FlattrAuthActivity";
private TextView txtvExplanation;
private Button butAuthenticate;
private Button butReturn;
private boolean authSuccessful;
private static FlattrAuthActivity singleton;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
singleton = this;
authSuccessful = false;
if (BuildConfig.DEBUG) Log.d(TAG, "Activity created");
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.flattr_auth);
txtvExplanation = findViewById(R.id.txtvExplanation);
butAuthenticate = findViewById(R.id.but_authenticate);
butReturn = findViewById(R.id.but_return_home);
butReturn.setOnClickListener(v -> {
Intent intent = new Intent(FlattrAuthActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
});
butAuthenticate.setOnClickListener(v -> {
try {
FlattrUtils.startAuthProcess(FlattrAuthActivity.this);
} catch (FlattrException e) {
e.printStackTrace();
}
});
}
public static FlattrAuthActivity getInstance() {
return singleton;
}
@Override
protected void onResume() {
super.onResume();
if (BuildConfig.DEBUG) Log.d(TAG, "Activity resumed");
Uri uri = getIntent().getData();
if (uri != null) {
if (BuildConfig.DEBUG) Log.d(TAG, "Received uri");
FlattrUtils.handleCallback(this, uri);
}
}
public void handleAuthenticationSuccess() {
authSuccessful = true;
txtvExplanation.setText(R.string.flattr_auth_success);
butAuthenticate.setEnabled(false);
butReturn.setVisibility(View.VISIBLE);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}
@Override
protected void onPause() {
super.onPause();
if (authSuccessful) {
finish();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (authSuccessful) {
Intent intent = new Intent(this, PreferenceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
} else {
finish();
}
break;
default:
return false;
}
return true;
}
}

View File

@ -208,7 +208,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
checkFirstLaunch();
PreferenceUpgrader.checkUpgrades(this);
NotificationUtils.createChannels(this);
}
private void saveLastNavFragment(String tag) {
@ -579,17 +578,17 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
}
Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
switch(item.getItemId()) {
case R.id.mark_all_seen_item:
ConfirmationDialog markAllSeenConfirmationDialog = new ConfirmationDialog(this,
R.string.mark_all_seen_label,
R.string.mark_all_seen_confirmation_msg) {
case R.id.remove_all_new_flags_item:
ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(this,
R.string.remove_all_new_flags_label,
R.string.remove_all_new_flags_confirmation_msg) {
@Override
public void onConfirmButtonPressed(DialogInterface dialog) {
dialog.dismiss();
DBWriter.markFeedSeen(feed.getId());
DBWriter.removeFeedNewFlag(feed.getId());
}
};
markAllSeenConfirmationDialog.createNewDialog().show();
removeAllNewFlagsConfirmationDialog.createNewDialog().show();
return true;
case R.id.mark_all_read_item:
ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(this,

View File

@ -330,11 +330,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
Playable media = controller.getMedia();
boolean isFeedMedia = media != null && (media instanceof FeedMedia);
menu.findItem(R.id.support_item).setVisible(isFeedMedia && media.getPaymentLink() != null &&
((FeedMedia) media).getItem() != null &&
((FeedMedia) media).getItem().getFlattrStatus().flattrable()
);
boolean hasWebsiteLink = ( getWebsiteLinkWithFallback(media) != null );
menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink);
@ -603,11 +598,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
Uri uri = Uri.parse(getWebsiteLinkWithFallback(media));
startActivity(new Intent(Intent.ACTION_VIEW, uri));
break;
case R.id.support_item:
if (media instanceof FeedMedia) {
DBTasks.flattrItemIfLoggedIn(this, ((FeedMedia) media).getItem());
}
break;
case R.id.share_link_item:
if (media instanceof FeedMedia) {
ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem());

View File

@ -46,7 +46,6 @@ import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.dialog.RenameFeedDialog;
@ -170,6 +169,7 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem
pager.setCurrentItem(lastPosition);
}
@Override
protected void onStart() {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
@ -339,8 +339,8 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem
}
Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
switch(item.getItemId()) {
case R.id.mark_all_seen_item:
DBWriter.markFeedSeen(feed.getId());
case R.id.remove_all_new_flags_item:
DBWriter.removeFeedNewFlag(feed.getId());
return true;
case R.id.mark_all_read_item:
DBWriter.markFeedRead(feed.getId());

View File

@ -15,7 +15,6 @@ import com.bytehamster.lib.preferencesearch.SearchPreferenceResultListener;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.fragment.preferences.AutoDownloadPreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.FlattrPreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.GpodderPreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.IntegrationsPreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.MainPreferencesFragment;
@ -64,8 +63,6 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe
prefFragment = new AutoDownloadPreferencesFragment();
} else if (screen == R.xml.preferences_gpodder) {
prefFragment = new GpodderPreferencesFragment();
} else if (screen == R.xml.preferences_flattr) {
prefFragment = new FlattrPreferencesFragment();
} else if (screen == R.xml.preferences_playback) {
prefFragment = new PlaybackPreferencesFragment();
}
@ -86,8 +83,6 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe
return R.string.user_interface_label;
case R.xml.preferences_integrations:
return R.string.integrations_label;
case R.xml.preferences_flattr:
return R.string.flattr_label;
case R.xml.preferences_gpodder:
return R.string.gpodnet_main_label;
default:

View File

@ -104,10 +104,6 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity {
return super.onOptionsItemSelected(item);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
}
private void setupLoginView(View view) {
final EditText username = view.findViewById(R.id.etxtUsername);
final EditText password = view.findViewById(R.id.etxtPassword);

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;
@ -280,7 +279,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
};
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null);
contextMenuInterface.setItemVisibility(R.id.mark_as_seen_item, item.isNew());
contextMenuInterface.setItemVisibility(R.id.remove_new_flag_item, item.isNew());
}
}

View File

@ -49,9 +49,10 @@ public class DataFolderAdapter extends RecyclerView.Adapter<DataFolderAdapter.Vi
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
StoragePath storagePath = entries.get(position);
String freeSpace = Converter.byteToString(storagePath.getAvailableSpace());
String totalSpace = Converter.byteToString(storagePath.getTotalSpace());
holder.path.setText(storagePath.getShortPath());
holder.size.setText(String.format(freeSpaceString, freeSpace));
holder.size.setText(String.format(freeSpaceString, freeSpace, totalSpace));
holder.progressBar.setProgress(storagePath.getUsagePercentage());
holder.root.setOnClickListener((View v) -> selectAndDismiss(storagePath));
holder.radioButton.setOnClickListener((View v) -> selectAndDismiss(storagePath));

View File

@ -0,0 +1,76 @@
package de.danoeh.antennapod.adapter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.discovery.PodcastSearchResult;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class FeedDiscoverAdapter extends BaseAdapter {
private final WeakReference<MainActivity> mainActivityRef;
private final List<PodcastSearchResult> data = new ArrayList<>();
public FeedDiscoverAdapter(MainActivity mainActivity) {
this.mainActivityRef = new WeakReference<>(mainActivity);
}
public void updateData(List<PodcastSearchResult> newData) {
data.clear();
data.addAll(newData);
notifyDataSetChanged();
}
@Override
public int getCount() {
return data.size();
}
@Override
public PodcastSearchResult getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
if (convertView == null) {
convertView = View.inflate(mainActivityRef.get(), R.layout.quick_feed_discovery_item, null);
holder = new Holder();
holder.imageView = convertView.findViewById(R.id.discovery_cover);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
final PodcastSearchResult podcast = getItem(position);
Glide.with(mainActivityRef.get())
.load(podcast.imageUrl)
.apply(new RequestOptions()
.placeholder(R.color.light_gray)
.fitCenter()
.dontAnimate())
.into(holder.imageView);
return convertView;
}
static class Holder {
ImageView imageView;
}
}

View File

@ -2,7 +2,6 @@ package de.danoeh.antennapod.adapter.itunes;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
@ -13,18 +12,14 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import de.danoeh.antennapod.discovery.PodcastSearchResult;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.mfietz.fyydlin.SearchHit;
public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
public class ItunesAdapter extends ArrayAdapter<PodcastSearchResult> {
/**
* Related Context
*/
@ -33,7 +28,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
/**
* List holding the podcasts found in the search
*/
private final List<Podcast> data;
private final List<PodcastSearchResult> data;
/**
* Constructor.
@ -41,7 +36,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
* @param context Related context
* @param objects Search result
*/
public ItunesAdapter(Context context, List<Podcast> objects) {
public ItunesAdapter(Context context, List<PodcastSearchResult> objects) {
super(context, 0, objects);
this.data = objects;
this.context = context;
@ -51,7 +46,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
//Current podcast
Podcast podcast = data.get(position);
PodcastSearchResult podcast = data.get(position);
//ViewHolder
PodcastViewHolder viewHolder;
@ -93,75 +88,6 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
return view;
}
/**
* Represents an individual podcast on the iTunes Store.
*/
public static class Podcast { //TODO: Move this out eventually. Possibly to core.itunes.model
/**
* The name of the podcast
*/
public final String title;
/**
* URL of the podcast image
*/
@Nullable
public final String imageUrl;
/**
* URL of the podcast feed
*/
@Nullable
public final String feedUrl;
private Podcast(String title, @Nullable String imageUrl, @Nullable String feedUrl) {
this.title = title;
this.imageUrl = imageUrl;
this.feedUrl = feedUrl;
}
/**
* Constructs a Podcast instance from a iTunes search result
*
* @param json object holding the podcast information
* @throws JSONException
*/
public static Podcast fromSearch(JSONObject json) {
String title = json.optString("collectionName", "");
String imageUrl = json.optString("artworkUrl100", null);
String feedUrl = json.optString("feedUrl", null);
return new Podcast(title, imageUrl, feedUrl);
}
public static Podcast fromSearch(SearchHit searchHit) {
return new Podcast(searchHit.getTitle(), searchHit.getThumbImageURL(), searchHit.getXmlUrl());
}
/**
* Constructs a Podcast instance from iTunes toplist entry
*
* @param json object holding the podcast information
* @throws JSONException
*/
public static Podcast fromToplist(JSONObject json) throws JSONException {
String title = json.getJSONObject("title").getString("label");
String imageUrl = null;
JSONArray images = json.getJSONArray("im:image");
for(int i=0; imageUrl == null && i < images.length(); i++) {
JSONObject image = images.getJSONObject(i);
String height = image.getJSONObject("attributes").getString("height");
if(Integer.parseInt(height) >= 100) {
imageUrl = image.getString("label");
}
}
String feedUrl = "https://itunes.apple.com/lookup?id=" +
json.getJSONObject("id").getJSONObject("attributes").getString("im:id");
return new Podcast(title, imageUrl, feedUrl);
}
}
/**
* View holder object for the GridView
*/

View File

@ -16,7 +16,6 @@ class ClientConfigurator {
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
ClientConfig.gpodnetCallbacks = new GpodnetCallbacksImpl();
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
ClientConfig.flattrCallbacks = new FlattrCallbacksImpl();
ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
ClientConfig.castCallbacks = new CastCallbackImpl();
}

View File

@ -1,53 +0,0 @@
package de.danoeh.antennapod.config;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.shredzone.flattr4j.oauth.AccessToken;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.activity.FlattrAuthActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.FlattrCallbacks;
public class FlattrCallbacksImpl implements FlattrCallbacks {
private static final String TAG = "FlattrCallbacksImpl";
@Override
public boolean flattrEnabled() {
return true;
}
@Override
public Intent getFlattrAuthenticationActivityIntent(Context context) {
return new Intent(context, FlattrAuthActivity.class);
}
@Override
public PendingIntent getFlattrFailedNotificationContentIntent(Context context) {
return PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0);
}
@Override
public String getFlattrAppKey() {
return BuildConfig.FLATTR_APP_KEY;
}
@Override
public String getFlattrAppSecret() {
return BuildConfig.FLATTR_APP_SECRET;
}
@Override
public void handleFlattrAuthenticationSuccess(AccessToken token) {
FlattrAuthActivity instance = FlattrAuthActivity.getInstance();
if (instance != null) {
instance.handleAuthenticationSuccess();
} else {
Log.e(TAG, "FlattrAuthActivity instance was null");
}
}
}

View File

@ -1,97 +0,0 @@
package de.danoeh.antennapod.dialog;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.CheckBox;
import android.widget.SeekBar;
import android.widget.TextView;
import org.apache.commons.lang3.Validate;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
/**
* Creates a new AlertDialog that displays preferences for auto-flattring to the user.
*/
public class AutoFlattrPreferenceDialog {
private AutoFlattrPreferenceDialog() {
}
public static void newAutoFlattrPreferenceDialog(final Activity activity, final AutoFlattrPreferenceDialogInterface callback) {
Validate.notNull(activity);
Validate.notNull(callback);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@SuppressLint("InflateParams") View view = activity.getLayoutInflater().inflate(R.layout.autoflattr_preference_dialog, null);
final CheckBox chkAutoFlattr = view.findViewById(R.id.chkAutoFlattr);
final SeekBar skbPercent = view.findViewById(R.id.skbPercent);
final TextView txtvStatus = view.findViewById(R.id.txtvStatus);
chkAutoFlattr.setChecked(UserPreferences.isAutoFlattr());
skbPercent.setEnabled(chkAutoFlattr.isChecked());
txtvStatus.setEnabled(chkAutoFlattr.isChecked());
final int initialValue = (int) (UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100.0f);
setStatusMsgText(activity, txtvStatus, initialValue);
skbPercent.setProgress(initialValue);
chkAutoFlattr.setOnClickListener(v -> {
skbPercent.setEnabled(chkAutoFlattr.isChecked());
txtvStatus.setEnabled(chkAutoFlattr.isChecked());
});
skbPercent.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
setStatusMsgText(activity, txtvStatus, progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
builder.setTitle(R.string.pref_auto_flattr_title)
.setView(view)
.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
float progDouble = ((float) skbPercent.getProgress()) / 100.0f;
callback.onConfirmed(chkAutoFlattr.isChecked(), progDouble);
dialog.dismiss();
})
.setNegativeButton(R.string.cancel_label, (dialog, which) -> {
callback.onCancelled();
dialog.dismiss();
})
.setCancelable(false).show();
}
private static void setStatusMsgText(Context context, TextView txtvStatus, int progress) {
if (progress == 0) {
txtvStatus.setText(R.string.auto_flattr_ater_beginning);
} else if (progress == 100) {
txtvStatus.setText(R.string.auto_flattr_ater_end);
} else {
txtvStatus.setText(context.getString(R.string.auto_flattr_after_percent, progress));
}
}
public interface AutoFlattrPreferenceDialogInterface {
void onCancelled();
void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue);
}
}

View File

@ -3,6 +3,7 @@ package de.danoeh.antennapod.dialog;
import android.app.AlertDialog;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
@ -12,6 +13,7 @@ import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.util.ArrayMap;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
@ -45,7 +47,7 @@ public class EpisodesApplyActionFragment extends Fragment {
public static final String TAG = "EpisodeActionFragment";
public static final int ACTION_ADD_TO_QUEUE = 1;
private static final int ACTION_REMOVE_FROM_QUEUE = 2;
public static final int ACTION_REMOVE_FROM_QUEUE = 2;
private static final int ACTION_MARK_PLAYED = 4;
private static final int ACTION_MARK_UNPLAYED = 8;
private static final int ACTION_DOWNLOAD = 16;
@ -203,6 +205,10 @@ public class EpisodesApplyActionFragment extends Fragment {
return true;
});
if (Build.VERSION.SDK_INT == 23 || Build.VERSION.SDK_INT == 24) {
ViewCompat.setElevation(view.findViewById(R.id.fabSDScrollCtr), 8);
}
showSpeedDialIfAnyChecked();
return view;
@ -218,10 +224,6 @@ public class EpisodesApplyActionFragment extends Fragment {
mSpeedDialView.setVisibility(checkedIds.size() > 0 ? View.VISIBLE : View.GONE);
}
private void hideSpeedDial() {
mSpeedDialView.setVisibility(View.GONE);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);

View File

@ -0,0 +1,96 @@
package de.danoeh.antennapod.discovery;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
public class CombinedSearcher implements PodcastSearcher {
private static final String TAG = "CombinedSearcher";
private final List<Pair<PodcastSearcher, Float>> searchProviders = new ArrayList<>();
public CombinedSearcher(Context context) {
addProvider(new FyydPodcastSearcher(), 1.f);
addProvider(new ItunesPodcastSearcher(context), 1.f);
addProvider(new GpodnetPodcastSearcher(), 0.6f);
}
private void addProvider(PodcastSearcher provider, float priority) {
searchProviders.add(new Pair<>(provider, priority));
}
public Single<List<PodcastSearchResult>> search(String query) {
ArrayList<Disposable> disposables = new ArrayList<>();
List<List<PodcastSearchResult>> singleResults = new ArrayList<>(Collections.nCopies(searchProviders.size(), null));
CountDownLatch latch = new CountDownLatch(searchProviders.size());
for (int i = 0; i < searchProviders.size(); i++) {
Pair<PodcastSearcher, Float> searchProviderInfo = searchProviders.get(i);
PodcastSearcher searcher = searchProviderInfo.first;
final int index = i;
disposables.add(searcher.search(query).subscribe(e -> {
singleResults.set(index, e);
latch.countDown();
}, throwable -> {
Log.d(TAG, Log.getStackTraceString(throwable));
latch.countDown();
}
));
}
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
latch.await();
List<PodcastSearchResult> results = weightSearchResults(singleResults);
subscriber.onSuccess(results);
})
.doOnDispose(() -> {
for (Disposable disposable : disposables) {
disposable.dispose();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private List<PodcastSearchResult> weightSearchResults(List<List<PodcastSearchResult>> singleResults) {
HashMap<String, Float> resultRanking = new HashMap<>();
HashMap<String, PodcastSearchResult> urlToResult = new HashMap<>();
for (int i = 0; i < singleResults.size(); i++) {
float providerPriority = searchProviders.get(i).second;
List<PodcastSearchResult> providerResults = singleResults.get(i);
if (providerResults == null) {
continue;
}
for (int position = 0; position < providerResults.size(); position++) {
PodcastSearchResult result = providerResults.get(position);
urlToResult.put(result.feedUrl, result);
float ranking = 0;
if (resultRanking.containsKey(result.feedUrl)) {
ranking = resultRanking.get(result.feedUrl);
}
ranking += 1.f / (position + 1.f);
resultRanking.put(result.feedUrl, ranking * providerPriority);
}
}
List<Map.Entry<String, Float>> sortedResults = new ArrayList<>(resultRanking.entrySet());
Collections.sort(sortedResults, (o1, o2) -> Double.compare(o2.getValue(), o1.getValue()));
List<PodcastSearchResult> results = new ArrayList<>();
for (Map.Entry<String, Float> res : sortedResults) {
results.add(urlToResult.get(res.getKey()));
}
return results;
}
}

View File

@ -0,0 +1,38 @@
package de.danoeh.antennapod.discovery;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.mfietz.fyydlin.FyydClient;
import de.mfietz.fyydlin.FyydResponse;
import de.mfietz.fyydlin.SearchHit;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
public class FyydPodcastSearcher implements PodcastSearcher {
private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient());
public Single<List<PodcastSearchResult>> search(String query) {
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
FyydResponse response = client.searchPodcasts(query, 10)
.subscribeOn(Schedulers.io())
.blockingGet();
ArrayList<PodcastSearchResult> searchResults = new ArrayList<>();
if (!response.getData().isEmpty()) {
for (SearchHit searchHit : response.getData()) {
PodcastSearchResult podcast = PodcastSearchResult.fromFyyd(searchHit);
searchResults.add(podcast);
}
}
subscriber.onSuccess(searchResults);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}

View File

@ -0,0 +1,38 @@
package de.danoeh.antennapod.discovery;
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
public class GpodnetPodcastSearcher implements PodcastSearcher {
public Single<List<PodcastSearchResult>> search(String query) {
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
GpodnetService service = null;
try {
service = new GpodnetService();
List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0);
List<PodcastSearchResult> results = new ArrayList<>();
for (GpodnetPodcast podcast : gpodnetPodcasts) {
results.add(PodcastSearchResult.fromGpodder(podcast));
}
subscriber.onSuccess(results);
} catch (GpodnetServiceException e) {
e.printStackTrace();
subscriber.onError(e);
} finally {
if (service != null) {
service.shutdown();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}

View File

@ -0,0 +1,74 @@
package de.danoeh.antennapod.discovery;
import android.content.Context;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
public class ItunesPodcastSearcher implements PodcastSearcher {
private static final String ITUNES_API_URL = "https://itunes.apple.com/search?media=podcast&term=%s";
private final Context context;
public ItunesPodcastSearcher(Context context) {
this.context = context;
}
public Single<List<PodcastSearchResult>> search(String query) {
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
String encodedQuery;
try {
encodedQuery = URLEncoder.encode(query, "UTF-8");
} catch (UnsupportedEncodingException e) {
// this won't ever be thrown
encodedQuery = query;
}
String formattedUrl = String.format(ITUNES_API_URL, encodedQuery);
OkHttpClient client = AntennapodHttpClient.getHttpClient();
Request.Builder httpReq = new Request.Builder()
.url(formattedUrl)
.header("User-Agent", ClientConfig.USER_AGENT);
List<PodcastSearchResult> podcasts = new ArrayList<>();
try {
Response response = client.newCall(httpReq.build()).execute();
if (response.isSuccessful()) {
String resultString = response.body().string();
JSONObject result = new JSONObject(resultString);
JSONArray j = result.getJSONArray("results");
for (int i = 0; i < j.length(); i++) {
JSONObject podcastJson = j.getJSONObject(i);
PodcastSearchResult podcast = PodcastSearchResult.fromItunes(podcastJson);
podcasts.add(podcast);
}
} else {
String prefix = context.getString(R.string.error_msg_prefix);
subscriber.onError(new IOException(prefix + response));
}
} catch (IOException | JSONException e) {
subscriber.onError(e);
}
subscriber.onSuccess(podcasts);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}

View File

@ -0,0 +1,110 @@
package de.danoeh.antennapod.discovery;
import android.content.Context;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class ItunesTopListLoader {
private final Context context;
public ItunesTopListLoader(Context context) {
this.context = context;
}
public Single<List<PodcastSearchResult>> loadToplist(int limit) {
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) emitter -> {
String lang = Locale.getDefault().getLanguage();
OkHttpClient client = AntennapodHttpClient.getHttpClient();
String feedString;
try {
try {
feedString = getTopListFeed(client, lang, limit);
} catch (IOException e) {
feedString = getTopListFeed(client, "us", limit);
}
List<PodcastSearchResult> podcasts = parseFeed(feedString);
emitter.onSuccess(podcasts);
} catch (IOException | JSONException e) {
emitter.onError(e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public Single<String> getFeedUrl(PodcastSearchResult podcast) {
if (!podcast.feedUrl.contains("itunes.apple.com")) {
return Single.just(podcast.feedUrl)
.observeOn(AndroidSchedulers.mainThread());
}
return Single.create((SingleOnSubscribe<String>) emitter -> {
OkHttpClient client = AntennapodHttpClient.getHttpClient();
Request.Builder httpReq = new Request.Builder()
.url(podcast.feedUrl)
.header("User-Agent", ClientConfig.USER_AGENT);
try {
Response response = client.newCall(httpReq.build()).execute();
if (response.isSuccessful()) {
String resultString = response.body().string();
JSONObject result = new JSONObject(resultString);
JSONObject results = result.getJSONArray("results").getJSONObject(0);
String feedUrl = results.getString("feedUrl");
emitter.onSuccess(feedUrl);
} else {
String prefix = context.getString(R.string.error_msg_prefix);
emitter.onError(new IOException(prefix + response));
}
} catch (IOException | JSONException e) {
emitter.onError(e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private String getTopListFeed(OkHttpClient client, String language, int limit) throws IOException {
String url = "https://itunes.apple.com/%s/rss/toppodcasts/limit="+limit+"/explicit=true/json";
Request.Builder httpReq = new Request.Builder()
.header("User-Agent", ClientConfig.USER_AGENT)
.url(String.format(url, language));
try (Response response = client.newCall(httpReq.build()).execute()) {
if (response.isSuccessful()) {
return response.body().string();
}
String prefix = context.getString(R.string.error_msg_prefix);
throw new IOException(prefix + response);
}
}
private List<PodcastSearchResult> parseFeed(String jsonString) throws JSONException {
JSONObject result = new JSONObject(jsonString);
JSONObject feed = result.getJSONObject("feed");
JSONArray entries = feed.getJSONArray("entry");
List<PodcastSearchResult> results = new ArrayList<>();
for (int i=0; i < entries.length(); i++) {
JSONObject json = entries.getJSONObject(i);
results.add(PodcastSearchResult.fromItunesToplist(json));
}
return results;
}
}

View File

@ -0,0 +1,81 @@
package de.danoeh.antennapod.discovery;
import android.support.annotation.Nullable;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
import de.mfietz.fyydlin.SearchHit;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class PodcastSearchResult {
/**
* The name of the podcast
*/
public final String title;
/**
* URL of the podcast image
*/
@Nullable
public final String imageUrl;
/**
* URL of the podcast feed
*/
@Nullable
public final String feedUrl;
private PodcastSearchResult(String title, @Nullable String imageUrl, @Nullable String feedUrl) {
this.title = title;
this.imageUrl = imageUrl;
this.feedUrl = feedUrl;
}
public static PodcastSearchResult dummy() {
return new PodcastSearchResult("", "", "");
}
/**
* Constructs a Podcast instance from a iTunes search result
*
* @param json object holding the podcast information
* @throws JSONException
*/
public static PodcastSearchResult fromItunes(JSONObject json) {
String title = json.optString("collectionName", "");
String imageUrl = json.optString("artworkUrl100", null);
String feedUrl = json.optString("feedUrl", null);
return new PodcastSearchResult(title, imageUrl, feedUrl);
}
/**
* Constructs a Podcast instance from iTunes toplist entry
*
* @param json object holding the podcast information
* @throws JSONException
*/
public static PodcastSearchResult fromItunesToplist(JSONObject json) throws JSONException {
String title = json.getJSONObject("title").getString("label");
String imageUrl = null;
JSONArray images = json.getJSONArray("im:image");
for(int i=0; imageUrl == null && i < images.length(); i++) {
JSONObject image = images.getJSONObject(i);
String height = image.getJSONObject("attributes").getString("height");
if(Integer.parseInt(height) >= 100) {
imageUrl = image.getString("label");
}
}
String feedUrl = "https://itunes.apple.com/lookup?id=" +
json.getJSONObject("id").getJSONObject("attributes").getString("im:id");
return new PodcastSearchResult(title, imageUrl, feedUrl);
}
public static PodcastSearchResult fromFyyd(SearchHit searchHit) {
return new PodcastSearchResult(searchHit.getTitle(), searchHit.getThumbImageURL(), searchHit.getXmlUrl());
}
public static PodcastSearchResult fromGpodder(GpodnetPodcast searchHit) {
return new PodcastSearchResult(searchHit.getTitle(), searchHit.getLogoUrl(), searchHit.getUrl());
}
}

View File

@ -0,0 +1,10 @@
package de.danoeh.antennapod.discovery;
import io.reactivex.Single;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import java.util.List;
public interface PodcastSearcher {
Single<List<PodcastSearchResult>> search(String query);
}

View File

@ -2,13 +2,20 @@ package de.danoeh.antennapod.fragment;
import android.content.Intent;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
@ -27,11 +34,28 @@ public class AddFeedFragment extends Fragment {
*/
private static final String ARG_FEED_URL = "feedurl";
private EditText combinedFeedSearchBox;
private MainActivity activity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View root = inflater.inflate(R.layout.addfeed, container, false);
activity = (MainActivity) getActivity();
activity.getSupportActionBar().setTitle(R.string.add_feed_label);
setupAdvancedSearchButtons(root);
setupSeachBox(root);
View butOpmlImport = root.findViewById(R.id.btn_opml_import);
butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(),
OpmlImportFromPathActivity.class)));
return root;
}
private void setupSeachBox(View root) {
final EditText etxtFeedurl = root.findViewById(R.id.etxtFeedurl);
Bundle args = getArguments();
@ -39,32 +63,69 @@ public class AddFeedFragment extends Fragment {
etxtFeedurl.setText(args.getString(ARG_FEED_URL));
}
Button butSearchITunes = root.findViewById(R.id.butSearchItunes);
Button butBrowserGpoddernet = root.findViewById(R.id.butBrowseGpoddernet);
Button butSearchFyyd = root.findViewById(R.id.butSearchFyyd);
Button butOpmlImport = root.findViewById(R.id.butOpmlImport);
Button butConfirm = root.findViewById(R.id.butConfirm);
final MainActivity activity = (MainActivity) getActivity();
activity.getSupportActionBar().setTitle(R.string.add_feed_label);
butSearchITunes.setOnClickListener(v -> activity.loadChildFragment(new ItunesSearchFragment()));
butBrowserGpoddernet.setOnClickListener(v -> activity.loadChildFragment(new GpodnetMainFragment()));
butSearchFyyd.setOnClickListener(v -> activity.loadChildFragment(new FyydSearchFragment()));
butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(),
OpmlImportFromPathActivity.class)));
butConfirm.setOnClickListener(v -> {
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, etxtFeedurl.getText().toString());
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label));
startActivity(intent);
Button butConfirmAddUrl = root.findViewById(R.id.butConfirm);
butConfirmAddUrl.setOnClickListener(v -> {
addUrl(etxtFeedurl.getText().toString());
});
return root;
combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox);
combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
performSearch();
return true;
}
return false;
});
}
private void setupAdvancedSearchButtons(View root) {
View butAdvancedSearch = root.findViewById(R.id.advanced_search);
registerForContextMenu(butAdvancedSearch);
butAdvancedSearch.setOnClickListener(v -> butAdvancedSearch.showContextMenu());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
getActivity().getMenuInflater().inflate(R.menu.advanced_search, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.search_fyyd:
activity.loadChildFragment(new FyydSearchFragment());
return true;
case R.id.search_gpodder:
activity.loadChildFragment(new GpodnetMainFragment());
return true;
case R.id.search_itunes:
activity.loadChildFragment(new ItunesSearchFragment());
return true;
}
return false;
}
private void addUrl(String url) {
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, url);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label));
startActivity(intent);
}
private void performSearch() {
String query = combinedFeedSearchBox.getText().toString();
if (query.startsWith("http")) {
addUrl(query);
return;
}
Bundle bundle = new Bundle();
bundle.putString(CombinedSearchFragment.ARGUMENT_QUERY, query);
CombinedSearchFragment fragment = new CombinedSearchFragment();
fragment.setArguments(bundle);
activity.loadChildFragment(fragment);
}
@Override

View File

@ -21,11 +21,15 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
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;
@ -40,6 +44,7 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
@ -50,15 +55,11 @@ import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.LongList;
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;
/**
* Shows unread or recently published episodes
@ -81,11 +82,10 @@ public class AllEpisodesFragment extends Fragment {
private ProgressBar progLoading;
EmptyViewHandler emptyView;
List<FeedItem> episodes;
private List<Downloader> downloaderList;
private boolean itemsLoaded = false;
private boolean viewsCreated = false;
@NonNull
List<FeedItem> episodes = new ArrayList<>();
@NonNull
private List<Downloader> downloaderList = new ArrayList<>();
private boolean isUpdatingFeeds;
boolean isMenuInvalidationAllowed = false;
@ -93,29 +93,26 @@ public class AllEpisodesFragment extends Fragment {
Disposable disposable;
private LinearLayoutManager layoutManager;
boolean showOnlyNewEpisodes() { return false; }
String getPrefName() { return DEFAULT_PREF_NAME; }
boolean showOnlyNewEpisodes() {
return false;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
String getPrefName() {
return DEFAULT_PREF_NAME;
}
@Override
public void onStart() {
super.onStart();
setHasOptionsMenu(true);
EventDistributor.getInstance().register(contentUpdate);
if (viewsCreated && itemsLoaded) {
onFragmentLoaded();
}
EventBus.getDefault().register(this);
loadItems();
}
@Override
public void onResume() {
super.onResume();
loadItems();
registerForContextMenu(recyclerView);
}
@ -136,17 +133,11 @@ public class AllEpisodesFragment extends Fragment {
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
resetViewState();
}
private void saveScrollPosition() {
int firstItem = layoutManager.findFirstVisibleItemPosition();
View firstItemView = layoutManager.findViewByPosition(firstItem);
float topOffset;
if(firstItemView == null) {
if (firstItemView == null) {
topOffset = 0;
} else {
topOffset = firstItemView.getTop();
@ -173,43 +164,35 @@ public class AllEpisodesFragment extends Fragment {
}
}
void resetViewState() {
viewsCreated = false;
listAdapter = null;
}
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker =
() -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if(!isAdded()) {
if (!isAdded()) {
return;
}
super.onCreateOptionsMenu(menu, inflater);
if (itemsLoaded) {
inflater.inflate(R.menu.episodes, menu);
inflater.inflate(R.menu.episodes, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
MenuItemUtils.adjustTextColor(getActivity(), sv);
sv.setQueryHint(getString(R.string.search_hint));
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
sv.clearFocus();
((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s));
return true;
}
MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
MenuItemUtils.adjustTextColor(getActivity(), sv);
sv.setQueryHint(getString(R.string.search_hint));
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
sv.clearFocus();
((MainActivity) requireActivity()).loadChildFragment(SearchFragment.newInstance(s));
return true;
}
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
@Override
@ -217,11 +200,11 @@ public class AllEpisodesFragment extends Fragment {
super.onPrepareOptionsMenu(menu);
MenuItem markAllRead = menu.findItem(R.id.mark_all_read_item);
if (markAllRead != null) {
markAllRead.setVisible(!showOnlyNewEpisodes() && episodes != null && !episodes.isEmpty());
markAllRead.setVisible(!showOnlyNewEpisodes() && !episodes.isEmpty());
}
MenuItem markAllSeen = menu.findItem(R.id.mark_all_seen_item);
if(markAllSeen != null) {
markAllSeen.setVisible(showOnlyNewEpisodes() && episodes != null && !episodes.isEmpty());
MenuItem removeAllNewFlags = menu.findItem(R.id.remove_all_new_flags_item);
if (removeAllNewFlags != null) {
removeAllNewFlags.setVisible(showOnlyNewEpisodes() && !episodes.isEmpty());
}
}
@ -249,19 +232,19 @@ public class AllEpisodesFragment extends Fragment {
};
markAllReadConfirmationDialog.createNewDialog().show();
return true;
case R.id.mark_all_seen_item:
ConfirmationDialog markAllSeenConfirmationDialog = new ConfirmationDialog(getActivity(),
R.string.mark_all_seen_label,
R.string.mark_all_seen_confirmation_msg) {
case R.id.remove_all_new_flags_item:
ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getActivity(),
R.string.remove_all_new_flags_label,
R.string.remove_all_new_flags_confirmation_msg) {
@Override
public void onConfirmButtonPressed(DialogInterface dialog) {
dialog.dismiss();
DBWriter.markNewItemsSeen();
Toast.makeText(getActivity(), R.string.mark_all_seen_msg, Toast.LENGTH_SHORT).show();
DBWriter.removeAllNewFlags();
Toast.makeText(getActivity(), R.string.removed_all_new_flags_msg, Toast.LENGTH_SHORT).show();
}
};
markAllSeenConfirmationDialog.createNewDialog().show();
removeAllNewFlagsConfirmationDialog.createNewDialog().show();
return true;
default:
return false;
@ -275,111 +258,102 @@ public class AllEpisodesFragment extends Fragment {
@Override
public boolean onContextItemSelected(MenuItem item) {
Log.d(TAG, "onContextItemSelected() called with: " + "item = [" + item + "]");
if(!isVisible()) {
if (!getUserVisibleHint()) {
return false;
}
if(item.getItemId() == R.id.share_item) {
if (!isVisible()) {
return false;
}
if (item.getItemId() == R.id.share_item) {
return true; // avoids that the position is reset when we need it in the submenu
}
if (listAdapter == null || listAdapter.getSelectedItem() == null) {
if (listAdapter.getSelectedItem() == null) {
Log.i(TAG, "Selected item or listAdapter was null, ignoring selection");
return super.onContextItemSelected(item);
}
FeedItem selectedItem = listAdapter.getSelectedItem();
// Mark as seen contains UI logic specific to All/New/FavoriteSegments,
// 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 Mark as seen, given there is no UI to undo it otherwise,
// 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.mark_as_seen_item == item.getItemId()) {
markItemAsSeenWithUndo(selectedItem);
if (R.id.remove_new_flag_item == item.getItemId()) {
removeNewFlagWithUndo(selectedItem);
return true;
}
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
}
@NonNull
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return onCreateViewHelper(inflater, container, savedInstanceState,
R.layout.all_episodes_fragment);
}
View onCreateViewHelper(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState,
int fragmentResource) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View root = inflater.inflate(R.layout.all_episodes_fragment, container, false);
View root = inflater.inflate(fragmentResource, container, false);
layoutManager = new LinearLayoutManager(getActivity());
recyclerView = root.findViewById(android.R.id.list);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setVisibility(View.GONE);
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
progLoading = root.findViewById(R.id.progLoading);
if (!itemsLoaded) {
progLoading.setVisibility(View.VISIBLE);
}
viewsCreated = true;
if (itemsLoaded) {
onFragmentLoaded();
}
progLoading.setVisibility(View.VISIBLE);
emptyView = new EmptyViewHandler(getContext());
emptyView.attachToRecyclerView(recyclerView);
emptyView.setIcon(R.attr.feed);
emptyView.setTitle(R.string.no_all_episodes_head_label);
emptyView.setMessage(R.string.no_all_episodes_label);
createRecycleAdapter(recyclerView, emptyView);
emptyView.hide();
return root;
}
private void onFragmentLoaded() {
if (episodes != null && episodes.size() > 0) {
if (listAdapter == null) {
MainActivity mainActivity = (MainActivity) getActivity();
listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, showOnlyNewEpisodes());
listAdapter.setHasStableIds(true);
recyclerView.setAdapter(listAdapter);
emptyView.updateAdapter(listAdapter);
}
recyclerView.setVisibility(View.VISIBLE);
listAdapter.notifyDataSetChanged();
} else {
listAdapter = null;
recyclerView.setVisibility(View.GONE);
emptyView.updateAdapter(listAdapter);
private void onFragmentLoaded(List<FeedItem> episodes) {
this.episodes = episodes;
listAdapter.notifyDataSetChanged();
if (episodes.size() == 0) {
createRecycleAdapter(recyclerView, emptyView);
}
restoreScrollPosition();
getActivity().supportInvalidateOptionsMenu();
updateShowOnlyEpisodesListViewState();
requireActivity().invalidateOptionsMenu();
}
/**
* Currently, we need to recreate the list adapter in order to be able to undo last item via the
* snackbar. See #3084 for details.
*/
private void createRecycleAdapter(RecyclerView recyclerView, EmptyViewHandler emptyViewHandler) {
MainActivity mainActivity = (MainActivity) getActivity();
listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, showOnlyNewEpisodes());
listAdapter.setHasStableIds(true);
recyclerView.setAdapter(listAdapter);
emptyViewHandler.updateAdapter(listAdapter);
}
private final AllEpisodesRecycleAdapter.ItemAccess itemAccess = new AllEpisodesRecycleAdapter.ItemAccess() {
@Override
public int getCount() {
if (episodes != null) {
return episodes.size();
}
return 0;
return episodes.size();
}
@Override
public FeedItem getItem(int position) {
if (episodes != null && 0 <= position && position < episodes.size()) {
if (0 <= position && position < episodes.size()) {
return episodes.get(position);
}
return null;
@ -387,11 +361,8 @@ public class AllEpisodesFragment extends Fragment {
@Override
public LongList getItemsIds() {
if(episodes == null) {
return new LongList(0);
}
LongList ids = new LongList(episodes.size());
for(FeedItem episode : episodes) {
for (FeedItem episode : episodes) {
ids.add(episode.getId());
}
return ids;
@ -399,12 +370,11 @@ public class AllEpisodesFragment extends Fragment {
@Override
public int getItemDownloadProgressPercent(FeedItem item) {
if (downloaderList != null) {
for (Downloader downloader : downloaderList) {
if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
&& downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) {
return downloader.getDownloadRequest().getProgressPercent();
}
for (Downloader downloader : downloaderList) {
DownloadRequest downloadRequest = downloader.getDownloadRequest();
if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
&& downloadRequest.getFeedfileId() == item.getMedia().getId()) {
return downloadRequest.getProgressPercent();
}
}
return 0;
@ -418,11 +388,8 @@ public class AllEpisodesFragment extends Fragment {
@Override
public LongList getQueueIds() {
LongList queueIds = new LongList();
if(episodes == null) {
return queueIds;
}
for(FeedItem item : episodes) {
if(item.isTagged(FeedItem.TAG_QUEUE)) {
for (FeedItem item : episodes) {
if (item.isTagged(FeedItem.TAG_QUEUE)) {
queueIds.add(item.getId());
}
}
@ -434,12 +401,6 @@ public class AllEpisodesFragment extends Fragment {
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedItemEvent event) {
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
if (episodes == null) {
return;
} else if (listAdapter == null) {
loadItems();
return;
}
for (FeedItem item : event.items) {
int pos = FeedItemUtil.indexOfItemWithId(episodes, item.getId());
if (pos >= 0) {
@ -464,16 +425,12 @@ public class AllEpisodesFragment extends Fragment {
DownloaderUpdate update = event.update;
downloaderList = update.downloaders;
if (isMenuInvalidationAllowed && isUpdatingFeeds != update.feedIds.length > 0) {
getActivity().supportInvalidateOptionsMenu();
}
if (listAdapter == null) {
loadItems();
return;
requireActivity().invalidateOptionsMenu();
}
if (update.mediaIds.length > 0) {
for(long mediaId : update.mediaIds) {
for (long mediaId : update.mediaIds) {
int pos = FeedItemUtil.indexOfItemWithMediaId(episodes, mediaId);
if(pos >= 0) {
if (pos >= 0) {
listAdapter.notifyItemChanged(pos);
}
}
@ -486,35 +443,22 @@ public class AllEpisodesFragment extends Fragment {
if ((arg & EVENTS) != 0) {
loadItems();
if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) {
getActivity().supportInvalidateOptionsMenu();
requireActivity().invalidateOptionsMenu();
}
}
}
};
private void updateShowOnlyEpisodesListViewState() {
}
void loadItems() {
if (disposable != null) {
disposable.dispose();
}
if (viewsCreated && !itemsLoaded) {
recyclerView.setVisibility(View.GONE);
emptyView.hide();
progLoading.setVisibility(View.VISIBLE);
}
disposable = Observable.fromCallable(this::loadData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
recyclerView.setVisibility(View.VISIBLE);
progLoading.setVisibility(View.GONE);
episodes = data;
itemsLoaded = true;
if (viewsCreated) {
onFragmentLoaded();
}
onFragmentLoaded(data);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
@ -523,12 +467,12 @@ public class AllEpisodesFragment extends Fragment {
return DBReader.getRecentlyPublishedEpisodes(RECENT_EPISODES_LIMIT);
}
void markItemAsSeenWithUndo(FeedItem item) {
void removeNewFlagWithUndo(FeedItem item) {
if (item == null) {
return;
}
Log.d(TAG, "markItemAsSeenWithUndo(" + item.getId() + ")");
Log.d(TAG, "removeNewFlagWithUndo(" + item.getId() + ")");
if (disposable != null) {
disposable.dispose();
}
@ -537,14 +481,14 @@ public class AllEpisodesFragment extends Fragment {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
final Handler h = new Handler(getActivity().getMainLooper());
final Runnable r = () -> {
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.marked_as_seen_label),
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());
@ -552,7 +496,6 @@ public class AllEpisodesFragment extends Fragment {
h.removeCallbacks(r);
});
snackbar.show();
h.postDelayed(r, (int)Math.ceil(snackbar.getDuration() * 1.05f));
h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f));
}
}

View File

@ -15,6 +15,7 @@ import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.view.EmptyViewHandler;
import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -25,6 +26,7 @@ public class ChaptersFragment extends ListFragment {
private ChaptersListAdapter adapter;
private PlaybackController controller;
private Disposable disposable;
private EmptyViewHandler emptyView;
@Override
@ -36,6 +38,12 @@ public class ChaptersFragment extends ListFragment {
final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding);
lv.setPadding(0, vertPadding, 0, vertPadding);
emptyView = new EmptyViewHandler(getContext());
emptyView.attachToListView(lv);
emptyView.setIcon(R.attr.ic_bookmark);
emptyView.setTitle(R.string.no_chapters_head_label);
emptyView.setMessage(R.string.no_chapters_label);
adapter = new ChaptersListAdapter(getActivity(), 0, pos -> {
Chapter chapter = (Chapter) getListAdapter().getItem(pos);
controller.seekToChapter(chapter);
@ -118,10 +126,7 @@ public class ChaptersFragment extends ListFragment {
if (adapter != null) {
adapter.setMedia(media);
adapter.notifyDataSetChanged();
if (media == null || media.getChapters() == null || media.getChapters().size() == 0) {
setEmptyText(getString(R.string.no_chapters_label));
} else {
setEmptyText(null);
if (media != null && media.getChapters() != null && media.getChapters().size() != 0) {
scrollTo(getCurrentChapter(media));
}
}

View File

@ -0,0 +1,173 @@
package de.danoeh.antennapod.fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
import de.danoeh.antennapod.discovery.CombinedSearcher;
import de.danoeh.antennapod.discovery.PodcastSearchResult;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import io.reactivex.disposables.Disposable;
import java.util.ArrayList;
import java.util.List;
public class CombinedSearchFragment extends Fragment {
private static final String TAG = "CombinedSearchFragment";
public static final String ARGUMENT_QUERY = "query";
/**
* Adapter responsible with the search results
*/
private ItunesAdapter adapter;
private GridView gridView;
private ProgressBar progressBar;
private TextView txtvError;
private Button butRetry;
private TextView txtvEmpty;
/**
* List of podcasts retreived from the search
*/
private List<PodcastSearchResult> searchResults = new ArrayList<>();
private Disposable disposable;
/**
* Constructor
*/
public CombinedSearchFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_itunes_search, container, false);
gridView = root.findViewById(R.id.gridView);
adapter = new ItunesAdapter(getActivity(), new ArrayList<>());
gridView.setAdapter(adapter);
//Show information about the podcast when the list item is clicked
gridView.setOnItemClickListener((parent, view1, position, id) -> {
PodcastSearchResult podcast = searchResults.get(position);
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title);
startActivity(intent);
});
progressBar = root.findViewById(R.id.progressBar);
txtvError = root.findViewById(R.id.txtvError);
butRetry = root.findViewById(R.id.butRetry);
txtvEmpty = root.findViewById(android.R.id.empty);
return root;
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposable != null) {
disposable.dispose();
}
adapter = null;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.itunes_search, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
MenuItemUtils.adjustTextColor(getActivity(), sv);
sv.setQueryHint(getString(R.string.search_label));
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
sv.clearFocus();
search(s);
return true;
}
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
getActivity().getSupportFragmentManager().popBackStack();
return true;
}
});
MenuItemCompat.expandActionView(searchItem);
if (getArguments() != null && getArguments().getString(ARGUMENT_QUERY, null) != null) {
sv.setQuery(getArguments().getString(ARGUMENT_QUERY, null), true);
}
}
private void search(String query) {
if (disposable != null) {
disposable.dispose();
}
showOnlyProgressBar();
CombinedSearcher searcher = new CombinedSearcher(getContext());
disposable = searcher.search(query).subscribe(result -> {
searchResults = result;
progressBar.setVisibility(View.GONE);
adapter.clear();
adapter.addAll(searchResults);
adapter.notifyDataSetInvalidated();
gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE);
txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
progressBar.setVisibility(View.GONE);
txtvError.setText(error.toString());
txtvError.setVisibility(View.VISIBLE);
butRetry.setOnClickListener(v -> search(query));
butRetry.setVisibility(View.VISIBLE);
});
}
private void showOnlyProgressBar() {
gridView.setVisibility(View.GONE);
txtvError.setVisibility(View.GONE);
butRetry.setVisibility(View.GONE);
txtvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
}

View File

@ -1,7 +1,7 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.Menu;
@ -10,6 +10,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
@ -27,6 +28,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE;
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE;
/**
* Displays all running downloads and provides a button to delete them
*/
@ -38,24 +42,27 @@ public class CompletedDownloadsFragment extends ListFragment {
EventDistributor.DOWNLOADLOG_UPDATE |
EventDistributor.UNREAD_ITEMS_UPDATE;
private List<FeedItem> items;
private List<FeedItem> items = new ArrayList<>();
private DownloadedEpisodesListAdapter listAdapter;
private boolean viewCreated = false;
private Disposable disposable;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
loadItems();
addVerticalPadding();
addEmptyView();
listAdapter = new DownloadedEpisodesListAdapter(getActivity(), itemAccess);
setListAdapter(listAdapter);
setListShown(false);
}
@Override
public void onStart() {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
loadItems();
}
@Override
@ -68,104 +75,54 @@ public class CompletedDownloadsFragment extends ListFragment {
}
@Override
public void onDetach() {
super.onDetach();
if (disposable != null) {
disposable.dispose();
}
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
position -= l.getHeaderViewsCount();
long[] ids = FeedItemUtil.getIds(items);
((MainActivity) requireActivity()).loadChildFragment(ItemFragment.newInstance(ids, position));
}
@Override
public void onDestroyView() {
super.onDestroyView();
listAdapter = null;
viewCreated = false;
if (disposable != null) {
disposable.dispose();
}
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.downloads_completed, menu);
menu.findItem(R.id.episode_actions).setVisible(items.size() > 0);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (viewCreated && items != null) {
onFragmentLoaded();
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.episode_actions) {
((MainActivity) requireActivity())
.loadChildFragment(EpisodesApplyActionFragment.newInstance(items, ACTION_DELETE | ACTION_ADD_TO_QUEUE));
return true;
}
return false;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// add padding
final ListView lv = getListView();
lv.setClipToPadding(false);
final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding);
lv.setPadding(0, vertPadding, 0, vertPadding);
viewCreated = true;
if (items != null && getActivity() != null) {
onFragmentLoaded();
}
private void addEmptyView() {
EmptyViewHandler emptyView = new EmptyViewHandler(getActivity());
emptyView.setIcon(R.attr.av_download);
emptyView.setTitle(R.string.no_comp_downloads_head_label);
emptyView.setMessage(R.string.no_comp_downloads_label);
emptyView.attachToListView(getListView());
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
position -= l.getHeaderViewsCount();
long[] ids = FeedItemUtil.getIds(items);
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(ids, position));
}
private void onFragmentLoaded() {
if (listAdapter == null) {
listAdapter = new DownloadedEpisodesListAdapter(getActivity(), itemAccess);
setListAdapter(listAdapter);
}
setListShown(true);
listAdapter.notifyDataSetChanged();
getActivity().supportInvalidateOptionsMenu();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if(!isAdded()) {
return;
}
super.onCreateOptionsMenu(menu, inflater);
if(items != null) {
inflater.inflate(R.menu.downloads_completed, menu);
menu.findItem(R.id.episode_actions).setVisible(items.size() > 0);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.episode_actions:
EpisodesApplyActionFragment fragment = EpisodesApplyActionFragment
.newInstance(items, EpisodesApplyActionFragment.ACTION_DELETE | EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE);
((MainActivity) getActivity()).loadChildFragment(fragment);
return true;
default:
return false;
}
private void addVerticalPadding() {
final ListView lv = getListView();
lv.setClipToPadding(false);
final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding);
lv.setPadding(0, vertPadding, 0, vertPadding);
}
private final DownloadedEpisodesListAdapter.ItemAccess itemAccess = new DownloadedEpisodesListAdapter.ItemAccess() {
@Override
public int getCount() {
return (items != null) ? items.size() : 0;
return items.size();
}
@Override
public FeedItem getItem(int position) {
if (items != null && 0 <= position && position < items.size()) {
if (0 <= position && position < items.size()) {
return items.get(position);
} else {
return null;
@ -174,7 +131,7 @@ public class CompletedDownloadsFragment extends ListFragment {
@Override
public void onFeedItemSecondaryAction(FeedItem item) {
DBWriter.deleteFeedMediaOfItem(getActivity(), item.getMedia().getId());
DBWriter.deleteFeedMediaOfItem(requireActivity(), item.getMedia().getId());
}
};
@ -191,18 +148,18 @@ public class CompletedDownloadsFragment extends ListFragment {
if (disposable != null) {
disposable.dispose();
}
if (items == null && viewCreated) {
setListShown(false);
}
disposable = Observable.fromCallable(DBReader::getDownloadedItems)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
items = result;
if (viewCreated && getActivity() != null) {
onFragmentLoaded();
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
onItemsLoaded();
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
private void onItemsLoaded() {
setListShown(true);
listAdapter.notifyDataSetChanged();
requireActivity().invalidateOptionsMenu();
}
}

View File

@ -14,6 +14,7 @@ import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
@ -37,18 +38,13 @@ public class DownloadLogFragment extends ListFragment {
private static final String TAG = "DownloadLogFragment";
private List<DownloadStatus> downloadLog;
private List<DownloadStatus> downloadLog = new ArrayList<>();
private DownloadLogAdapter adapter;
private boolean viewsCreated = false;
private boolean itemsLoaded = false;
private Disposable disposable;
@Override
public void onStart() {
super.onStart();
setHasOptionsMenu(true);
EventDistributor.getInstance().register(contentUpdate);
loadItems();
}
@ -57,7 +53,7 @@ public class DownloadLogFragment extends ListFragment {
public void onStop() {
super.onStop();
EventDistributor.getInstance().unregister(contentUpdate);
if(disposable != null) {
if (disposable != null) {
disposable.dispose();
}
}
@ -65,6 +61,7 @@ public class DownloadLogFragment extends ListFragment {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
// add padding
final ListView lv = getListView();
@ -72,23 +69,17 @@ public class DownloadLogFragment extends ListFragment {
final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding);
lv.setPadding(0, vertPadding, 0, vertPadding);
viewsCreated = true;
if (itemsLoaded) {
onFragmentLoaded();
}
EmptyViewHandler emptyView = new EmptyViewHandler(getActivity());
emptyView.setIcon(R.attr.av_download);
emptyView.setTitle(R.string.no_log_downloads_head_label);
emptyView.setMessage(R.string.no_log_downloads_label);
emptyView.attachToListView(getListView());
adapter = new DownloadLogAdapter(getActivity(), itemAccess);
setListAdapter(adapter);
}
private void onFragmentLoaded() {
if (adapter == null) {
adapter = new DownloadLogAdapter(getActivity(), itemAccess);
setListAdapter(adapter);
}
setListShown(true);
adapter.notifyDataSetChanged();
getActivity().supportInvalidateOptionsMenu();
@ -129,12 +120,12 @@ public class DownloadLogFragment extends ListFragment {
@Override
public int getCount() {
return (downloadLog != null) ? downloadLog.size() : 0;
return downloadLog.size();
}
@Override
public DownloadStatus getItem(int position) {
if (downloadLog != null && 0 <= position && position < downloadLog.size()) {
if (0 <= position && position < downloadLog.size()) {
return downloadLog.get(position);
} else {
return null;
@ -154,27 +145,23 @@ public class DownloadLogFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if(!isAdded()) {
if (!isAdded()) {
return;
}
super.onCreateOptionsMenu(menu, inflater);
if (itemsLoaded) {
MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label);
MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard});
clearHistory.setIcon(drawables.getDrawable(0));
drawables.recycle();
}
MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label);
MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard});
clearHistory.setIcon(drawables.getDrawable(0));
drawables.recycle();
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if (itemsLoaded) {
MenuItem menuItem = menu.findItem(R.id.clear_history_item);
if(menuItem != null) {
menuItem.setVisible(downloadLog != null && !downloadLog.isEmpty());
}
MenuItem menuItem = menu.findItem(R.id.clear_history_item);
if (menuItem != null) {
menuItem.setVisible(!downloadLog.isEmpty());
}
}
@ -194,7 +181,7 @@ public class DownloadLogFragment extends ListFragment {
}
private void loadItems() {
if(disposable != null) {
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(DBReader::getDownloadLog)
@ -203,12 +190,8 @@ public class DownloadLogFragment extends ListFragment {
.subscribe(result -> {
if (result != null) {
downloadLog = result;
itemsLoaded = true;
if (viewsCreated) {
onFragmentLoaded();
}
onFragmentLoaded();
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
}

View File

@ -1,6 +1,7 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
@ -8,7 +9,8 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.greenrobot.eventbus.Subscribe;
import java.util.List;
@ -18,42 +20,37 @@ import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
/**
* Like 'EpisodesFragment' except that it only shows favorite episodes and
* supports swiping to remove from favorites.
*/
public class FavoriteEpisodesFragment extends AllEpisodesFragment {
private static final String TAG = "FavoriteEpisodesFrag";
private static final String PREF_NAME = "PrefFavoriteEpisodesFragment";
@Override
protected boolean showOnlyNewEpisodes() { return true; }
protected boolean showOnlyNewEpisodes() {
return true;
}
@Override
protected String getPrefName() { return PREF_NAME; }
protected String getPrefName() {
return PREF_NAME;
}
@Subscribe
public void onEvent(FavoritesEvent event) {
Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]");
Log.d(TAG, String.format("onEvent() called with: event = [%s]", event));
loadItems();
}
@NonNull
@Override
protected void resetViewState() {
super.resetViewState();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = super.onCreateViewHelper(inflater, container, savedInstanceState,
R.layout.all_episodes_fragment);
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, container, savedInstanceState);
emptyView.setIcon(R.attr.ic_unfav);
emptyView.setTitle(R.string.no_fav_episodes_head_label);
emptyView.setMessage(R.string.no_fav_episodes_label);
@ -65,8 +62,8 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment {
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder)viewHolder;
Log.d(TAG, "remove(" + holder.getItemId() + ")");
AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder;
Log.d(TAG, String.format("remove(%s)", holder.getItemId()));
if (disposable != null) {
disposable.dispose();
@ -75,8 +72,7 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment {
if (item != null) {
DBWriter.removeFavoriteItem(item);
Snackbar snackbar = Snackbar.make(root, getString(R.string.removed_item),
Snackbar.LENGTH_LONG);
Snackbar snackbar = Snackbar.make(root, getString(R.string.removed_item), Snackbar.LENGTH_LONG);
snackbar.setAction(getString(R.string.undo), v -> DBWriter.addFavoriteItem(item));
snackbar.show();
}
@ -88,6 +84,7 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment {
return root;
}
@NonNull
@Override
protected List<FeedItem> loadData() {
return DBReader.getFavoriteItemsList();

View File

@ -16,24 +16,16 @@ import android.widget.Button;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.discovery.FyydPodcastSearcher;
import de.danoeh.antennapod.discovery.PodcastSearchResult;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.mfietz.fyydlin.FyydClient;
import de.mfietz.fyydlin.FyydResponse;
import de.mfietz.fyydlin.SearchHit;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast;
import static java.util.Collections.emptyList;
import java.util.ArrayList;
import java.util.List;
public class FyydSearchFragment extends Fragment {
@ -49,12 +41,10 @@ public class FyydSearchFragment extends Fragment {
private Button butRetry;
private TextView txtvEmpty;
private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient());
/**
* List of podcasts retreived from the search
*/
private List<Podcast> searchResults;
private List<PodcastSearchResult> searchResults;
private Disposable disposable;
/**
@ -81,7 +71,7 @@ public class FyydSearchFragment extends Fragment {
//Show information about the podcast when the list item is clicked
gridView.setOnItemClickListener((parent, view1, position, id) -> {
Podcast podcast = searchResults.get(position);
PodcastSearchResult podcast = searchResults.get(position);
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title);
@ -145,20 +135,26 @@ public class FyydSearchFragment extends Fragment {
disposable.dispose();
}
showOnlyProgressBar();
disposable = client.searchPodcasts(query, 10)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
progressBar.setVisibility(View.GONE);
processSearchResult(result);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
progressBar.setVisibility(View.GONE);
txtvError.setText(error.toString());
txtvError.setVisibility(View.VISIBLE);
butRetry.setOnClickListener(v -> search(query));
butRetry.setVisibility(View.VISIBLE);
});
FyydPodcastSearcher searcher = new FyydPodcastSearcher();
disposable = searcher.search(query).subscribe(result -> {
searchResults = result;
progressBar.setVisibility(View.GONE);
adapter.clear();
adapter.addAll(searchResults);
adapter.notifyDataSetInvalidated();
gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE);
txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
progressBar.setVisibility(View.GONE);
txtvError.setText(error.toString());
txtvError.setVisibility(View.VISIBLE);
butRetry.setOnClickListener(v -> search(query));
butRetry.setVisibility(View.VISIBLE);
});
}
private void showOnlyProgressBar() {
@ -168,25 +164,4 @@ public class FyydSearchFragment extends Fragment {
txtvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
private void processSearchResult(FyydResponse response) {
adapter.clear();
if (!response.getData().isEmpty()) {
adapter.clear();
searchResults = new ArrayList<>();
for (SearchHit searchHit : response.getData()) {
Podcast podcast = Podcast.fromSearch(searchHit);
searchResults.add(podcast);
}
} else {
searchResults = emptyList();
}
for(Podcast podcast : searchResults) {
adapter.add(podcast);
}
adapter.notifyDataSetInvalidated();
gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE);
txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE);
}
}

View File

@ -36,6 +36,9 @@ import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.widget.IconButton;
import org.apache.commons.lang3.ArrayUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
@ -71,9 +74,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;
/**
* Displays information about a FeedItem and actions.
@ -266,7 +266,6 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
load();
}
@Override
@ -274,6 +273,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
EventBus.getDefault().register(this);
load();
}
@Override
@ -306,19 +306,20 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
@Override
public boolean onSwipeLeftToRight() {
Log.d(TAG, "onSwipeLeftToRight()");
feedItemPos = feedItemPos - 1;
if(feedItemPos < 0) {
feedItemPos = feedItems.length - 1;
}
load();
return true;
return swipeFeedItem(-1);
}
@Override
public boolean onSwipeRightToLeft() {
Log.d(TAG, "onSwipeRightToLeft()");
feedItemPos = (feedItemPos + 1) % feedItems.length;
return swipeFeedItem(+1);
}
private boolean swipeFeedItem(int position) {
Log.d(TAG, String.format("onSwipe() shift: %s", position));
feedItemPos = (feedItemPos + position) % feedItems.length;
if (feedItemPos < 0) {
feedItemPos = feedItems.length - 1;
}
load();
return true;
}
@ -602,8 +603,9 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
@Nullable
private FeedItem loadInBackground() {
FeedItem feedItem = DBReader.getFeedItem(feedItems[feedItemPos]);
if (feedItem != null) {
Timeline t = new Timeline(getContext(), feedItem);
Context context = getContext();
if (feedItem != null && context != null) {
Timeline t = new Timeline(context, feedItem);
webviewData = t.processShownotes(false);
}
return feedItem;

View File

@ -30,6 +30,9 @@ import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.widget.IconTextView;
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;
@ -71,9 +74,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;
/**
* Displays a list of FeedItems.
@ -96,8 +96,6 @@ public class ItemlistFragment extends ListFragment {
private long feedID;
private Feed feed;
private boolean itemsLoaded = false;
private boolean viewsCreated = false;
private boolean headerCreated = false;
private List<Downloader> downloaderList;
@ -105,7 +103,7 @@ public class ItemlistFragment extends ListFragment {
private MoreContentListFooterUtil listFooter;
private boolean isUpdatingFeed;
private TextView txtvTitle;
private IconTextView txtvFailure;
private ImageView imgvBackground;
@ -146,9 +144,7 @@ public class ItemlistFragment extends ListFragment {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
EventBus.getDefault().register(this);
if (viewsCreated && itemsLoaded) {
onFragmentLoaded();
}
loadItems();
}
@Override
@ -156,7 +152,6 @@ public class ItemlistFragment extends ListFragment {
super.onResume();
((MainActivity)getActivity()).getSupportActionBar().setTitle("");
updateProgressBarVisibility();
loadItems();
}
@Override
@ -177,7 +172,6 @@ public class ItemlistFragment extends ListFragment {
private void resetViewState() {
adapter = null;
viewsCreated = false;
listFooter = null;
}
@ -190,45 +184,43 @@ public class ItemlistFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if(!isAdded()) {
if (!isAdded()) {
return;
}
super.onCreateOptionsMenu(menu, inflater);
if (itemsLoaded) {
FeedMenuHandler.onCreateOptionsMenu(inflater, menu);
FeedMenuHandler.onCreateOptionsMenu(inflater, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
MenuItemUtils.adjustTextColor(getActivity(), sv);
sv.setQueryHint(getString(R.string.search_hint));
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
sv.clearFocus();
if (itemsLoaded) {
((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s, feed.getId()));
}
return true;
MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
MenuItemUtils.adjustTextColor(getActivity(), sv);
sv.setQueryHint(getString(R.string.search_hint));
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
sv.clearFocus();
if (feed != null) {
((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s, feed.getId()));
}
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
if(feed == null || feed.getLink() == null) {
menu.findItem(R.id.share_link_item).setVisible(false);
menu.findItem(R.id.visit_website_item).setVisible(false);
return true;
}
isUpdatingFeed = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
if (feed == null || feed.getLink() == null) {
menu.findItem(R.id.share_link_item).setVisible(false);
menu.findItem(R.id.visit_website_item).setVisible(false);
}
isUpdatingFeed = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
if (itemsLoaded) {
if (feed != null) {
FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
}
}
@ -341,11 +333,6 @@ public class ItemlistFragment extends ListFragment {
super.onViewCreated(view, savedInstanceState);
registerForContextMenu(getListView());
viewsCreated = true;
if (itemsLoaded) {
onFragmentLoaded();
}
}
@Override
@ -503,7 +490,7 @@ public class ItemlistFragment extends ListFragment {
butShowInfo.setOnClickListener(v -> showFeedInfo());
imgvCover.setOnClickListener(v -> showFeedInfo());
butShowSettings.setOnClickListener(v -> {
if (viewsCreated && itemsLoaded) {
if (feed != null) {
Intent startIntent = new Intent(getActivity(), FeedSettingsActivity.class);
startIntent.putExtra(FeedSettingsActivity.EXTRA_FEED_ID,
feed.getId());
@ -514,7 +501,7 @@ public class ItemlistFragment extends ListFragment {
}
private void showFeedInfo() {
if (viewsCreated && itemsLoaded) {
if (feed != null) {
Intent startIntent = new Intent(getActivity(), FeedInfoActivity.class);
startIntent.putExtra(FeedInfoActivity.EXTRA_FEED_ID,
feed.getId());
@ -624,10 +611,7 @@ public class ItemlistFragment extends ListFragment {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
feed = result.orElse(null);
itemsLoaded = true;
if (viewsCreated) {
onFragmentLoaded();
}
onFragmentLoaded();
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}

View File

@ -20,13 +20,14 @@ import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import de.danoeh.antennapod.discovery.ItunesPodcastSearcher;
import de.danoeh.antennapod.discovery.ItunesTopListLoader;
import de.danoeh.antennapod.discovery.PodcastSearchResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -46,15 +47,11 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast;
//Searches iTunes store for given string and displays results in a list
public class ItunesSearchFragment extends Fragment {
private static final String TAG = "ItunesSearchFragment";
private static final String API_URL = "https://itunes.apple.com/search?media=podcast&term=%s";
/**
* Adapter responsible with the search results
@ -69,21 +66,21 @@ public class ItunesSearchFragment extends Fragment {
/**
* List of podcasts retreived from the search
*/
private List<Podcast> searchResults;
private List<Podcast> topList;
private List<PodcastSearchResult> searchResults;
private List<PodcastSearchResult> topList;
private Disposable disposable;
/**
* Replace adapter data with provided search results from SearchTask.
* @param result List of Podcast objects containing search results
*/
private void updateData(List<Podcast> result) {
private void updateData(List<PodcastSearchResult> result) {
this.searchResults = result;
adapter.clear();
if (result != null && result.size() > 0) {
gridView.setVisibility(View.VISIBLE);
txtvEmpty.setVisibility(View.GONE);
for (Podcast p : result) {
for (PodcastSearchResult p : result) {
adapter.add(p);
}
adapter.notifyDataSetInvalidated();
@ -117,61 +114,31 @@ public class ItunesSearchFragment extends Fragment {
//Show information about the podcast when the list item is clicked
gridView.setOnItemClickListener((parent, view1, position, id) -> {
Podcast podcast = searchResults.get(position);
if(podcast.feedUrl == null) {
PodcastSearchResult podcast = searchResults.get(position);
if (podcast.feedUrl == null) {
return;
}
if (!podcast.feedUrl.contains("itunes.apple.com")) {
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, "iTunes");
startActivity(intent);
} else {
gridView.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
disposable = Single.create((SingleOnSubscribe<String>) emitter -> {
OkHttpClient client = AntennapodHttpClient.getHttpClient();
Request.Builder httpReq = new Request.Builder()
.url(podcast.feedUrl)
.header("User-Agent", ClientConfig.USER_AGENT);
try {
Response response = client.newCall(httpReq.build()).execute();
if (response.isSuccessful()) {
String resultString = response.body().string();
JSONObject result = new JSONObject(resultString);
JSONObject results = result.getJSONArray("results").getJSONObject(0);
String feedUrl = results.getString("feedUrl");
emitter.onSuccess(feedUrl);
} else {
String prefix = getString(R.string.error_msg_prefix);
emitter.onError(new IOException(prefix + response));
}
} catch (IOException | JSONException e) {
if (!disposable.isDisposed()) {
emitter.onError(e);
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(feedUrl -> {
progressBar.setVisibility(View.GONE);
gridView.setVisibility(View.VISIBLE);
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, feedUrl);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, "iTunes");
startActivity(intent);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
progressBar.setVisibility(View.GONE);
gridView.setVisibility(View.VISIBLE);
String prefix = getString(R.string.error_msg_prefix);
new MaterialDialog.Builder(getActivity())
.content(prefix + " " + error.getMessage())
.neutralText(android.R.string.ok)
.show();
});
}
gridView.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
disposable = loader.getFeedUrl(podcast)
.subscribe(feedUrl -> {
progressBar.setVisibility(View.GONE);
gridView.setVisibility(View.VISIBLE);
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, feedUrl);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, "iTunes");
startActivity(intent);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
progressBar.setVisibility(View.GONE);
gridView.setVisibility(View.VISIBLE);
String prefix = getString(R.string.error_msg_prefix);
new MaterialDialog.Builder(getActivity())
.content(prefix + " " + error.getMessage())
.neutralText(android.R.string.ok)
.show();
});
});
progressBar = root.findViewById(R.id.progressBar);
txtvError = root.findViewById(R.id.txtvError);
@ -239,26 +206,9 @@ public class ItunesSearchFragment extends Fragment {
butRetry.setVisibility(View.GONE);
txtvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
disposable = Single.create((SingleOnSubscribe<List<Podcast>>) emitter -> {
String lang = Locale.getDefault().getLanguage();
OkHttpClient client = AntennapodHttpClient.getHttpClient();
String feedString;
try {
try {
feedString = getTopListFeed(client, lang);
} catch (IOException e) {
feedString = getTopListFeed(client, "us");
}
List<Podcast> podcasts = parseFeed(feedString);
emitter.onSuccess(podcasts);
} catch (IOException | JSONException e) {
if (!disposable.isDisposed()) {
emitter.onError(e);
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
disposable = loader.loadToplist(25)
.subscribe(podcasts -> {
progressBar.setVisibility(View.GONE);
topList = podcasts;
@ -273,35 +223,6 @@ public class ItunesSearchFragment extends Fragment {
});
}
private String getTopListFeed(OkHttpClient client, String language) throws IOException {
String url = "https://itunes.apple.com/%s/rss/toppodcasts/limit=25/explicit=true/json";
Request.Builder httpReq = new Request.Builder()
.header("User-Agent", ClientConfig.USER_AGENT)
.url(String.format(url, language));
try (Response response = client.newCall(httpReq.build()).execute()) {
if (response.isSuccessful()) {
return response.body().string();
}
String prefix = getString(R.string.error_msg_prefix);
throw new IOException(prefix + response);
}
}
private List<Podcast> parseFeed(String jsonString) throws JSONException {
JSONObject result = new JSONObject(jsonString);
JSONObject feed = result.getJSONObject("feed");
JSONArray entries = feed.getJSONArray("entry");
List<Podcast> results = new ArrayList<>();
for (int i=0; i < entries.length(); i++) {
JSONObject json = entries.getJSONObject(i);
results.add(Podcast.fromToplist(json));
}
return results;
}
private void search(String query) {
if (disposable != null) {
disposable.dispose();
@ -311,60 +232,19 @@ public class ItunesSearchFragment extends Fragment {
butRetry.setVisibility(View.GONE);
txtvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
disposable = Single.create((SingleOnSubscribe<List<Podcast>>) subscriber -> {
String encodedQuery = null;
try {
encodedQuery = URLEncoder.encode(query, "UTF-8");
} catch (UnsupportedEncodingException e) {
// this won't ever be thrown
}
if (encodedQuery == null) {
encodedQuery = query; // failsafe
}
String formattedUrl = String.format(API_URL, encodedQuery);
OkHttpClient client = AntennapodHttpClient.getHttpClient();
Request.Builder httpReq = new Request.Builder()
.url(formattedUrl)
.header("User-Agent", ClientConfig.USER_AGENT);
List<Podcast> podcasts = new ArrayList<>();
try {
Response response = client.newCall(httpReq.build()).execute();
if(response.isSuccessful()) {
String resultString = response.body().string();
JSONObject result = new JSONObject(resultString);
JSONArray j = result.getJSONArray("results");
for (int i = 0; i < j.length(); i++) {
JSONObject podcastJson = j.getJSONObject(i);
Podcast podcast = Podcast.fromSearch(podcastJson);
podcasts.add(podcast);
}
}
else {
String prefix = getString(R.string.error_msg_prefix);
subscriber.onError(new IOException(prefix + response));
}
} catch (IOException | JSONException e) {
subscriber.onError(e);
}
subscriber.onSuccess(podcasts);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(podcasts -> {
progressBar.setVisibility(View.GONE);
updateData(podcasts);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
progressBar.setVisibility(View.GONE);
txtvError.setText(error.toString());
txtvError.setVisibility(View.VISIBLE);
butRetry.setOnClickListener(v -> search(query));
butRetry.setVisibility(View.VISIBLE);
});
ItunesPodcastSearcher searcher = new ItunesPodcastSearcher(getContext());
disposable = searcher.search(query).subscribe(podcasts -> {
progressBar.setVisibility(View.GONE);
updateData(podcasts);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
progressBar.setVisibility(View.GONE);
txtvError.setText(error.toString());
txtvError.setVisibility(View.VISIBLE);
butRetry.setOnClickListener(v -> search(query));
butRetry.setVisibility(View.VISIBLE);
});
}
}

View File

@ -1,40 +1,37 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.FeedItemUtil;
/**
* Like 'EpisodesFragment' except that it only shows new episodes and
* supports swiping to mark as read.
*/
public class NewEpisodesFragment extends AllEpisodesFragment {
public static final String TAG = "NewEpisodesFragment";
private static final String PREF_NAME = "PrefNewEpisodesFragment";
@Override
protected boolean showOnlyNewEpisodes() { return true; }
@Override
protected String getPrefName() { return PREF_NAME; }
protected boolean showOnlyNewEpisodes() {
return true;
}
@Override
protected void resetViewState() {
super.resetViewState();
protected String getPrefName() {
return PREF_NAME;
}
@Override
@ -42,10 +39,10 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
return item.isNew();
}
@NonNull
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = super.onCreateViewHelper(inflater, container, savedInstanceState,
R.layout.all_episodes_fragment);
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, container, savedInstanceState);
emptyView.setTitle(R.string.no_new_episodes_head_label);
emptyView.setMessage(R.string.no_new_episodes_label);
@ -57,8 +54,8 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder)viewHolder;
markItemAsSeenWithUndo(holder.getFeedItem());
AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder;
removeNewFlagWithUndo(holder.getFeedItem());
}
@Override
@ -75,6 +72,7 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder) {
@ -94,9 +92,9 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
return root;
}
@NonNull
@Override
protected List<FeedItem> loadData() {
return DBReader.getNewItemsList();
}
}

View File

@ -1,6 +1,5 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.annotation.NonNull;
@ -13,6 +12,10 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
import de.danoeh.antennapod.R;
@ -34,9 +37,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;
public class PlaybackHistoryFragment extends ListFragment {
@ -47,22 +47,9 @@ public class PlaybackHistoryFragment extends ListFragment {
private List<FeedItem> playbackHistory;
private FeedItemlistAdapter adapter;
private boolean itemsLoaded = false;
private boolean viewsCreated = false;
private List<Downloader> downloaderList;
private Disposable disposable;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (viewsCreated && itemsLoaded) {
onFragmentLoaded();
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -80,16 +67,17 @@ public class PlaybackHistoryFragment extends ListFragment {
final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding);
lv.setPadding(0, vertPadding, 0, vertPadding);
viewsCreated = true;
if (itemsLoaded) {
onFragmentLoaded();
}
EmptyViewHandler emptyView = new EmptyViewHandler(getActivity());
emptyView.setIcon(R.attr.ic_history);
emptyView.setTitle(R.string.no_history_head_label);
emptyView.setMessage(R.string.no_history_label);
emptyView.attachToListView(getListView());
// played items shoudln't be transparent for this fragment since, *all* items
// in this fragment will, by definition, be played. So it serves no purpose and can make
// it harder to read.
adapter = new FeedItemlistAdapter(getActivity(), itemAccess, true, false);
setListAdapter(adapter);
}
@Override
@ -105,34 +93,17 @@ public class PlaybackHistoryFragment extends ListFragment {
super.onStop();
EventBus.getDefault().unregister(this);
EventDistributor.getInstance().unregister(contentUpdate);
if(disposable != null) {
if (disposable != null) {
disposable.dispose();
}
}
@Override
public void onDetach() {
super.onDetach();
if(disposable != null) {
disposable.dispose();
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
adapter = null;
viewsCreated = false;
}
@Subscribe(sticky = true)
public void onEvent(DownloadEvent event) {
Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]");
DownloaderUpdate update = event.update;
downloaderList = update.downloaders;
if (adapter != null) {
adapter.notifyDataSetChanged();
}
adapter.notifyDataSetChanged();
}
@Override
@ -145,27 +116,23 @@ public class PlaybackHistoryFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if(!isAdded()) {
if (!isAdded()) {
return;
}
super.onCreateOptionsMenu(menu, inflater);
if (itemsLoaded) {
MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label);
MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard});
clearHistory.setIcon(drawables.getDrawable(0));
drawables.recycle();
}
MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label);
MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard});
clearHistory.setIcon(drawables.getDrawable(0));
drawables.recycle();
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if (itemsLoaded) {
MenuItem menuItem = menu.findItem(R.id.clear_history_item);
if (menuItem != null) {
menuItem.setVisible(playbackHistory != null && !playbackHistory.isEmpty());
}
MenuItem menuItem = menu.findItem(R.id.clear_history_item);
if (menuItem != null) {
menuItem.setVisible(playbackHistory != null && !playbackHistory.isEmpty());
}
}
@ -211,14 +178,6 @@ public class PlaybackHistoryFragment extends ListFragment {
};
private void onFragmentLoaded() {
if (adapter == null) {
// played items shoudln't be transparent for this fragment since, *all* items
// in this fragment will, by definition, be played. So it serves no purpose and can make
// it harder to read.
adapter = new FeedItemlistAdapter(getActivity(), itemAccess, true, false);
setListAdapter(adapter);
}
setListShown(true);
adapter.notifyDataSetChanged();
getActivity().supportInvalidateOptionsMenu();
}
@ -277,10 +236,7 @@ public class PlaybackHistoryFragment extends ListFragment {
.subscribe(result -> {
if (result != null) {
playbackHistory = result;
itemsLoaded = true;
if (viewsCreated) {
onFragmentLoaded();
}
onFragmentLoaded();
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
@ -291,5 +247,4 @@ public class PlaybackHistoryFragment extends ListFragment {
DBReader.loadAdditionalFeedItemListData(history);
return history;
}
}

View File

@ -49,6 +49,7 @@ import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
@ -61,6 +62,9 @@ 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;
/**
* Shows all items in the queue
*/
@ -323,6 +327,10 @@ public class QueueFragment extends Fragment {
};
conDialog.createNewDialog().show();
return true;
case R.id.episode_actions:
((MainActivity) requireActivity()) .loadChildFragment(
EpisodesApplyActionFragment.newInstance(queue, ACTION_DELETE | ACTION_REMOVE_FROM_QUEUE));
return true;
case R.id.queue_sort_episode_title_asc:
QueueSorter.sort(QueueSorter.Rule.EPISODE_TITLE_ASC, true);
return true;
@ -518,6 +526,7 @@ public class QueueFragment extends Fragment {
emptyView = new EmptyViewHandler(getContext());
emptyView.attachToRecyclerView(recyclerView);
emptyView.setIcon(R.attr.stat_playlist);
emptyView.setTitle(R.string.no_items_header_label);
emptyView.setMessage(R.string.no_items_label);

View File

@ -0,0 +1,119 @@
package de.danoeh.antennapod.fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.FeedDiscoverAdapter;
import de.danoeh.antennapod.discovery.ItunesTopListLoader;
import de.danoeh.antennapod.discovery.PodcastSearchResult;
import io.reactivex.disposables.Disposable;
import java.util.ArrayList;
import java.util.List;
public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.OnItemClickListener {
private static final String TAG = "FeedDiscoveryFragment";
private ProgressBar progressBar;
private Disposable disposable;
private FeedDiscoverAdapter adapter;
private GridView subscriptionGridLayout;
private TextView errorTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View root = inflater.inflate(R.layout.quick_feed_discovery, container, false);
View discoverMore = root.findViewById(R.id.discover_more);
discoverMore.setOnClickListener(v ->
((MainActivity) getActivity()).loadChildFragment(new ItunesSearchFragment()));
subscriptionGridLayout = root.findViewById(R.id.discover_grid);
progressBar = root.findViewById(R.id.discover_progress_bar);
errorTextView = root.findViewById(R.id.discover_error);
adapter = new FeedDiscoverAdapter((MainActivity) getActivity());
subscriptionGridLayout.setAdapter(adapter);
subscriptionGridLayout.setOnItemClickListener(this);
// Fill with dummy elements to have a fixed height and
// prevent the UI elements below from jumping on slow connections
List<PodcastSearchResult> dummies = new ArrayList<>();
for (int i = 0; i < 8; i++) {
dummies.add(PodcastSearchResult.dummy());
}
adapter.updateData(dummies);
loadToplist();
return root;
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposable != null) {
disposable.dispose();
}
}
private void loadToplist() {
progressBar.setVisibility(View.VISIBLE);
subscriptionGridLayout.setVisibility(View.INVISIBLE);
errorTextView.setVisibility(View.GONE);
ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
disposable = loader.loadToplist(8)
.subscribe(podcasts -> {
errorTextView.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
subscriptionGridLayout.setVisibility(View.VISIBLE);
adapter.updateData(podcasts);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
errorTextView.setText(error.getLocalizedMessage());
errorTextView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
subscriptionGridLayout.setVisibility(View.INVISIBLE);
});
}
@Override
public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
PodcastSearchResult podcast = adapter.getItem(position);
if (podcast.feedUrl == null) {
return;
}
view.setAlpha(0.5f);
ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
disposable = loader.getFeedUrl(podcast)
.subscribe(feedUrl -> {
view.setAlpha(1f);
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, feedUrl);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label));
startActivity(intent);
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
view.setAlpha(1f);
String prefix = getString(R.string.error_msg_prefix);
new MaterialDialog.Builder(getActivity())
.content(prefix + " " + error.getMessage())
.neutralText(android.R.string.ok)
.show();
});
}
}

View File

@ -7,6 +7,10 @@ import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
@ -21,8 +25,6 @@ import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.view.EmptyViewHandler;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
/**
* Displays all running downloads and provides actions to cancel them
@ -32,7 +34,7 @@ public class RunningDownloadsFragment extends ListFragment {
private static final String TAG = "RunningDownloadsFrag";
private DownloadlistAdapter adapter;
private List<Downloader> downloaderList;
private List<Downloader> downloaderList = new ArrayList<>();
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
@ -48,6 +50,7 @@ public class RunningDownloadsFragment extends ListFragment {
setListAdapter(adapter);
EmptyViewHandler emptyView = new EmptyViewHandler(getActivity());
emptyView.setIcon(R.attr.av_download);
emptyView.setTitle(R.string.no_run_downloads_head_label);
emptyView.setMessage(R.string.no_run_downloads_label);
emptyView.attachToListView(getListView());
@ -70,7 +73,6 @@ public class RunningDownloadsFragment extends ListFragment {
public void onDestroy() {
super.onDestroy();
setListAdapter(null);
adapter = null;
}
@Subscribe(sticky = true)
@ -78,21 +80,18 @@ public class RunningDownloadsFragment extends ListFragment {
Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]");
DownloaderUpdate update = event.update;
downloaderList = update.downloaders;
if (adapter != null) {
adapter.notifyDataSetChanged();
}
adapter.notifyDataSetChanged();
}
private final DownloadlistAdapter.ItemAccess itemAccess = new DownloadlistAdapter.ItemAccess() {
@Override
public int getCount() {
return (downloaderList != null) ? downloaderList.size() : 0;
return downloaderList.size();
}
@Override
public Downloader getItem(int position) {
if (downloaderList != null && 0 <= position && position < downloaderList.size()) {
if (0 <= position && position < downloaderList.size()) {
return downloaderList.get(position);
} else {
return null;

View File

@ -14,6 +14,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
@ -40,11 +41,7 @@ public class SearchFragment extends ListFragment {
private static final String ARG_FEED = "feed";
private SearchlistAdapter searchAdapter;
private List<SearchResult> searchResults;
private boolean viewCreated = false;
private boolean itemsLoaded = false;
private List<SearchResult> searchResults = new ArrayList<>();
private Disposable disposable;
/**
@ -74,13 +71,13 @@ public class SearchFragment extends ListFragment {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
search();
}
@Override
public void onStart() {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
search();
}
@Override
@ -92,21 +89,6 @@ public class SearchFragment extends ListFragment {
EventDistributor.getInstance().unregister(contentUpdate);
}
@Override
public void onDetach() {
super.onDetach();
if(disposable != null) {
disposable.dispose();
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
searchAdapter = null;
viewCreated = false;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@ -118,10 +100,9 @@ public class SearchFragment extends ListFragment {
lv.setPadding(0, vertPadding, 0, vertPadding);
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.search_label);
viewCreated = true;
if (itemsLoaded) {
onFragmentLoaded();
}
searchAdapter = new SearchlistAdapter(getActivity(), itemAccess);
setListAdapter(searchAdapter);
}
@Override
@ -142,28 +123,26 @@ public class SearchFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (itemsLoaded) {
MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label);
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
final SearchView sv = new SearchView(getActivity());
sv.setQueryHint(getString(R.string.search_hint));
sv.setQuery(getArguments().getString(ARG_QUERY), false);
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
getArguments().putString(ARG_QUERY, s);
itemsLoaded = false;
search();
return true;
}
MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label);
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
final SearchView sv = new SearchView(getActivity());
sv.setQueryHint(getString(R.string.search_hint));
sv.setQuery(getArguments().getString(ARG_QUERY), false);
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
sv.clearFocus();
getArguments().putString(ARG_QUERY, s);
search();
return true;
}
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
MenuItemCompat.setActionView(item, sv);
}
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
MenuItemCompat.setActionView(item, sv);
}
private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@ -176,14 +155,9 @@ public class SearchFragment extends ListFragment {
}
};
private void onFragmentLoaded() {
if (searchAdapter == null) {
searchAdapter = new SearchlistAdapter(getActivity(), itemAccess);
setListAdapter(searchAdapter);
}
private void onSearchResults(List<SearchResult> results) {
searchResults = results;
searchAdapter.notifyDataSetChanged();
setListShown(true);
String query = getArguments().getString(ARG_QUERY);
setEmptyText(getString(R.string.no_results_for_query, query));
}
@ -191,12 +165,12 @@ public class SearchFragment extends ListFragment {
private final SearchlistAdapter.ItemAccess itemAccess = new SearchlistAdapter.ItemAccess() {
@Override
public int getCount() {
return (searchResults != null) ? searchResults.size() : 0;
return searchResults.size();
}
@Override
public SearchResult getItem(int position) {
if (searchResults != null && 0 <= position && position < searchResults.size()) {
if (0 <= position && position < searchResults.size()) {
return searchResults.get(position);
} else {
return null;
@ -204,24 +178,14 @@ public class SearchFragment extends ListFragment {
}
};
private void search() {
if(disposable != null) {
disposable.dispose();
}
if (viewCreated && !itemsLoaded) {
setListShown(false);
}
disposable = Observable.fromCallable(this::performSearch)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
itemsLoaded = true;
searchResults = result;
if (viewCreated) {
onFragmentLoaded();
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
.subscribe(this::onSearchResults, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
@NonNull
@ -232,5 +196,4 @@ public class SearchFragment extends ListFragment {
Context context = getActivity();
return FeedSearcher.performSearch(context, query, feed);
}
}

View File

@ -1,9 +1,11 @@
package de.danoeh.antennapod.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.ContextMenu;
@ -16,6 +18,8 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.GridView;
import java.util.concurrent.Callable;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.SubscriptionsAdapter;
@ -56,16 +60,13 @@ public class SubscriptionFragment extends Fragment {
private Disposable disposable;
private SharedPreferences prefs;
public SubscriptionFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
prefs = requireActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
}
@Override
@ -123,23 +124,25 @@ public class SubscriptionFragment extends Fragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
subscriptionAdapter = new SubscriptionsAdapter((MainActivity)getActivity(), itemAccess);
subscriptionGridLayout.setAdapter(subscriptionAdapter);
loadSubscriptions();
subscriptionGridLayout.setOnItemClickListener(subscriptionAdapter);
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.subscriptions_label);
}
EventDistributor.getInstance().register(contentUpdate);
}
@Override
public void onDestroy() {
super.onDestroy();
public void onStart() {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
loadSubscriptions();
}
@Override
public void onStop() {
super.onStop();
EventDistributor.getInstance().unregister(contentUpdate);
if(disposable != null) {
disposable.dispose();
}
@ -172,7 +175,7 @@ public class SubscriptionFragment extends Fragment {
Feed feed = (Feed)selectedObject;
MenuInflater inflater = getActivity().getMenuInflater();
MenuInflater inflater = requireActivity().getMenuInflater();
inflater.inflate(R.menu.nav_feed_context, menu);
menu.setHeaderTitle(feed.getTitle());
@ -182,7 +185,6 @@ public class SubscriptionFragment extends Fragment {
@Override
public boolean onContextItemSelected(MenuItem item) {
final int position = mPosition;
mPosition = -1; // reset
if(position < 0) {
@ -197,84 +199,73 @@ public class SubscriptionFragment extends Fragment {
Feed feed = (Feed)selectedObject;
switch(item.getItemId()) {
case R.id.mark_all_seen_item:
ConfirmationDialog markAllSeenConfirmationDialog = new ConfirmationDialog(getActivity(),
R.string.mark_all_seen_label,
R.string.mark_all_seen_confirmation_msg) {
@Override
public void onConfirmButtonPressed(DialogInterface dialog) {
dialog.dismiss();
Observable.fromCallable(() -> DBWriter.markFeedSeen(feed.getId()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> loadSubscriptions(),
error -> Log.e(TAG, Log.getStackTraceString(error)));
}
};
markAllSeenConfirmationDialog.createNewDialog().show();
case R.id.remove_all_new_flags_item:
displayConfirmationDialog(
R.string.remove_all_new_flags_label,
R.string.remove_all_new_flags_confirmation_msg,
() -> DBWriter.removeFeedNewFlag(feed.getId()));
return true;
case R.id.mark_all_read_item:
ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(),
displayConfirmationDialog(
R.string.mark_all_read_label,
R.string.mark_all_read_confirmation_msg) {
@Override
public void onConfirmButtonPressed(DialogInterface dialog) {
dialog.dismiss();
Observable.fromCallable(() -> DBWriter.markFeedRead(feed.getId()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> loadSubscriptions(),
error -> Log.e(TAG, Log.getStackTraceString(error)));
}
};
markAllReadConfirmationDialog.createNewDialog().show();
R.string.mark_all_read_confirmation_msg,
() -> DBWriter.markFeedRead(feed.getId()));
return true;
case R.id.rename_item:
new RenameFeedDialog(getActivity(), feed).show();
return true;
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(getContext(), feed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
loadSubscriptions();
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(getContext(),
R.string.remove_feed_label,
getString(R.string.feed_delete_confirmation_msg, feed.getTitle())) {
@Override
public void onConfirmButtonPressed(
DialogInterface dialog) {
dialog.dismiss();
long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId();
if (mediaId > 0 &&
FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) {
Log.d(TAG, "Currently playing episode is about to be deleted, skipping");
remover.skipOnCompletion = true;
int playerStatus = PlaybackPreferences.getCurrentPlayerStatus();
if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) {
IntentUtils.sendLocalBroadcast(getContext(), PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE);
}
}
remover.executeAsync();
}
};
conDialog.createNewDialog().show();
displayRemoveFeedDialog(feed);
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public void onResume() {
super.onResume();
loadSubscriptions();
private void displayRemoveFeedDialog(Feed feed) {
final FeedRemover remover = new FeedRemover(getContext(), feed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
loadSubscriptions();
}
};
String message = getString(R.string.feed_delete_confirmation_msg, feed.getTitle());
ConfirmationDialog dialog = new ConfirmationDialog(getContext(), R.string.remove_feed_label, message) {
@Override
public void onConfirmButtonPressed(DialogInterface clickedDialog) {
clickedDialog.dismiss();
long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId();
if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) {
Log.d(TAG, "Currently playing episode is about to be deleted, skipping");
remover.skipOnCompletion = true;
int playerStatus = PlaybackPreferences.getCurrentPlayerStatus();
if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) {
IntentUtils.sendLocalBroadcast(getContext(), PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE);
}
}
remover.executeAsync();
}
};
dialog.createNewDialog().show();
}
private <T> void displayConfirmationDialog(@StringRes int title, @StringRes int message, Callable<? extends T> task) {
ConfirmationDialog dialog = new ConfirmationDialog(getActivity(), title, message) {
@Override
@SuppressLint("CheckResult")
public void onConfirmButtonPressed(DialogInterface clickedDialog) {
clickedDialog.dismiss();
Observable.fromCallable(task)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> loadSubscriptions(),
error -> Log.e(TAG, Log.getStackTraceString(error)));
}
};
dialog.createNewDialog().show();
}
private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {

View File

@ -1,61 +0,0 @@
package de.danoeh.antennapod.fragment.preferences;
import android.os.Bundle;
import android.support.v7.preference.PreferenceFragmentCompat;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.dialog.AutoFlattrPreferenceDialog;
public class FlattrPreferencesFragment extends PreferenceFragmentCompat {
private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
private static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs";
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_flattr);
setupFlattrScreen();
}
@Override
public void onResume() {
super.onResume();
checkFlattrItemVisibility();
}
private void setupFlattrScreen() {
findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
preference -> {
FlattrUtils.revokeAccessToken(getActivity());
checkFlattrItemVisibility();
return true;
}
);
findPreference(PREF_AUTO_FLATTR_PREFS)
.setOnPreferenceClickListener(preference -> {
AutoFlattrPreferenceDialog.newAutoFlattrPreferenceDialog(getActivity(),
new AutoFlattrPreferenceDialog.AutoFlattrPreferenceDialogInterface() {
@Override
public void onCancelled() {
}
@Override
public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue) {
UserPreferences.setAutoFlattrSettings(autoFlattrEnabled, autoFlattrValue);
checkFlattrItemVisibility();
}
});
return true;
});
}
private void checkFlattrItemVisibility() {
boolean hasFlattrToken = FlattrUtils.hasToken();
findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
findPreference(PREF_AUTO_FLATTR_PREFS).setEnabled(hasFlattrToken);
}
}

View File

@ -4,10 +4,8 @@ import android.os.Bundle;
import android.support.v7.preference.PreferenceFragmentCompat;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
public class IntegrationsPreferencesFragment extends PreferenceFragmentCompat {
private static final String PREF_SCREEN_FLATTR = "prefFlattrSettings";
private static final String PREF_SCREEN_GPODDER = "prefGpodderSettings";
@Override
@ -17,19 +15,9 @@ public class IntegrationsPreferencesFragment extends PreferenceFragmentCompat {
}
private void setupIntegrationsScreen() {
findPreference(PREF_SCREEN_FLATTR).setOnPreferenceClickListener(preference -> {
((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_flattr);
return true;
});
findPreference(PREF_SCREEN_GPODDER).setOnPreferenceClickListener(preference -> {
((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_gpodder);
return true;
});
}
@Override
public void onResume() {
super.onResume();
findPreference(PREF_SCREEN_FLATTR).setEnabled(FlattrUtils.hasAPICredentials());
}
}

View File

@ -143,8 +143,5 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
config.index(R.xml.preferences_gpodder)
.addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_integrations))
.addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_gpodder));
config.index(R.xml.preferences_flattr)
.addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_integrations))
.addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_flattr));
}
}

View File

@ -124,10 +124,6 @@ public class FeedItemMenuHandler {
mi.setItemVisibility(R.id.deactivate_auto_download, false);
}
if (selectedItem.getPaymentLink() == null || !selectedItem.getFlattrStatus().flattrable()) {
mi.setItemVisibility(R.id.support_item, false);
}
boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE);
mi.setItemVisibility(R.id.add_to_favorites_item, !isFavorite);
mi.setItemVisibility(R.id.remove_from_favorites_item, isFavorite);
@ -230,9 +226,6 @@ public class FeedItemMenuHandler {
Toast.LENGTH_SHORT).show();
}
break;
case R.id.support_item:
DBTasks.flattrItemIfLoggedIn(context, selectedItem);
break;
case R.id.share_link_item:
ShareUtils.shareFeedItemLink(context, selectedItem);
break;

View File

@ -46,11 +46,6 @@ public class FeedMenuHandler {
}
Log.d(TAG, "Preparing options menu");
if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) {
menu.findItem(R.id.support_item).setVisible(true);
} else {
menu.findItem(R.id.support_item).setVisible(false);
}
menu.findItem(R.id.refresh_complete_item).setVisible(selectedFeed.isPaged());
@ -98,9 +93,6 @@ public class FeedMenuHandler {
Toast.LENGTH_SHORT).show();
}
break;
case R.id.support_item:
DBTasks.flattrFeedIfLoggedIn(context, selectedFeed);
break;
case R.id.share_link_item:
ShareUtils.shareFeedlink(context, selectedFeed);
break;

View File

@ -2,28 +2,51 @@ package de.danoeh.antennapod.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
public class PreferenceUpgrader {
private static final String PREF_CONFIGURED_VERSION = "configuredVersion";
private static final String PREF_NAME = "PreferenceUpgrader";
private static SharedPreferences prefs;
public static void checkUpgrades(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
int oldVersion = prefs.getInt(PREF_CONFIGURED_VERSION, 1070200);
prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences upgraderPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
int oldVersion = upgraderPrefs.getInt(PREF_CONFIGURED_VERSION, 1070200);
int newVersion = BuildConfig.VERSION_CODE;
if (oldVersion != newVersion) {
prefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply();
NotificationUtils.createChannels(context);
upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply();
upgrade(oldVersion);
}
}
private static void upgrade(int oldVersion) {
if (oldVersion < 1070196) {
// migrate episode cleanup value (unit changed from days to hours)
int oldValueInDays = UserPreferences.getEpisodeCleanupValue();
if (oldValueInDays > 0) {
UserPreferences.setEpisodeCleanupValue(oldValueInDays * 24);
} // else 0 or special negative values, no change needed
}
if (oldVersion < 1070197) {
if (prefs.getBoolean("prefMobileUpdate", false)) {
prefs.edit().putString(UserPreferences.PREF_MOBILE_UPDATE, "everything").apply();
}
}
if (oldVersion < 1070300) {
UserPreferences.restartUpdateAlarm();
if (UserPreferences.getMediaPlayer().equals("builtin")) {
prefs.edit().putString(UserPreferences.PREF_MEDIA_PLAYER,
UserPreferences.PREF_MEDIA_PLAYER_EXOPLAYER).apply();
}
}
}
}

View File

@ -1,9 +1,14 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.AttrRes;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@ -15,14 +20,18 @@ public class EmptyViewHandler {
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private final Context context;
private final View emptyView;
private final TextView tvTitle;
private final TextView tvMessage;
private final ImageView ivIcon;
public EmptyViewHandler(Context context) {
emptyView = View.inflate(context, R.layout.empty_view_layout, null);
this.context = context;
tvTitle = emptyView.findViewById(R.id.emptyViewTitle);
tvMessage = emptyView.findViewById(R.id.emptyViewMessage);
ivIcon = emptyView.findViewById(R.id.emptyViewIcon);
}
public void setTitle(int title) {
@ -33,6 +42,14 @@ public class EmptyViewHandler {
tvMessage.setText(message);
}
public void setIcon(@AttrRes int iconAttr) {
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(iconAttr, typedValue, true);
Drawable d = ContextCompat.getDrawable(context, typedValue.resourceId);
ivIcon.setImageDrawable(d);
ivIcon.setVisibility(View.VISIBLE);
}
public void hide() {
emptyView.setVisibility(View.GONE);
}

View File

@ -0,0 +1,35 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
/**
* Source: https://stackoverflow.com/a/46350213/
*/
public class WrappingGridView extends GridView {
public WrappingGridView(Context context) {
super(context);
}
public WrappingGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WrappingGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightSpec = heightMeasureSpec;
if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
// The great Android "hackatlon", the love, the magic.
// The two leftmost bits in the height measure spec have
// a special meaning, hence we can't use them to describe height.
heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightSpec);
}
}

View File

@ -1,5 +1,5 @@
AntennaPod is a podcast manager and player that gives you instant access to millions of free and paid podcasts, from independent podcasters to large publishing houses such as the BBC, NPR and CNN. Add, import and export their feeds hassle-free using the iTunes podcast database, OPML files or simple RSS URLs. Save effort, battery power and mobile data usage with powerful automation controls for downloading episodes (specify times, intervals and WiFi networks) and deleting episodes (based on your favourites and delay settings).<br>
But most importantly: Download, stream or queue episodes and enjoy them the way you like with adjustable playback speeds, chapter support and a sleep timer. You can even show your love to the content creators with our Flattr integration.
But most importantly: Download, stream or queue episodes and enjoy them the way you like with adjustable playback speeds, chapter support and a sleep timer.
Made by podcast-enthousiast, AntennaPod is free in all senses of the word: open source, no costs, no ads.
@ -15,7 +15,6 @@ KEEP TRACK, SHARE & APPRECIATE<br>
&#8226; Keep track of the best of the best by marking episodes as favourites<br>
&#8226; Find that one episode through the playback history or by searching (titles and shownotes)<br>
&#8226; Share episodes and feeds through advanced social media and email options, the gPodder.net services and via OPML export<br>
&#8226; Support content creators with Flattr integration including automatic flattring
CONTROL THE SYSTEM<br>
&#8226; Take control over automated downloading: choose feeds, exclude mobile networks, select specific WiFi networks, require the phone to be charging and set times or intervals<br>

View File

@ -1,100 +1,173 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp">
<TextView
android:id="@+id/txtvPodcastDirectories"
style="@style/AntennaPod.TextView.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/podcastdirectories_label"/>
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/txtvPodcastDirectoriesDescr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/podcastdirectories_descr"
android:textSize="@dimen/text_size_medium"/>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
android:elevation="16dp"
android:layout_margin="8dp">
<Button
android:id="@+id/butSearchItunes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/search_itunes_label"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/butSearchFyyd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/search_fyyd_label"/>
<ImageView
android:layout_width="40dp"
android:layout_height="match_parent"
android:contentDescription="@string/search_podcast_hint"
app:srcCompat="?attr/action_search"
android:scaleType="center"/>
<Button
android:id="@+id/butBrowseGpoddernet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/browse_gpoddernet_label"/>
<EditText
android:id="@+id/combinedFeedSearchBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="text"
android:imeOptions="actionSearch"
android:importantForAutofill="no"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:hint="@string/search_podcast_hint"
android:background="@null"/>
<View style="@style/Divider"/>
</LinearLayout>
<TextView
android:id="@+id/txtvFeedurl"
style="@style/AntennaPod.TextView.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txtvfeedurl_label"/>
</android.support.v7.widget.CardView>
<EditText
android:id="@+id/etxtFeedurl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="@string/etxtFeedurlHint"
android:inputType="textUri"/>
<fragment
android:id="@+id/quickFeedDiscovery"
android:name="de.danoeh.antennapod.fragment.QuickFeedDiscoveryFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"/>
<Button
android:id="@+id/butConfirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/confirm_label"/>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
android:elevation="8dp"
android:layout_margin="8dp">
<View style="@style/Divider"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/txtvOpmlImport"
style="@style/AntennaPod.TextView.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/opml_import_label"/>
<TextView
android:id="@+id/txtvFeedurl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txtvfeedurl_label"
android:textSize="18sp"
android:layout_marginBottom="8dp"
android:textColor="?android:attr/textColorPrimary"/>
<TextView
android:id="@+id/txtvOpmlImportExpl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/opml_import_txtv_button_lable"
android:textSize="@dimen/text_size_medium"/>
<EditText
android:id="@+id/etxtFeedurl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="@string/etxtFeedurlHint"
android:inputType="textUri"/>
<Button
android:id="@+id/butOpmlImport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/opml_import_label"/>
<Button
android:id="@+id/butConfirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/confirm_label"/>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
android:elevation="8dp"
android:layout_margin="8dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/advanced_search"
android:layout_width="96dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="40dp"
android:layout_height="match_parent"
android:contentDescription="@string/advanced_search"
app:srcCompat="?attr/action_search"
android:scaleType="center"
android:layout_marginBottom="4dp"
android:tint="?android:attr/textColorPrimary"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_search"
android:textAlignment="center"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
<LinearLayout
android:id="@+id/btn_opml_import"
android:layout_width="96dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="40dp"
android:layout_height="match_parent"
android:contentDescription="@string/opml_import_label"
app:srcCompat="?attr/av_download"
android:scaleType="center"
android:layout_marginBottom="4dp"
android:tint="?android:attr/textColorPrimary"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/opml_import_label"
android:textAlignment="center"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@ -1,35 +0,0 @@
<?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">
<CheckBox
android:id="@+id/chkAutoFlattr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/auto_flattr_enable"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small" />
<SeekBar
android:id="@+id/skbPercent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:max="100" />
<TextView
android:id="@+id/txtvStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:ellipsize="end"
android:lines="2"
android:text="@string/auto_flattr_after_percent"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small" />
</LinearLayout>

View File

@ -1,31 +1,39 @@
<?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:gravity="center"
android:layout_centerInParent="true"
xmlns:tools="http://schemas.android.com/tools">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:layout_centerInParent="true"
android:paddingLeft="40dp"
android:paddingRight="40dp"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/emptyViewIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingBottom="8dp"
android:visibility="gone"
tools:src="@drawable/ic_feed_grey600_24dp"
tools:visibility="visible"/>
<TextView
android:id="@+id/emptyViewTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
tools:text="empty"
android:textSize="20sp"
android:textStyle="bold"
android:paddingBottom="10dp"/>
android:id="@+id/emptyViewTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Title"
android:textSize="16sp"
android:textStyle="bold"
android:paddingBottom="8dp"/>
<TextView
android:id="@+id/emptyViewMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
tools:text="empty"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:textAlignment="center"/>
android:id="@+id/emptyViewMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
tools:text="Message"
android:textAlignment="center"/>
</LinearLayout>

View File

@ -1,30 +0,0 @@
<?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" >
<TextView
android:id="@+id/txtvExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/flattr_auth_explanation" />
<Button
android:id="@+id/but_authenticate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="8dp"
android:text="@string/authenticate_label" />
<Button
android:id="@+id/but_return_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/return_home_label"
android:visibility="gone" />
</LinearLayout>

View File

@ -36,7 +36,7 @@
android:cursorVisible="true"
android:maxLines="1"
android:inputType="text"
android:imeOptions="actionNext"
android:imeOptions="actionNext|flagNoFullscreen"
android:nextFocusForward="@id/etxtPassword"/>
<EditText
@ -50,7 +50,7 @@
android:focusable="true"
android:focusableInTouchMode="true"
android:cursorVisible="true"
android:imeOptions="actionGo"
android:imeOptions="actionGo|flagNoFullscreen"
android:imeActionLabel="@string/gpodnetauth_login_butLabel"/>
<Button

View File

@ -30,7 +30,8 @@
android:layout_height="wrap_content"
android:hint="@string/gpodnetauth_device_caption"
android:layout_below="@id/txtvDescription"
android:layout_margin="8dp"/>
android:layout_margin="8dp"
android:imeOptions="flagNoFullscreen"/>
<TextView
android:id="@+id/txtvDeviceID"
@ -53,7 +54,8 @@
android:layout_toEndOf="@id/txtvDeviceID"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_margin="8dp"/>
android:layout_margin="8dp"
android:imeOptions="flagNoFullscreen"/>
<Button
android:id="@+id/butCreateNewDevice"

View File

@ -51,11 +51,13 @@
android:orientation="vertical">
<RelativeLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:paddingTop="@dimen/scrubber_vertical_padding"
android:paddingBottom="@dimen/scrubber_vertical_padding">
<TextView
<TextView
android:id="@+id/txtvPosition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -67,9 +69,9 @@
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro"
tools:background="@android:color/holo_green_dark" />
tools:background="@android:color/holo_green_dark"/>
<TextView
<TextView
android:id="@+id/txtvLength"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -81,9 +83,9 @@
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro"
tools:background="@android:color/holo_green_dark" />
tools:background="@android:color/holo_green_dark"/>
<SeekBar
<SeekBar
android:id="@+id/sbPosition"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -97,9 +99,9 @@
android:layout_toRightOf="@id/txtvPosition"
android:layout_toEndOf="@id/txtvPosition"
android:max="500"
tools:background="@android:color/holo_green_dark" />
tools:background="@android:color/holo_green_dark"/>
</RelativeLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/player_control"

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
android:elevation="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="vertical">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/discover"
android:textSize="18sp"
android:layout_marginBottom="8dp"
android:textColor="?android:attr/textColorPrimary"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/discover_more"
android:gravity="end"
android:textAlignment="viewEnd"
android:textSize="18sp"
android:background="?android:attr/selectableItemBackground"
android:layout_marginBottom="8dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_weight="1"
android:id="@+id/discover_more"
android:textColor="@color/antennapod_blue"/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<de.danoeh.antennapod.view.WrappingGridView
android:id="@+id/discover_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="4"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:horizontalSpacing="4dp"
android:verticalSpacing="4dp"
android:scrollbars="none"
android:layout_marginTop="8dp"
android:layout_centerInParent="true"
android:layout_gravity="center_horizontal"/>
<ProgressBar
android:id="@+id/discover_progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_centerInParent="true"
android:layout_marginTop="30dp"/>
<TextView
android:id="@+id/discover_error"
android:textColor="@color/md_edittext_error"
android:layout_width="match_parent"
android:layout_centerInParent="true"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@ -0,0 +1,14 @@
<?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">
<de.danoeh.antennapod.view.SquareImageView
android:id="@+id/discovery_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackground"/>
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/search_itunes"
android:title="@string/search_itunes_label" />
<item
android:id="@+id/search_gpodder"
android:title="@string/browse_gpoddernet_label" />
<item
android:id="@+id/search_fyyd"
android:title="@string/search_fyyd_label" />
</menu>

View File

@ -9,9 +9,9 @@
<item
android:id="@+id/mark_as_seen_item"
android:id="@+id/remove_new_flag_item"
android:menuCategory="container"
android:title="@string/mark_as_seen_label" />
android:title="@string/remove_new_flag_label" />
<item
android:id="@+id/mark_read_item"
@ -83,10 +83,4 @@
android:title="@string/share_file_label" />
</menu>
</item>
<item
android:id="@+id/support_item"
android:menuCategory="container"
android:title="@string/support_label" />
</menu>

View File

@ -25,8 +25,8 @@
android:icon="?attr/navigation_accept"/>
<item
android:id="@+id/mark_all_seen_item"
android:title="@string/mark_all_seen_label"
android:id="@+id/remove_all_new_flags_item"
android:title="@string/remove_all_new_flags_label"
android:menuCategory="container"
custom:showAsAction="collapseActionView"
android:icon="?attr/navigation_accept"/>

View File

@ -8,12 +8,6 @@
android:title="@string/visit_website_label"
android:visible="true">
</item>
<item
android:id="@+id/support_item"
custom:showAsAction="ifRoom|collapseActionView"
android:title="@string/support_label"
android:visible="false">
</item>
<item
android:id="@+id/share_link_item"
custom:showAsAction="collapseActionView"

View File

@ -91,12 +91,6 @@
</menu>
</item>
<item
android:id="@+id/support_item"
custom:showAsAction="collapseActionView"
android:title="@string/support_label">
</item>
<item
android:id="@+id/open_podcast"
custom:showAsAction="collapseActionView"

View File

@ -83,11 +83,4 @@
android:title="@string/share_file_label" />
</menu>
</item>
<item
android:id="@+id/support_item"
android:menuCategory="container"
android:title="@string/support_label" />
</menu>

View File

@ -37,14 +37,6 @@
custom:actionViewClass="android.support.v7.widget.SearchView"
android:title="@string/search_label"/>
<item
android:id="@+id/support_item"
android:menuCategory="container"
android:title="@string/support_label"
android:visible="false"
custom:showAsAction="collapseActionView">
</item>
<item
android:id="@+id/visit_website_item"
android:icon="?attr/location_web_site"

View File

@ -78,11 +78,4 @@
android:title="@string/share_file_label" />
</menu>
</item>
<item
android:id="@+id/support_item"
custom:showAsAction="collapseActionView"
android:title="@string/support_label"
android:visible="false">
</item>
</menu>

View File

@ -2,9 +2,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/mark_all_seen_item"
android:id="@+id/remove_all_new_flags_item"
android:menuCategory="container"
android:title="@string/mark_all_seen_label" />
android:title="@string/remove_all_new_flags_label" />
<item
android:id="@+id/mark_all_read_item"

View File

@ -23,13 +23,6 @@
custom:actionViewClass="android.support.v7.widget.SearchView"
android:title="@string/search_label"/>
<item
android:id="@+id/clear_queue"
android:title="@string/clear_queue_label"
android:menuCategory="container"
custom:showAsAction="collapseActionView"
android:icon="?attr/navigation_accept"/>
<item
android:id="@+id/queue_sort"
android:title="@string/sort">
@ -112,4 +105,15 @@
</menu>
</item>
<item
android:id="@+id/clear_queue"
android:title="@string/clear_queue_label"
custom:showAsAction="collapseActionView"
android:icon="?attr/navigation_accept"/>
<item
android:id="@+id/episode_actions"
custom:showAsAction="collapseActionView"
android:title="@string/batch_edit" />
</menu>

View File

@ -84,10 +84,4 @@
android:title="@string/share_file_label" />
</menu>
</item>
<item
android:id="@+id/support_item"
android:menuCategory="container"
android:title="@string/support_label" />
</menu>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:key="pref_flattr_authenticate"
android:summary="@string/pref_flattr_auth_sum"
android:title="@string/pref_flattr_auth_title">
<intent android:action=".activities.FlattrAuthActivity"/>
</PreferenceScreen>
<Preference
android:key="prefAutoFlattrPrefs"
android:summary="@string/pref_auto_flattr_sum"
android:title="@string/pref_auto_flattr_title"/>
<Preference
android:key="prefRevokeAccess"
android:summary="@string/pref_revokeAccess_sum"
android:title="@string/pref_revokeAccess_title"/>
</PreferenceScreen>

View File

@ -1,12 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
android:key="prefFlattrSettings"
android:title="@string/flattr_label"
android:summary="@string/flattr_summary" />
xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:key="prefGpodderSettings"

View File

@ -115,7 +115,7 @@
<PreferenceCategory android:title="@string/media_player">
<ListPreference
android:defaultValue="sonic"
android:defaultValue="exoplayer"
android:entries="@array/media_player_options"
android:key="prefMediaPlayer"
android:title="@string/media_player"

View File

@ -103,11 +103,6 @@ by greenrobot, licensed under the Apache 2.0 license <a href="LICENSE_APACHE-2.0
by Google, licensed under the Apache 2.0 license <a href="LICENSE_APACHE-2.0.txt">(View)</a>
</div>
<div class="card">
<h2>flattr4j <a href="http://www.shredzone.org/projects/flattr4j/wiki">(Link)</a></h2>
licensed under the Apache 2.0 license <a href="LICENSE_APACHE-2.0.txt">(View)</a>
</div>
<div class="card">
<h2>Glide <a href="https://github.com/bumptech/glide/">(Link)</a></h2>
licensed under the Simplified BSD license <a href="LICENSE_GLIDE.txt">(View)</a>

View File

@ -40,9 +40,9 @@ subprojects {
}
project.ext {
compileSdkVersion = 26
compileSdkVersion = 28
minSdkVersion = 14
targetSdkVersion = 26
targetSdkVersion = 28
supportVersion = "27.1.1"
lifecycle_version = "1.1.1"
@ -52,7 +52,6 @@ project.ext {
commonslangVersion = "3.6"
commonstextVersion = "1.3"
eventbusVersion = "3.1.1"
flattr4jVersion = "2.14"
glideVersion = "4.8.0"
glideOkhttpIntegrationVersion = "4.8.0"
iconifyVersion = "2.2.2"

View File

@ -54,9 +54,6 @@ dependencies {
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
implementation "org.apache.commons:commons-text:$commonstextVersion"
implementation ("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") {
exclude group: "org.json", module: "json"
}
implementation "commons-io:commons-io:$commonsioVersion"
implementation "com.jayway.android.robotium:robotium-solo:$robotiumSoloVersion"
implementation "org.jsoup:jsoup:$jsoupVersion"

View File

@ -27,8 +27,6 @@ public class ClientConfig {
public static GpodnetCallbacks gpodnetCallbacks;
public static FlattrCallbacks flattrCallbacks;
public static DBTasksCallbacks dbTasksCallbacks;
public static CastCallbacks castCallbacks;
@ -41,7 +39,6 @@ public class ClientConfig {
}
PodDBAdapter.init(context);
UserPreferences.init(context);
UpdateManager.init(context);
PlaybackPreferences.init(context);
NetworkUtils.init(context);
SleepTimerPreferences.init(context);

View File

@ -1,36 +0,0 @@
package de.danoeh.antennapod.core;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import org.shredzone.flattr4j.oauth.AccessToken;
/**
* Callbacks for the flattr integration of the app.
*/
public interface FlattrCallbacks {
/**
* Returns if true if the flattr integration should be activated,
* false otherwise.
*/
boolean flattrEnabled();
/**
* Returns an intent that starts the activity that is responsible for
* letting users log into their flattr account.
*
* @return The intent that starts the authentication activity or null
* if flattr integration is disabled (i.e. flattrEnabled() == false).
*/
Intent getFlattrAuthenticationActivityIntent(Context context);
PendingIntent getFlattrFailedNotificationContentIntent(Context context);
String getFlattrAppKey();
String getFlattrAppSecret();
void handleFlattrAuthenticationSuccess(AccessToken token);
}

View File

@ -1,81 +0,0 @@
package de.danoeh.antennapod.core;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import org.antennapod.audio.MediaPlayer;
import de.danoeh.antennapod.core.preferences.UserPreferences;
/*
* This class's job is do perform maintenance tasks whenever the app has been updated
*/
class UpdateManager {
private UpdateManager(){}
private static final String TAG = UpdateManager.class.getSimpleName();
private static final String PREF_NAME = "app_version";
private static final String KEY_VERSION_CODE = "version_code";
private static int currentVersionCode;
private static Context context;
private static SharedPreferences prefs;
public static void init(Context context) {
UpdateManager.context = context;
prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
PackageManager pm = context.getPackageManager();
try {
PackageInfo info = pm.getPackageInfo(context.getPackageName(), 0);
currentVersionCode = info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to obtain package info for package name: " + context.getPackageName(), e);
currentVersionCode = 0;
return;
}
final int oldVersionCode = getStoredVersionCode();
Log.d(TAG, "old: " + oldVersionCode + ", current: " + currentVersionCode);
if(oldVersionCode < currentVersionCode) {
onUpgrade(oldVersionCode, currentVersionCode);
setCurrentVersionCode();
}
}
private static int getStoredVersionCode() {
return prefs.getInt(KEY_VERSION_CODE, -1);
}
private static void setCurrentVersionCode() {
prefs.edit().putInt(KEY_VERSION_CODE, currentVersionCode).apply();
}
private static void onUpgrade(final int oldVersionCode, final int newVersionCode) {
if (oldVersionCode < 1050004) {
if(MediaPlayer.isPrestoLibraryInstalled(context) && Build.VERSION.SDK_INT >= 16) {
UserPreferences.enableSonic();
}
}
if (oldVersionCode < 1070196) {
// migrate episode cleanup value (unit changed from days to hours)
int oldValueInDays = UserPreferences.getEpisodeCleanupValue();
if (oldValueInDays > 0) {
UserPreferences.setEpisodeCleanupValue(oldValueInDays * 24);
} // else 0 or special negative values, no change needed
}
if (oldVersionCode < 1070197) {
if (prefs.getBoolean("prefMobileUpdate", false)) {
prefs.edit().putString(UserPreferences.PREF_MOBILE_UPDATE, "everything").apply();
}
}
}
}

View File

@ -1,231 +0,0 @@
package de.danoeh.antennapod.core.asynctask;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import org.shredzone.flattr4j.exception.FlattrException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
/**
* Performs a click action in a background thread.
* <p/>
* When started, the flattr click worker will try to flattr every item that is in the flattr queue. If no network
* connection is available it will shut down immediately. The FlattrClickWorker can also be given one additional
* FlattrThing which will be flattrd immediately.
* <p/>
* The FlattrClickWorker will display a toast notification for every item that has been flattrd. If the FlattrClickWorker failed
* to flattr something, a notification will be displayed.
*/
public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorker.ExitCode> {
private static final String TAG = "FlattrClickWorker";
private static final int NOTIFICATION_ID = 4;
private final Context context;
public enum ExitCode {EXIT_NORMAL, NO_TOKEN, NO_NETWORK, NO_THINGS}
private final AtomicInteger countFailed = new AtomicInteger();
private final AtomicInteger countSuccess = new AtomicInteger();
private volatile FlattrThing extraFlattrThing;
/**
* Only relevant if just one thing is flattrd
*/
private volatile FlattrException exception;
/**
* Creates a new FlattrClickWorker which will only flattr all things in the queue.
* <p/>
* The FlattrClickWorker has to be started by calling executeAsync().
*
* @param context A context for accessing the database and posting notifications. Must not be null.
*/
public FlattrClickWorker(@NonNull Context context) {
this.context = context.getApplicationContext();
}
/**
* Creates a new FlattrClickWorker which will flattr all things in the queue and one additional
* FlattrThing.
* <p/>
* The FlattrClickWorker has to be started by calling executeAsync().
*
* @param context A context for accessing the database and posting notifications. Must not be null.
* @param extraFlattrThing The additional thing to flattr
*/
public FlattrClickWorker(Context context, FlattrThing extraFlattrThing) {
this(context);
this.extraFlattrThing = extraFlattrThing;
}
@Override
protected ExitCode doInBackground(Void... params) {
if (!FlattrUtils.hasToken()) {
return ExitCode.NO_TOKEN;
}
if (!NetworkUtils.networkAvailable()) {
return ExitCode.NO_NETWORK;
}
final List<FlattrThing> flattrQueue = DBReader.getFlattrQueue();
if (extraFlattrThing != null) {
flattrQueue.add(extraFlattrThing);
} else if (flattrQueue.size() == 1) {
// if only one item is flattrd, the report can specifically mentioned that this item has failed
extraFlattrThing = flattrQueue.get(0);
}
if (flattrQueue.isEmpty()) {
return ExitCode.NO_THINGS;
}
List<Future<?>> dbFutures = new LinkedList<>();
for (FlattrThing thing : flattrQueue) {
if (BuildConfig.DEBUG) Log.d(TAG, "Processing " + thing.getTitle());
try {
thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely
FlattrUtils.clickUrl(context, thing.getPaymentLink());
thing.getFlattrStatus().setFlattred();
publishProgress(R.string.flattr_click_success);
countSuccess.incrementAndGet();
} catch (FlattrException e) {
e.printStackTrace();
int failed = countFailed.incrementAndGet();
if (failed == 1) {
exception = e;
}
}
Future<?> f = DBWriter.setFlattredStatus(context, thing, false);
if (f != null) {
dbFutures.add(f);
}
}
for (Future<?> f : dbFutures) {
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
return ExitCode.EXIT_NORMAL;
}
@Override
protected void onPostExecute(ExitCode exitCode) {
super.onPostExecute(exitCode);
switch (exitCode) {
case EXIT_NORMAL:
if (countFailed.get() > 0) {
postFlattrFailedNotification();
}
break;
case NO_NETWORK:
postToastNotification(R.string.flattr_click_enqueued);
break;
case NO_TOKEN:
postNoTokenNotification();
break;
case NO_THINGS: // nothing to notify here
break;
}
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
postToastNotification(values[0]);
}
private void postToastNotification(int msg) {
Toast.makeText(context, context.getString(msg), Toast.LENGTH_LONG).show();
}
private void postNoTokenNotification() {
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
ClientConfig.flattrCallbacks.getFlattrAuthenticationActivityIntent(context), 0);
Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.no_flattr_token_notification_msg)))
.setContentIntent(contentIntent)
.setContentTitle(context.getString(R.string.no_flattr_token_title))
.setTicker(context.getString(R.string.no_flattr_token_title))
.setSmallIcon(R.drawable.stat_notify_sync_error)
.setOngoing(false)
.setAutoCancel(true)
.build();
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification);
}
private void postFlattrFailedNotification() {
int failed = countFailed.get();
if (failed == 0) {
return;
}
PendingIntent contentIntent = ClientConfig.flattrCallbacks.getFlattrFailedNotificationContentIntent(context);
String title;
String subtext;
if (failed == 1) {
title = context.getString(R.string.flattrd_failed_label);
String exceptionMsg = (exception.getMessage() != null) ? exception.getMessage() : "";
subtext = context.getString(R.string.flattr_click_failure, extraFlattrThing.getTitle())
+ "\n" + exceptionMsg;
} else {
title = context.getString(R.string.flattrd_label);
subtext = context.getString(R.string.flattr_click_success_count, countSuccess.get()) + "\n"
+ context.getString(R.string.flattr_click_failure_count, failed);
}
Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(subtext))
.setContentIntent(contentIntent)
.setContentTitle(title)
.setTicker(title)
.setSmallIcon(R.drawable.stat_notify_sync_error)
.setOngoing(false)
.setAutoCancel(true)
.build();
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification);
}
/**
* Starts the FlattrClickWorker as an AsyncTask.
*/
public void executeAsync() {
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View File

@ -1,45 +0,0 @@
package de.danoeh.antennapod.core.asynctask;
import android.content.Context;
import android.util.Log;
import org.shredzone.flattr4j.exception.FlattrException;
import org.shredzone.flattr4j.model.Flattr;
import java.util.List;
import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
/**
* Fetch list of flattred things and flattr status in database in a background thread.
*/
public class FlattrStatusFetcher extends Thread {
private static final String TAG = "FlattrStatusFetcher";
public FlattrStatusFetcher(Context context) {
super();
}
@Override
public void run() {
if (BuildConfig.DEBUG) Log.d(TAG, "Starting background work: Retrieving Flattr status");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
try {
List<Flattr> flattredThings = FlattrUtils.retrieveFlattredThings();
DBWriter.setFlattredStatus(flattredThings).get();
} catch (FlattrException e) {
e.printStackTrace();
Log.d(TAG, "flattrQueue exception retrieving list with flattred items " + e.getMessage());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (BuildConfig.DEBUG) Log.d(TAG, "Finished background work: Retrieved Flattr status");
}
}

View File

@ -1,86 +0,0 @@
package de.danoeh.antennapod.core.asynctask;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import org.shredzone.flattr4j.exception.FlattrException;
import org.shredzone.flattr4j.oauth.AccessToken;
import org.shredzone.flattr4j.oauth.AndroidAuthenticator;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
/**
* Fetches the access token in the background in order to avoid networkOnMainThread exception.
*/
public class FlattrTokenFetcher extends AsyncTask<Void, Void, AccessToken> {
private static final String TAG = "FlattrTokenFetcher";
private final Context context;
private final AndroidAuthenticator auth;
private AccessToken token;
private final Uri uri;
private ProgressDialog dialog;
private FlattrException exception;
public FlattrTokenFetcher(Context context, AndroidAuthenticator auth, Uri uri) {
super();
this.context = context;
this.auth = auth;
this.uri = uri;
}
@Override
protected void onPostExecute(AccessToken result) {
if (result != null) {
FlattrUtils.storeToken(result);
}
dialog.dismiss();
if (exception == null) {
ClientConfig.flattrCallbacks.handleFlattrAuthenticationSuccess(result);
} else {
FlattrUtils.showErrorDialog(context, exception.getMessage());
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(context);
dialog.setMessage(context.getString(R.string.processing_label));
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
}
@Override
protected AccessToken doInBackground(Void... params) {
try {
token = auth.fetchAccessToken(uri);
} catch (FlattrException e) {
e.printStackTrace();
exception = e;
return null;
}
if (token != null) {
if (BuildConfig.DEBUG) Log.d(TAG, "Successfully got token");
return token;
} else {
Log.w(TAG, "Flattr token was null");
return null;
}
}
public void executeAsync() {
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View File

@ -1,5 +1,7 @@
package de.danoeh.antennapod.core.event;
import android.support.annotation.NonNull;
import java.util.Arrays;
import java.util.List;
@ -11,6 +13,7 @@ import de.danoeh.antennapod.core.util.LongList;
public class DownloaderUpdate {
/* Downloaders that are currently running */
@NonNull
public final List<Downloader> downloaders;
/**
@ -25,7 +28,7 @@ public class DownloaderUpdate {
*/
public final long[] mediaIds;
public DownloaderUpdate(List<Downloader> downloaders) {
DownloaderUpdate(@NonNull List<Downloader> downloaders) {
this.downloaders = downloaders;
LongList feedIds1 = new LongList(), mediaIds1 = new LongList();
for(Downloader d1 : downloaders) {

View File

@ -11,15 +11,12 @@ import java.util.List;
import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
/**
* Data Object for a whole feed
*
* @author daniel
*/
public class Feed extends FeedFile implements FlattrThing, ImageResource {
public class Feed extends FeedFile implements ImageResource {
public static final int FEEDFILETYPE_FEED = 0;
public static final String TYPE_RSS2 = "rss";
@ -52,7 +49,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
*/
private String lastUpdate;
private FlattrStatus flattrStatus;
private String paymentLink;
/**
* Feed type, for example RSS 2 or Atom
@ -97,7 +93,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
*/
public Feed(long id, String lastUpdate, String title, String customTitle, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink,
String downloadUrl, boolean downloaded, boolean paged, String nextPageLink,
String filter, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
this.id = id;
@ -112,7 +108,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
this.type = type;
this.feedIdentifier = feedIdentifier;
this.imageUrl = imageUrl;
this.flattrStatus = status;
this.paged = paged;
this.nextPageLink = nextPageLink;
this.items = new ArrayList<>();
@ -125,13 +120,13 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
}
/**
* This constructor is used for test purposes and uses a default flattr status object.
* This constructor is used for test purposes
*/
public Feed(long id, String lastUpdate, String title, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded) {
this(id, lastUpdate, title, null, link, description, paymentLink, author, language, type, feedIdentifier, imageUrl,
fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null, false);
fileUrl, downloadUrl, downloaded, false, null, null, false);
}
/**
@ -139,7 +134,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
*/
public Feed() {
super();
this.flattrStatus = new FlattrStatus();
}
/**
@ -149,7 +143,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
public Feed(String url, String lastUpdate) {
super(null, url, false);
this.lastUpdate = lastUpdate;
this.flattrStatus = new FlattrStatus();
}
/**
@ -159,7 +152,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
public Feed(String url, String lastUpdate, String title) {
this(url, lastUpdate);
this.feedTitle = title;
this.flattrStatus = new FlattrStatus();
}
/**
@ -186,7 +178,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
int indexFlattrStatus = cursor.getColumnIndex(PodDBAdapter.KEY_FLATTR_STATUS);
int indexIsPaged = cursor.getColumnIndex(PodDBAdapter.KEY_IS_PAGED);
int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK);
int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE);
@ -209,7 +200,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
cursor.getString(indexFileUrl),
cursor.getString(indexDownloadUrl),
cursor.getInt(indexDownloaded) > 0,
new FlattrStatus(cursor.getLong(indexFlattrStatus)),
cursor.getInt(indexIsPaged) > 0,
cursor.getString(indexNextPageLink),
cursor.getString(indexHide),
@ -291,9 +281,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
if (other.paymentLink != null) {
paymentLink = other.paymentLink;
}
if (other.flattrStatus != null) {
flattrStatus = other.flattrStatus;
}
// this feed's nextPage might already point to a higher page, so we only update the nextPage value
// if this feed is not paged and the other feed is.
if (!this.paged && other.paged) {
@ -444,14 +431,6 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
this.feedIdentifier = feedIdentifier;
}
public void setFlattrStatus(FlattrStatus status) {
this.flattrStatus = status;
}
public FlattrStatus getFlattrStatus() {
return flattrStatus;
}
public String getPaymentLink() {
return paymentLink;
}

View File

@ -18,15 +18,13 @@ import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ShownotesProvider;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
/**
* Data Object for a XML message
*
* @author daniel
*/
public class FeedItem extends FeedComponent implements ShownotesProvider, FlattrThing, ImageResource {
public class FeedItem extends FeedComponent implements ShownotesProvider, ImageResource {
/** tag that indicates this item is in the queue */
public static final String TAG_QUEUE = "Queue";
@ -60,7 +58,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public static final int PLAYED = 1;
private String paymentLink;
private final FlattrStatus flattrStatus;
/**
* Is true if the database contains any chapters that belong to this item. This attribute is only
@ -92,7 +89,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public FeedItem() {
this.state = UNPLAYED;
this.flattrStatus = new FlattrStatus();
this.hasChapters = false;
}
@ -100,7 +96,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* This constructor is used by DBReader.
* */
public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId,
FlattrStatus flattrStatus, boolean hasChapters, String imageUrl, int state,
boolean hasChapters, String imageUrl, int state,
String itemIdentifier, long autoDownload) {
this.id = id;
this.title = title;
@ -108,7 +104,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.pubDate = pubDate;
this.paymentLink = paymentLink;
this.feedId = feedId;
this.flattrStatus = flattrStatus;
this.hasChapters = hasChapters;
this.imageUrl = imageUrl;
this.state = state;
@ -127,7 +122,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null;
this.state = state;
this.feed = feed;
this.flattrStatus = new FlattrStatus();
this.hasChapters = false;
}
@ -142,7 +136,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null;
this.state = state;
this.feed = feed;
this.flattrStatus = new FlattrStatus();
this.hasChapters = hasChapters;
}
@ -153,7 +146,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
int indexPubDate = cursor.getColumnIndex(PodDBAdapter.KEY_PUBDATE);
int indexPaymentLink = cursor.getColumnIndex(PodDBAdapter.KEY_PAYMENT_LINK);
int indexFeedId = cursor.getColumnIndex(PodDBAdapter.KEY_FEED);
int indexFlattrStatus = cursor.getColumnIndex(PodDBAdapter.KEY_FLATTR_STATUS);
int indexHasChapters = cursor.getColumnIndex(PodDBAdapter.KEY_HAS_CHAPTERS);
int indexRead = cursor.getColumnIndex(PodDBAdapter.KEY_READ);
int indexItemIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_ITEM_IDENTIFIER);
@ -167,13 +159,12 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
String paymentLink = cursor.getString(indexPaymentLink);
long feedId = cursor.getLong(indexFeedId);
boolean hasChapters = cursor.getInt(indexHasChapters) > 0;
FlattrStatus flattrStatus = new FlattrStatus(cursor.getLong(indexFlattrStatus));
int state = cursor.getInt(indexRead);
String itemIdentifier = cursor.getString(indexItemIdentifier);
long autoDownload = cursor.getLong(indexAutoDownload);
String imageUrl = cursor.getString(indexImageUrl);
return new FeedItem(id, title, link, pubDate, paymentLink, feedId, flattrStatus,
return new FeedItem(id, title, link, pubDate, paymentLink, feedId,
hasChapters, imageUrl, state, itemIdentifier, autoDownload);
}
@ -329,10 +320,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public void setContentEncoded(String contentEncoded) {
this.contentEncoded = contentEncoded;
}
public FlattrStatus getFlattrStatus() {
return flattrStatus;
}
public String getPaymentLink() {
return paymentLink;

View File

@ -25,7 +25,6 @@ import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
public class FeedMedia extends FeedFile implements Playable {
@ -49,7 +48,7 @@ public class FeedMedia extends FeedFile implements Playable {
private int duration;
private int position; // Current position in file
private long lastPlayedTime; // Last time this media was played (in ms)
private int played_duration; // How many ms of this file have been played (for autoflattring)
private int played_duration; // How many ms of this file have been played
private long size; // File size in Byte
private String mime_type;
@Nullable private volatile FeedItem item;
@ -526,16 +525,6 @@ public class FeedMedia extends FeedFile implements Playable {
.build();
GpodnetPreferences.enqueueEpisodeAction(action);
}
// Auto flattr
float autoFlattrThreshold = UserPreferences.getAutoFlattrPlayedDurationThreshold();
if (FlattrUtils.hasToken() &&
UserPreferences.isAutoFlattr() &&
item.getPaymentLink() != null &&
item.getFlattrStatus().getUnflattred() &&
((completed && autoFlattrThreshold <= 1.0f) ||
(played_duration >= autoFlattrThreshold * duration))) {
DBTasks.flattrItemIfLoggedIn(context, item);
}
}
}

View File

@ -95,8 +95,6 @@ public class UserPreferences {
private static final String PREF_PROXY_PASSWORD = "prefProxyPassword";
// Services
private static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
private static final String PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD = "prefAutoFlattrPlayedDurationThreshold";
private static final String PREF_GPODNET_NOTIFICATIONS = "pref_gpodnet_notifications";
// Other
@ -318,10 +316,6 @@ public class UserPreferences {
return prefs.getBoolean(PREF_DELETE_REMOVES_FROM_QUEUE, false);
}
public static boolean isAutoFlattr() {
return prefs.getBoolean(PREF_AUTO_FLATTR, false);
}
public static String getPlaybackSpeed() {
return prefs.getString(PREF_PLAYBACK_SPEED, "1.00");
}
@ -450,16 +444,7 @@ public class UserPreferences {
}
public static int getRewindSecs() {
return prefs.getInt(PREF_REWIND_SECS, 30);
}
/**
* Returns the time after which an episode should be auto-flattr'd in percent of the episode's
* duration.
*/
public static float getAutoFlattrPlayedDurationThreshold() {
return prefs.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, 0.8f);
return prefs.getInt(PREF_REWIND_SECS, 10);
}
public static String[] getAutodownloadSelectedNetworks() {
@ -588,23 +573,6 @@ public class UserPreferences {
AutoUpdateManager.disableAutoUpdate();
}
/**
* Change the auto-flattr settings
*
* @param enabled Whether automatic flattring should be enabled at all
* @param autoFlattrThreshold The percentage of playback time after which an episode should be
* flattrd. Must be a value between 0 and 1 (inclusive)
* */
public static void setAutoFlattrSettings( boolean enabled, float autoFlattrThreshold) {
if(autoFlattrThreshold < 0.0 || autoFlattrThreshold > 1.0) {
throw new IllegalArgumentException("Flattr threshold must be in range [0.0, 1.0]");
}
prefs.edit()
.putBoolean(PREF_AUTO_FLATTR, enabled)
.putFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, autoFlattrThreshold)
.apply();
}
public static boolean gpodnetNotificationsEnabled() {
return prefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true);
}
@ -681,12 +649,16 @@ public class UserPreferences {
return selectedSpeeds;
}
public static String getMediaPlayer() {
return prefs.getString(PREF_MEDIA_PLAYER, PREF_MEDIA_PLAYER_EXOPLAYER);
}
public static boolean useSonic() {
return prefs.getString(PREF_MEDIA_PLAYER, "sonic").equals("sonic");
return getMediaPlayer().equals("sonic");
}
public static boolean useExoplayer() {
return prefs.getString(PREF_MEDIA_PLAYER, "sonic").equals(PREF_MEDIA_PLAYER_EXOPLAYER);
return getMediaPlayer().equals(PREF_MEDIA_PLAYER_EXOPLAYER);
}
public static void enableSonic() {

View File

@ -41,6 +41,7 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
private PlaybackService playbackService;
private final Object waitForService = new Object();
private final Object waitUsingService = new Object();
private static final int JOB_ID = -17001;
@ -67,7 +68,11 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
}
}
updateViews();
synchronized (waitUsingService) {
if (playbackService != null) {
updateViews();
}
}
if (playbackService != null) {
try {
@ -230,7 +235,9 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
@Override
public void onServiceDisconnected(ComponentName name) {
playbackService = null;
synchronized (waitUsingService) {
playbackService = null;
}
Log.d(TAG, "Disconnected from service");
}

View File

@ -243,7 +243,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
callback.onMediaChanged(false);
if (stream) {
mediaPlayer.setDataSource(media.getStreamUrl());
} else if (new File(media.getLocalMediaUrl()).canRead()) {
} else if (media.getLocalMediaUrl() != null && new File(media.getLocalMediaUrl()).canRead()) {
mediaPlayer.setDataSource(media.getLocalMediaUrl());
} else {
throw new IOException("Unable to read local file " + media.getLocalMediaUrl());

View File

@ -26,7 +26,6 @@ import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.comparator.PlaybackCompletionDateComparator;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
/**
* Provides methods for reading data from the AntennaPod database.
@ -1003,36 +1002,6 @@ public final class DBReader {
}
}
/**
* Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems.
*
* @return The flattr queue as a List.
*/
public static List<FlattrThing> getFlattrQueue() {
Log.d(TAG, "getFlattrQueue() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FlattrThing> result = new ArrayList<>();
// load feeds
Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor();
if (feedCursor.moveToFirst()) {
do {
result.add(extractFeedFromCursorRow(feedCursor));
} while (feedCursor.moveToNext());
}
feedCursor.close();
//load feed items
Cursor feedItemCursor = adapter.getFeedItemsInFlattrQueueCursor();
result.addAll(extractItemlistFromCursor(adapter, feedItemCursor));
feedItemCursor.close();
adapter.close();
Log.d(TAG, "Returning flattrQueueIterator for queue with " + result.size() + " items.");
return result;
}
/**
* Returns data necessary for displaying the navigation drawer. This includes
* the list of subscriptions, the number of items in the queue and the number of unread

View File

@ -20,8 +20,6 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.asynctask.FlattrStatusFetcher;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
@ -35,7 +33,6 @@ import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import static android.content.Context.MODE_PRIVATE;
@ -182,14 +179,6 @@ public final class DBTasks {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
if (FlattrUtils.hasToken()) {
Log.d(TAG, "Flattring all pending things.");
new FlattrClickWorker(context).executeAsync(); // flattr pending things
Log.d(TAG, "Fetching flattr status.");
new FlattrStatusFetcher(context).start();
}
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
GpodnetSyncService.sendSyncIntent(context);
}
@ -771,37 +760,4 @@ public final class DBTasks {
this.result = result;
}
}
/**
* Adds the given FeedItem to the flattr queue if the user is logged in. Otherwise, a dialog
* will be opened that lets the user go either to the login screen or the website of the flattr thing.
*
* @param context
* @param item
*/
public static void flattrItemIfLoggedIn(Context context, FeedItem item) {
if (FlattrUtils.hasToken()) {
item.getFlattrStatus().setFlattrQueue();
DBWriter.setFlattredStatus(context, item, true);
} else {
FlattrUtils.showNoTokenDialogOrRedirect(context, item.getPaymentLink());
}
}
/**
* Adds the given Feed to the flattr queue if the user is logged in. Otherwise, a dialog
* will be opened that lets the user go either to the login screen or the website of the flattr thing.
*
* @param context
* @param feed
*/
public static void flattrFeedIfLoggedIn(Context context, Feed feed) {
if (FlattrUtils.hasToken()) {
feed.getFlattrStatus().setFlattrQueue();
DBWriter.setFlattredStatus(context, feed, true);
} else {
FlattrUtils.showNoTokenDialogOrRedirect(context, feed.getPaymentLink());
}
}
}

View File

@ -76,10 +76,10 @@ class DBUpgrader {
}
if (oldVersion <= 10) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ " ADD COLUMN flattr_status"
+ " INTEGER");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ " ADD COLUMN flattr_status"
+ " INTEGER");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_PLAYED_DURATION

View File

@ -9,7 +9,6 @@ import android.support.annotation.NonNull;
import android.util.Log;
import org.greenrobot.eventbus.EventBus;
import org.shredzone.flattr4j.model.Flattr;
import java.io.File;
import java.io.UnsupportedEncodingException;
@ -26,7 +25,6 @@ import java.util.concurrent.Future;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.MessageEvent;
@ -47,9 +45,6 @@ import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.Permutor;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
import de.danoeh.antennapod.core.util.flattr.SimpleFlattrThing;
/**
* Provides methods for writing data to AntennaPod's database.
@ -685,7 +680,7 @@ public class DBWriter {
*
* @param feedId ID of the Feed.
*/
public static Future<?> markFeedSeen(final long feedId) {
public static Future<?> removeFeedNewFlag(final long feedId) {
return dbExec.submit(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@ -729,7 +724,7 @@ public class DBWriter {
/**
* Sets the 'read'-attribute of all NEW FeedItems to UNPLAYED.
*/
public static Future<?> markNewItemsSeen() {
public static Future<?> removeAllNewFlags() {
return dbExec.submit(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@ -854,44 +849,6 @@ public class DBWriter {
return -1;
}
/**
* Saves the FlattrStatus of a FeedItem object in the database.
*
* @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved
*/
private static Future<?> setFeedItemFlattrStatus(final Context context,
final FeedItem item,
final boolean startFlattrClickWorker) {
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItemFlattrStatus(item);
adapter.close();
if (startFlattrClickWorker) {
new FlattrClickWorker(context).executeAsync();
}
});
}
/**
* Saves the FlattrStatus of a Feed object in the database.
*
* @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved
*/
private static Future<?> setFeedFlattrStatus(final Context context,
final Feed feed,
final boolean startFlattrClickWorker) {
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedFlattrStatus(feed);
adapter.close();
if (startFlattrClickWorker) {
new FlattrClickWorker(context).executeAsync();
}
});
}
/**
* Saves if a feed's last update failed
*
@ -917,77 +874,6 @@ public class DBWriter {
});
}
/**
* format an url for querying the database
* (postfix a / and apply percent-encoding)
*/
private static String formatURIForQuery(String uri) {
try {
return URLEncoder.encode(uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
return "";
}
}
/**
* Set flattr status of the passed thing (either a FeedItem or a Feed)
*
* @param context
* @param thing
* @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved
* @return
*/
public static Future<?> setFlattredStatus(Context context, FlattrThing thing, boolean startFlattrClickWorker) {
// must propagate this to back db
if (thing instanceof FeedItem) {
return setFeedItemFlattrStatus(context, (FeedItem) thing, startFlattrClickWorker);
} else if (thing instanceof Feed) {
return setFeedFlattrStatus(context, (Feed) thing, startFlattrClickWorker);
} else if (thing instanceof SimpleFlattrThing) {
// SimpleFlattrThings are generated on the fly and do not have DB backing
} else {
Log.e(TAG, "flattrQueue processing - thing is neither FeedItem nor Feed nor SimpleFlattrThing");
}
return null;
}
/**
* Reset flattr status to unflattrd for all items
*/
public static Future<?> clearAllFlattrStatus() {
Log.d(TAG, "clearAllFlattrStatus()");
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.clearAllFlattrStatus();
adapter.close();
});
}
/**
* Set flattr status of the feeds/feeditems in flattrList to flattred at the given timestamp,
* where the information has been retrieved from the flattr API
*/
public static Future<?> setFlattredStatus(final List<Flattr> flattrList) {
Log.d(TAG, "setFlattredStatus to status retrieved from flattr api running with " + flattrList.size() + " items");
// clear flattr status in db
clearAllFlattrStatus();
// submit list with flattred things having normalized URLs to db
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
for (Flattr flattr : flattrList) {
adapter.setItemFlattrStatus(formatURIForQuery(flattr.getThing().getUrl()), new FlattrStatus(flattr.getCreated().getTime()));
}
adapter.close();
});
}
/**
* Sort the FeedItems in the queue with the given Permutor.
*

View File

@ -38,7 +38,6 @@ import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import org.greenrobot.eventbus.EventBus;
// TODO Remove media column from feeditem table
@ -96,7 +95,6 @@ public class PodDBAdapter {
public static final String KEY_HAS_CHAPTERS = "has_simple_chapters";
public static final String KEY_TYPE = "type";
public static final String KEY_ITEM_IDENTIFIER = "item_identifier";
public static final String KEY_FLATTR_STATUS = "flattr_status";
public static final String KEY_FEED_IDENTIFIER = "feed_identifier";
public static final String KEY_REASON_DETAILED = "reason_detailed";
public static final String KEY_DOWNLOADSTATUS_TITLE = "title";
@ -139,7 +137,6 @@ public class PodDBAdapter {
+ KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
+ " TEXT," + KEY_IMAGE_URL + " TEXT," + KEY_TYPE + " TEXT,"
+ KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1,"
+ KEY_FLATTR_STATUS + " INTEGER,"
+ KEY_USERNAME + " TEXT,"
+ KEY_PASSWORD + " TEXT,"
+ KEY_INCLUDE_FILTER + " TEXT DEFAULT '',"
@ -158,7 +155,6 @@ public class PodDBAdapter {
+ KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT,"
+ KEY_FLATTR_STATUS + " INTEGER,"
+ KEY_IMAGE_URL + " TEXT,"
+ KEY_AUTO_DOWNLOAD + " INTEGER)";
@ -239,7 +235,6 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD,
TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED,
TABLE_NAME_FEEDS + "." + KEY_FLATTR_STATUS,
TABLE_NAME_FEEDS + "." + KEY_IS_PAGED,
TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK,
TABLE_NAME_FEEDS + "." + KEY_USERNAME,
@ -266,7 +261,6 @@ public class PodDBAdapter {
TABLE_NAME_FEED_ITEMS + "." + KEY_FEED,
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS,
TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL,
TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD
};
@ -382,9 +376,6 @@ public class PodDBAdapter {
values.put(KEY_TYPE, feed.getType());
values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
Log.d(TAG, "Setting feed with flattr status " + feed.getTitle() + ": " + feed.getFlattrStatus().toLong());
values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong());
values.put(KEY_IS_PAGED, feed.isPaged());
values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink());
if (feed.getItemFilter() != null && feed.getItemFilter().getValues().length > 0) {
@ -515,31 +506,6 @@ public class PodDBAdapter {
}
}
/**
* Update the flattr status of a feed
*/
public void setFeedFlattrStatus(Feed feed) {
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong());
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())});
}
/**
* Get all feeds in the flattr queue.
*/
public Cursor getFeedsInFlattrQueueCursor() {
return db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_FLATTR_STATUS + "=?",
new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)}, null, null, null);
}
/**
* Get all feed items in the flattr queue.
*/
public Cursor getFeedItemsInFlattrQueueCursor() {
return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FLATTR_STATUS + "=?",
new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)}, null, null, null);
}
/**
* Updates the download URL of a Feed.
*/
@ -577,61 +543,6 @@ public class PodDBAdapter {
return result;
}
/**
* Update the flattr status of a FeedItem
*/
public void setFeedItemFlattrStatus(FeedItem feedItem) {
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, feedItem.getFlattrStatus().toLong());
db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(feedItem.getId())});
}
/**
* Update the flattr status of a feed or feed item specified by its payment link
* and the new flattr status to use
*/
public void setItemFlattrStatus(String url, FlattrStatus status) {
//Log.d(TAG, "setItemFlattrStatus(" + url + ") = " + status.toString());
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, status.toLong());
// regexps in sqlite would be neat!
String[] query_urls = new String[]{
"*" + url + "&*",
"*" + url + "%2F&*",
"*" + url + "",
"*" + url + "%2F"
};
if (db.update(TABLE_NAME_FEEDS, values,
KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls
) > 0) {
Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in Feeds table");
return;
}
if (db.update(TABLE_NAME_FEED_ITEMS, values,
KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls
) > 0) {
Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in FeedsItems table");
}
}
/**
* Reset flattr status to unflattrd for all items
*/
public void clearAllFlattrStatus() {
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, 0);
db.update(TABLE_NAME_FEEDS, values, null, null);
db.update(TABLE_NAME_FEED_ITEMS, values, null, null);
}
/**
* Inserts or updates a feeditem entry
*
@ -665,7 +576,6 @@ public class PodDBAdapter {
}
values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters());
values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong());
values.put(KEY_AUTO_DOWNLOAD, item.getAutoDownload());
values.put(KEY_IMAGE_URL, item.getImageUrl());

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