diff options
Diffstat (limited to 'app/src/main/java')
83 files changed, 7424 insertions, 5119 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/AppConfig.java b/app/src/main/java/de/danoeh/antennapod/AppConfig.java deleted file mode 100644 index 24f13d4a3..000000000 --- a/app/src/main/java/de/danoeh/antennapod/AppConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.danoeh.antennapod; - -public final class AppConfig { - /** Should be used when setting User-Agent header for HTTP-requests. */ - public final static String USER_AGENT = "AntennaPod/0.9.9.4"; - -} diff --git a/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java b/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java new file mode 100644 index 000000000..ea2166674 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java @@ -0,0 +1,52 @@ +package de.danoeh.antennapod; + +import android.os.Build; +import android.util.Log; + +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import de.danoeh.antennapod.core.preferences.UserPreferences; + +public class CrashReportWriter implements Thread.UncaughtExceptionHandler { + + private static final String TAG = "CrashReportWriter"; + + private final Thread.UncaughtExceptionHandler defaultHandler; + + public CrashReportWriter() { + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + public static File getFile() { + return new File(UserPreferences.getDataFolder(null), "crash-report.log"); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + File path = getFile(); + PrintWriter out = null; + try { + out = new PrintWriter(new FileWriter(path)); + out.println("[ Environment ]"); + out.println("Android version: " + Build.VERSION.RELEASE); + out.println("OS version: " + System.getProperty("os.version")); + out.println("AntennaPod version: " + BuildConfig.VERSION_NAME); + out.println("Model: " + Build.MODEL); + out.println("Device: " + Build.DEVICE); + out.println("Product: " + Build.PRODUCT); + out.println(); + out.println("[ StackTrace ]"); + ex.printStackTrace(out); + } catch (IOException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + IOUtils.closeQuietly(out); + } + defaultHandler.uncaughtException(thread, ex); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java index 451094909..c1d4bc4fd 100644 --- a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java +++ b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java @@ -1,12 +1,18 @@ package de.danoeh.antennapod; import android.app.Application; -import android.content.res.Configuration; +import android.os.Build; +import android.os.StrictMode; + +import com.joanzapata.iconify.Iconify; +import com.joanzapata.iconify.fonts.FontAwesomeModule; +import com.joanzapata.iconify.fonts.MaterialModule; -import de.danoeh.antennapod.core.asynctask.PicassoProvider; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.spa.SPAUtil; /** Main application class. */ @@ -21,10 +27,6 @@ public class PodcastApp extends Application { } } - private static final String TAG = "PodcastApp"; - - private static float LOGICAL_DENSITY; - private static PodcastApp singleton; public static PodcastApp getInstance() { @@ -34,24 +36,36 @@ public class PodcastApp extends Application { @Override public void onCreate() { super.onCreate(); + + Thread.setDefaultUncaughtExceptionHandler(new CrashReportWriter()); + + if(BuildConfig.DEBUG) { + StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder() + .detectLeakedSqlLiteObjects() + .penaltyLog() + .penaltyDropBox(); + if (Build.VERSION.SDK_INT >= 11) { + builder.detectActivityLeaks(); + builder.detectLeakedClosableObjects(); + } + if(Build.VERSION.SDK_INT >= 16) { + builder.detectLeakedRegistrationObjects(); + } + StrictMode.setVmPolicy(builder.build()); + } + singleton = this; - LOGICAL_DENSITY = getResources().getDisplayMetrics().density; - PicassoProvider.setupPicassoInstance(this); - UserPreferences.createInstance(this); - PlaybackPreferences.createInstance(this); + PodDBAdapter.init(this); + UpdateManager.init(this); + UserPreferences.init(this); + PlaybackPreferences.init(this); + NetworkUtils.init(this); EventDistributor.getInstance(); + Iconify.with(new FontAwesomeModule()); + Iconify.with(new MaterialModule()); SPAUtil.sendSPAppsQueryFeedsIntent(this); - } - - public static float getLogicalDensity() { - return LOGICAL_DENSITY; - } - - public boolean isLargeScreen() { - return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE - || (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE; + } - } } diff --git a/app/src/main/java/de/danoeh/antennapod/UpdateManager.java b/app/src/main/java/de/danoeh/antennapod/UpdateManager.java new file mode 100644 index 000000000..b1d7fffc8 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/UpdateManager.java @@ -0,0 +1,88 @@ +package de.danoeh.antennapod; + + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +import java.io.File; +import java.util.List; + +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedImage; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; + +/* + * This class's job is do perform maintenance tasks whenever the app has been updated + */ +public class UpdateManager { + + public 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(); + } + } + + public static int getStoredVersionCode() { + return prefs.getInt(KEY_VERSION_CODE, -1); + } + + public static void setCurrentVersionCode() { + prefs.edit().putInt(KEY_VERSION_CODE, currentVersionCode).apply(); + } + + private static void onUpgrade(final int oldVersionCode, final int newVersionCode) { + if(oldVersionCode < 1030099) { + // delete the now obsolete image cache + // from now on, Glide will handle caching images + new Thread() { + public void run() { + List<Feed> feeds = DBReader.getFeedList(); + for (Feed podcast : feeds) { + List<FeedItem> episodes = DBReader.getFeedItemList(podcast); + for (FeedItem episode : episodes) { + FeedImage image = episode.getImage(); + if (image != null && image.isDownloaded() && image.getFile_url() != null) { + File imageFile = new File(image.getFile_url()); + if (imageFile.exists()) { + imageFile.delete(); + } + image.setFile_url(null); // calls setDownloaded(false) + DBWriter.setFeedImage(image); + } + } + } + } + }.start(); + } + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java index 811628ebf..c835f8073 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java @@ -1,43 +1,165 @@ package de.danoeh.antennapod.activity; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.os.Build; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.LinearLayout; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Displays the 'about' screen */ public class AboutActivity extends ActionBarActivity { + private static final String TAG = AboutActivity.class.getSimpleName(); + private WebView webview; private LinearLayout webviewContainer; + private int depth = 0; + + private Subscription subscription; @Override protected void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); - getSupportActionBar().hide(); + getSupportActionBar().setDisplayShowHomeEnabled(true); setContentView(R.layout.about); webviewContainer = (LinearLayout) findViewById(R.id.webvContainer); webview = (WebView) findViewById(R.id.webvAbout); + webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); + if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { + if (Build.VERSION.SDK_INT >= 11 + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + webview.setBackgroundColor(Color.TRANSPARENT); + } webview.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - view.loadUrl(url); - return false; + if(url.startsWith("http")) { + depth++; + return false; + } else { + url = url.replace("file:///android_asset/", ""); + loadAsset(url); + return true; + } } }); - webview.loadUrl("file:///android_asset/about.html"); + loadAsset("about.html"); + } + + private void loadAsset(String filename) { + subscription = Observable.create(new Observable.OnSubscribe<String>() { + @Override + public void call(Subscriber<? super String> subscriber) { + InputStream input = null; + try { + TypedArray res = AboutActivity.this.getTheme().obtainStyledAttributes( + new int[] { android.R.attr.textColorPrimary }); + int colorResource = res.getColor(0, 0); + String colorString = String.format("#%06X", 0xFFFFFF & colorResource); + res.recycle(); + input = getAssets().open(filename); + String webViewData = IOUtils.toString(input, Charset.defaultCharset()); + if(false == webViewData.startsWith("<!DOCTYPE html>")) { + //webViewData = webViewData.replace("\n\n", "</p><p>"); + webViewData = webViewData.replace("%", "%"); + webViewData = + "<!DOCTYPE html>" + + "<html>" + + "<head>" + + " <meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">" + + " <style type=\"text/css\">" + + " @font-face {" + + " font-family: 'Roboto-Light';" + + " src: url('file:///android_asset/Roboto-Light.ttf');" + + " }" + + " * {" + + " color: %s;" + + " font-family: roboto-Light;" + + " font-size: 8pt;" + + " }" + + " </style>" + + "</head><body><p>" + webViewData + "</p></body></html>"; + webViewData = webViewData.replace("\n", "<br/>"); + depth++; + } else { + depth = 0; + } + webViewData = String.format(webViewData, colorString); + subscriber.onNext(webViewData); + } catch (IOException e) { + subscriber.onError(e); + } finally { + IOUtils.closeQuietly(input); + } + subscriber.onCompleted(); + } + }) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(webviewData -> { + webview.loadDataWithBaseURL("file:///android_asset/", webviewData, "text/html", + "utf-8", "about:blank"); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } + + @Override + public void onBackPressed() { + Log.d(TAG, "depth: " + depth); + if(depth == 1) { + loadAsset("about.html"); + } else if(depth > 1) { + webview.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } else { + return super.onOptionsItemSelected(item); + } } @Override protected void onDestroy() { super.onDestroy(); + if(subscription != null) { + subscription.unsubscribe(); + } if (webviewContainer != null && webview != null) { webviewContainer.removeAllViews(); webview.destroy(); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java index b59f6ba35..12bae2f51 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -1,165 +1,139 @@ package de.danoeh.antennapod.activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.os.AsyncTask; -import android.os.Bundle; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.ListFragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.ViewPager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.widget.Toolbar; +import android.text.TextUtils; import android.util.Log; +import android.util.TypedValue; +import android.view.ContextMenu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ImageButton; import android.widget.ListView; -import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.viewpagerindicator.CirclePageIndicator; -import org.apache.commons.lang3.StringUtils; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.ChapterListAdapter; +import de.danoeh.antennapod.adapter.ChaptersListAdapter; import de.danoeh.antennapod.adapter.NavListAdapter; -import de.danoeh.antennapod.core.feed.Chapter; +import de.danoeh.antennapod.core.asynctask.FeedRemover; +import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.feed.SimpleChapter; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; -import de.danoeh.antennapod.dialog.VariableSpeedDialog; +import de.danoeh.antennapod.fragment.AddFeedFragment; +import de.danoeh.antennapod.fragment.ChaptersFragment; import de.danoeh.antennapod.fragment.CoverFragment; +import de.danoeh.antennapod.fragment.DownloadsFragment; +import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.ItemDescriptionFragment; +import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; +import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import de.danoeh.antennapod.preferences.PreferenceController; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Activity for playing audio files. */ -public class AudioplayerActivity extends MediaplayerActivity implements ItemDescriptionFragment.ItemDescriptionFragmentCallback, - NavDrawerActivity { +public class AudioplayerActivity extends MediaplayerActivity implements NavDrawerActivity { + private static final int POS_COVER = 0; private static final int POS_DESCR = 1; private static final int POS_CHAPTERS = 2; private static final int NUM_CONTENT_FRAGMENTS = 3; - private static final int POS_NONE = -1; final String TAG = "AudioplayerActivity"; private static final String PREFS = "AudioPlayerActivityPreferences"; private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition"; - private static final String PREF_PLAYABLE_ID = "playableId"; + + public static final String[] NAV_DRAWER_TAGS = { + QueueFragment.TAG, + EpisodesFragment.TAG, + DownloadsFragment.TAG, + PlaybackHistoryFragment.TAG, + AddFeedFragment.TAG + }; + + private AtomicBoolean isSetup = new AtomicBoolean(false); private DrawerLayout drawerLayout; private NavListAdapter navAdapter; private ListView navList; private View navDrawer; private ActionBarDrawerToggle drawerToggle; + private int mPosition = -1; - private Fragment[] detachedFragments; - - private CoverFragment coverFragment; - private ItemDescriptionFragment descriptionFragment; - private ListFragment chapterFragment; - - private Fragment currentlyShownFragment; - private int currentlyShownPosition = -1; - private int lastShownPosition = POS_NONE; - /** - * Used if onResume was called without loadMediaInfo. - */ - private int savedPosition = -1; + private Playable media; + private ViewPager mPager; + private AudioplayerPagerAdapter mPagerAdapter; - private TextView txtvTitle; - private Button butPlaybackSpeed; - private ImageButton butNavChaptersShownotes; - private ImageButton butShowCover; - - private void resetFragmentView() { - FragmentTransaction fT = getSupportFragmentManager().beginTransaction(); - - if (coverFragment != null) { - Log.d(TAG, "Removing cover fragment"); - fT.remove(coverFragment); - } - if (descriptionFragment != null) { - Log.d(TAG, "Removing description fragment"); - fT.remove(descriptionFragment); - } - if (chapterFragment != null) { - Log.d(TAG, "Removing chapter fragment"); - fT.remove(chapterFragment); - } - if (currentlyShownFragment != null) { - Log.d(TAG, "Removing currently shown fragment"); - fT.remove(currentlyShownFragment); - } - for (int i = 0; i < detachedFragments.length; i++) { - Fragment f = detachedFragments[i]; - if (f != null) { - Log.d(TAG, "Removing detached fragment"); - fT.remove(f); - } - } - fT.commit(); - currentlyShownFragment = null; - coverFragment = null; - descriptionFragment = null; - chapterFragment = null; - currentlyShownPosition = -1; - detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS]; - } + private Subscription subscription; @Override protected void onStop() { super.onStop(); Log.d(TAG, "onStop()"); - cancelLoadTask(); + if(subscription != null) { + subscription.unsubscribe(); + } EventDistributor.getInstance().unregister(contentUpdate); + saveCurrentFragment(); } @Override - protected void chooseTheme() { - setTheme(UserPreferences.getNoTitleTheme()); + public void onDestroy() { + super.onDestroy(); + // don't risk creating memory leaks + navAdapter = null; + drawerToggle = null; + mPager = null; + mPagerAdapter = null; } @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS]; + protected void chooseTheme() { + setTheme(UserPreferences.getNoTitleTheme()); } - private void savePreferences() { + private void saveCurrentFragment() { + if(mPager == null) { + return; + } Log.d(TAG, "Saving preferences"); SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - if (currentlyShownPosition >= 0 && controller != null - && controller.getMedia() != null) { - editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, - currentlyShownPosition); - editor.putString(PREF_PLAYABLE_ID, controller.getMedia() - .getIdentifier().toString()); - } else { - editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1); - editor.putString(PREF_PLAYABLE_ID, ""); - } - editor.commit(); - - savedPosition = currentlyShownPosition; + prefs.edit() + .putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, mPager.getCurrentItem()) + .commit(); } @Override @@ -168,59 +142,17 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc drawerToggle.onConfigurationChanged(newConfig); } - @Override - protected void onSaveInstanceState(Bundle outState) { - // super.onSaveInstanceState(outState); would cause crash - Log.d(TAG, "onSaveInstanceState"); - } - - @Override - protected void onPause() { - savePreferences(); - resetFragmentView(); - super.onPause(); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - restoreFromPreferences(); - } - - /** - * Tries to restore the selected fragment position from the Activity's - * preferences. - * - * @return true if restoreFromPrefernces changed the activity's state - */ - private boolean restoreFromPreferences() { + private void loadLastFragment() { Log.d(TAG, "Restoring instance state"); SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); - int savedPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, - -1); - String playableId = prefs.getString(PREF_PLAYABLE_ID, ""); - - if (savedPosition != -1 - && controller != null - && controller.getMedia() != null - && controller.getMedia().getIdentifier().toString() - .equals(playableId)) { - switchToFragment(savedPosition); - return true; - } else if (controller == null || controller.getMedia() == null) { - Log.d(TAG, "Couldn't restore from preferences: controller or media was null"); - } else { - Log.d(TAG, "Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: " - + savedPosition + ", id: " + playableId); - - } - return false; + int lastPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1); + mPager.setCurrentItem(lastPosition); } @Override protected void onResume() { super.onResume(); - if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { + if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { Intent intent = getIntent(); Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath()); ExternalMedia media = new ExternalMedia(intent.getData().getPath(), @@ -234,8 +166,9 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc true); startService(launchIntent); } - if (savedPosition != -1) { - switchToFragment(savedPosition); + if(mPagerAdapter != null && controller != null && controller.getMedia() != media) { + media = controller.getMedia(); + mPagerAdapter.onMediaChanged(media); } EventDistributor.getInstance().register(contentUpdate); @@ -266,150 +199,28 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc @Override protected void clearStatusMsg() { // TODO Hide progress bar here - - } - - /** - * Changes the currently displayed fragment. - * - * @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS - */ - private void switchToFragment(int pos) { - Log.d(TAG, "Switching contentView to position " + pos); - if (currentlyShownPosition != pos && controller != null) { - Playable media = controller.getMedia(); - if (media != null) { - FragmentTransaction ft = getSupportFragmentManager() - .beginTransaction(); - if (currentlyShownFragment != null) { - detachedFragments[currentlyShownPosition] = currentlyShownFragment; - ft.detach(currentlyShownFragment); - } - switch (pos) { - case POS_COVER: - if (coverFragment == null) { - Log.i(TAG, "Using new coverfragment"); - coverFragment = CoverFragment.newInstance(media); - } - currentlyShownFragment = coverFragment; - break; - case POS_DESCR: - if (descriptionFragment == null) { - descriptionFragment = ItemDescriptionFragment - .newInstance(media, true, true); - } - currentlyShownFragment = descriptionFragment; - break; - case POS_CHAPTERS: - if (chapterFragment == null) { - chapterFragment = new ListFragment() { - - @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); - } - }; - chapterFragment.setListAdapter(new ChapterListAdapter( - AudioplayerActivity.this, 0, media - .getChapters(), media, new ChapterListAdapter.Callback() { - @Override - public void onPlayChapterButtonClicked(int position) { - Chapter chapter = (Chapter) - chapterFragment.getListAdapter().getItem(position); - controller.seekToChapter(chapter); - } - } - )); - } - currentlyShownFragment = chapterFragment; - break; - } - if (currentlyShownFragment != null) { - lastShownPosition = currentlyShownPosition; - currentlyShownPosition = pos; - if (detachedFragments[pos] != null) { - Log.d(TAG, "Reattaching fragment at position " + pos); - ft.attach(detachedFragments[pos]); - } else { - ft.add(R.id.contentView, currentlyShownFragment); - } - ft.disallowAddToBackStack(); - ft.commit(); - updateNavButtonDrawable(); - } - } - } - } - - /** - * Switches to the fragment that was displayed before the current one or the description fragment - * if no fragment was previously displayed. - */ - public void switchToLastFragment() { - if (lastShownPosition != POS_NONE) { - switchToFragment(lastShownPosition); - } else { - switchToFragment(POS_DESCR); - } } - private void updateNavButtonDrawable() { - - final int[] buttonTexts = new int[]{R.string.show_shownotes_label, - R.string.show_chapters_label}; - - final TypedArray drawables = obtainStyledAttributes(new int[]{ - R.attr.navigation_shownotes, R.attr.navigation_chapters}); - final Playable media = controller.getMedia(); - if (butNavChaptersShownotes != null && butShowCover != null && media != null) { - - butNavChaptersShownotes.setTag(R.id.imageloader_key, null); - setNavButtonVisibility(); - switch (currentlyShownPosition) { - case POS_COVER: - butShowCover.setVisibility(View.GONE); - if (lastShownPosition == POS_CHAPTERS) { - butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(1)); - butNavChaptersShownotes.setContentDescription(getString(buttonTexts[1])); - } else { - butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(0)); - butNavChaptersShownotes.setContentDescription(getString(buttonTexts[0])); - } - break; - case POS_DESCR: - butShowCover.setVisibility(View.VISIBLE); - butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(1)); - butNavChaptersShownotes.setContentDescription(getString(buttonTexts[1])); - break; - case POS_CHAPTERS: - butShowCover.setVisibility(View.VISIBLE); - butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(0)); - butNavChaptersShownotes.setContentDescription(getString(buttonTexts[0])); - break; - } - } - drawables.recycle(); - } @Override protected void setupGUI() { + if(isSetup.getAndSet(true)) { + return; + } super.setupGUI(); - resetFragmentView(); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(""); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + findViewById(R.id.shadow).setVisibility(View.GONE); + AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appBar); + float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); + appBarLayout.setElevation(px); + } drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); navList = (ListView) findViewById(R.id.nav_list); navDrawer = findViewById(R.id.nav_layout); - butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed); - butNavChaptersShownotes = (ImageButton) findViewById(R.id.butNavChaptersShownotes); - butShowCover = (ImageButton) findViewById(R.id.butCover); - txtvTitle = (TextView) findViewById(R.id.txtvTitle); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close); drawerToggle.setDrawerIndicatorEnabled(false); @@ -417,119 +228,40 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc navAdapter = new NavListAdapter(itemAccess, this); navList.setAdapter(navAdapter); - navList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - int viewType = parent.getAdapter().getItemViewType(position); - if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) { - Intent intent = new Intent(AudioplayerActivity.this, MainActivity.class); - intent.putExtra(MainActivity.EXTRA_NAV_TYPE, viewType); - intent.putExtra(MainActivity.EXTRA_NAV_INDEX, position); - startActivity(intent); - } - drawerLayout.closeDrawer(navDrawer); - } - }); - drawerToggle.syncState(); - - findViewById(R.id.nav_settings).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - drawerLayout.closeDrawer(navDrawer); - startActivity(new Intent(AudioplayerActivity.this, PreferenceController.getPreferenceActivity())); - } - }); - - butNavChaptersShownotes.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (currentlyShownPosition == POS_CHAPTERS) { - switchToFragment(POS_DESCR); - } else if (currentlyShownPosition == POS_DESCR) { - switchToFragment(POS_CHAPTERS); - } else if (currentlyShownPosition == POS_COVER) { - switchToLastFragment(); - } - } - }); - - butShowCover.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - switchToFragment(POS_COVER); + navList.setOnItemClickListener((parent, view, position, id) -> { + int viewType = parent.getAdapter().getItemViewType(position); + if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) { + Intent intent = new Intent(AudioplayerActivity.this, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_NAV_TYPE, viewType); + intent.putExtra(MainActivity.EXTRA_NAV_INDEX, position); + startActivity(intent); } + drawerLayout.closeDrawer(navDrawer); }); - - butPlaybackSpeed.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (controller != null && controller.canSetPlaybackSpeed()) { - String[] availableSpeeds = UserPreferences - .getPlaybackSpeedArray(); - String currentSpeed = UserPreferences.getPlaybackSpeed(); - - // Provide initial value in case the speed list has changed - // out from under us - // and our current speed isn't in the new list - String newSpeed; - if (availableSpeeds.length > 0) { - newSpeed = availableSpeeds[0]; - } else { - newSpeed = "1.0"; - } - - for (int i = 0; i < availableSpeeds.length; i++) { - if (availableSpeeds[i].equals(currentSpeed)) { - if (i == availableSpeeds.length - 1) { - newSpeed = availableSpeeds[0]; - } else { - newSpeed = availableSpeeds[i + 1]; - } - break; - } - } - UserPreferences.setPlaybackSpeed(newSpeed); - controller.setPlaybackSpeed(Float.parseFloat(newSpeed)); - } - } - }); - - butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - VariableSpeedDialog.showDialog(AudioplayerActivity.this); + navList.setOnItemLongClickListener((parent, view, position, id) -> { + if (position < navAdapter.getTags().size()) { + showDrawerPreferencesDialog(); return true; + } else { + mPosition = position; + return false; } }); - } - - private void setNavButtonVisibility() { - if (butNavChaptersShownotes != null) { - if (controller != null) { - Playable media = controller.getMedia(); - if (media != null) { - if (media.getChapters() != null || currentlyShownPosition == POS_COVER) { - butNavChaptersShownotes.setVisibility(View.VISIBLE); - return; - } - } - } - butNavChaptersShownotes.setVisibility(View.GONE); - } - - } + registerForContextMenu(navList); + drawerToggle.syncState(); - @Override - protected void onPlaybackSpeedChange() { - super.onPlaybackSpeedChange(); - updateButPlaybackSpeed(); - } + findViewById(R.id.nav_settings).setOnClickListener(v -> { + drawerLayout.closeDrawer(navDrawer); + startActivity(new Intent(AudioplayerActivity.this, PreferenceController.getPreferenceActivity())); + }); - private void updateButPlaybackSpeed() { - if (controller != null && controller.canSetPlaybackSpeed()) { - butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed()); - } + mPager = (ViewPager) findViewById(R.id.pager); + mPagerAdapter = new AudioplayerPagerAdapter(getSupportFragmentManager()); + mPager.setAdapter(mPagerAdapter); + CirclePageIndicator pageIndicator = (CirclePageIndicator) findViewById(R.id.page_indicator); + pageIndicator.setViewPager(mPager); + loadLastFragment(); + mPager.onSaveInstanceState(); } @Override @@ -543,45 +275,20 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc if (!super.loadMediaInfo()) { return false; } - final Playable media = controller.getMedia(); - if (media == null) { - return false; - } - txtvTitle.setText(media.getEpisodeTitle()); - getSupportActionBar().setTitle(""); - Picasso.with(this) - .load(media.getImageUri()) - .fit() - .into(butShowCover); - - setNavButtonVisibility(); - - if (currentlyShownPosition == -1) { - if (!restoreFromPreferences()) { - switchToFragment(POS_COVER); - } + if(controller.getMedia() != media) { + media = controller.getMedia(); + mPagerAdapter.onMediaChanged(media); } - if (currentlyShownFragment instanceof AudioplayerContentFragment) { - ((AudioplayerContentFragment) currentlyShownFragment) - .onDataSetChanged(media); - } - - if (controller == null - || !controller.canSetPlaybackSpeed()) { - butPlaybackSpeed.setVisibility(View.GONE); - } else { - butPlaybackSpeed.setVisibility(View.VISIBLE); - } - - updateButPlaybackSpeed(); return true; } public void notifyMediaPositionChanged() { - if (chapterFragment != null) { - ArrayAdapter<SimpleChapter> adapter = (ArrayAdapter<SimpleChapter>) chapterFragment - .getListAdapter(); - adapter.notifyDataSetChanged(); + ChaptersFragment chaptersFragment = mPagerAdapter.getChaptersFragment(); + if(chaptersFragment != null) { + ChaptersListAdapter adapter = (ChaptersListAdapter) chaptersFragment.getListAdapter(); + if (adapter != null) { + adapter.notifyDataSetChanged(); + } } } @@ -605,7 +312,6 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc clearStatusMsg(); } - @Override public PlaybackController getPlaybackController() { return controller; } @@ -615,10 +321,6 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); } - public interface AudioplayerContentFragment { - public void onDataSetChanged(Playable media); - } - @Override protected int getContentViewResourceId() { return R.layout.audioplayer_activity; @@ -634,34 +336,137 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc } } - private DBReader.NavDrawerData navDrawerData; - private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask; + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + if(v.getId() != R.id.nav_list) { + return; + } + AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; + int position = adapterInfo.position; + if(position < navAdapter.getSubscriptionOffset()) { + return; + } + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.nav_feed_context, menu); + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + menu.setHeaderTitle(feed.getTitle()); + // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones! + } - private void loadData() { - loadTask = new AsyncTask<Void, Void, DBReader.NavDrawerData>() { - @Override - protected DBReader.NavDrawerData doInBackground(Void... params) { - return DBReader.getNavDrawerData(AudioplayerActivity.this); + @Override + public boolean onContextItemSelected(MenuItem item) { + final int position = mPosition; + mPosition = -1; // reset + if(position < 0) { + return false; + } + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + switch(item.getItemId()) { + case R.id.mark_all_seen_item: + DBWriter.markFeedSeen(feed.getId()); + return true; + case R.id.mark_all_read_item: + DBWriter.markFeedRead(feed.getId()); + return true; + case R.id.remove_item: + final FeedRemover remover = new FeedRemover(this, feed) { + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + } + }; + ConfirmationDialog conDialog = new ConfirmationDialog(this, + R.string.remove_feed_label, + R.string.feed_delete_confirmation_msg) { + @Override + public void onConfirmButtonPressed( + DialogInterface dialog) { + dialog.dismiss(); + if (controller != null) { + Playable playable = controller.getMedia(); + if (playable != null && playable instanceof FeedMedia) { + FeedMedia media = (FeedMedia) playable; + if (media.getItem().getFeed().getId() == feed.getId()) { + Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); + remover.skipOnCompletion = true; + if(controller.getStatus() == PlayerStatus.PLAYING) { + sendBroadcast(new Intent( + PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); + } + } + } + } + remover.executeAsync(); + } + }; + conDialog.createNewDialog().show(); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public void onBackPressed() { + if(isDrawerOpen()) { + drawerLayout.closeDrawer(navDrawer); + } else if (mPager.getCurrentItem() == 0) { + // If the user is currently looking at the first step, allow the system to handle the + // Back button. This calls finish() on this activity and pops the back stack. + super.onBackPressed(); + } else { + // Otherwise, select the previous step. + mPager.setCurrentItem(mPager.getCurrentItem() - 1); + } + } + + public void showDrawerPreferencesDialog() { + final List<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); + String[] navLabels = new String[NAV_DRAWER_TAGS.length]; + final boolean[] checked = new boolean[NAV_DRAWER_TAGS.length]; + for (int i = 0; i < NAV_DRAWER_TAGS.length; i++) { + String tag = NAV_DRAWER_TAGS[i]; + navLabels[i] = navAdapter.getLabel(tag); + if (!hiddenDrawerItems.contains(tag)) { + checked[i] = true; } + } - @Override - protected void onPostExecute(DBReader.NavDrawerData result) { - super.onPostExecute(result); - navDrawerData = result; - if (navAdapter != null) { - navAdapter.notifyDataSetChanged(); - } + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.drawer_preferences); + builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> { + if (isChecked) { + hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); + } else { + hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); } - }; - loadTask.execute(); + }); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); } - private void cancelLoadTask() { - if (loadTask != null) { - loadTask.cancel(true); - } + private DBReader.NavDrawerData navDrawerData; + + private void loadData() { + subscription = Observable.fromCallable(() -> DBReader.getNavDrawerData()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + navDrawerData = result; + if (navAdapter != null) { + navAdapter.notifyDataSetChanged(); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override @@ -685,7 +490,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc @Override public Feed getItem(int position) { - if (navDrawerData != null && position < navDrawerData.feeds.size()) { + if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { return navDrawerData.feeds.get(position); } else { return null; @@ -708,8 +513,75 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc } @Override - public int getNumberOfUnreadFeedItems(long feedId) { - return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0; + public int getNumberOfDownloadedItems() { + return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0; + } + + @Override + public int getFeedCounter(long feedId) { + return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; } }; + + public interface AudioplayerContentFragment { + void onMediaChanged(Playable media); + } + + private class AudioplayerPagerAdapter extends FragmentStatePagerAdapter { + + public AudioplayerPagerAdapter(FragmentManager fm) { + super(fm); + } + + private CoverFragment coverFragment; + private ItemDescriptionFragment itemDescriptionFragment; + private ChaptersFragment chaptersFragment; + + public void onMediaChanged(Playable media) { + if(coverFragment != null) { + coverFragment.onMediaChanged(media); + } + if(itemDescriptionFragment != null) { + itemDescriptionFragment.onMediaChanged(media); + } + if(chaptersFragment != null) { + chaptersFragment.onMediaChanged(media); + } + } + + @Nullable + public ChaptersFragment getChaptersFragment() { + return chaptersFragment; + } + + @Override + public Fragment getItem(int position) { + Log.d(TAG, "getItem(" + position + ")"); + switch (position) { + case POS_COVER: + if(coverFragment == null) { + coverFragment = CoverFragment.newInstance(media); + } + return coverFragment; + case POS_DESCR: + if(itemDescriptionFragment == null) { + itemDescriptionFragment = ItemDescriptionFragment.newInstance(media, true, true); + } + return itemDescriptionFragment; + case POS_CHAPTERS: + if(chaptersFragment == null) { + chaptersFragment = ChaptersFragment.newInstance(media, controller); + } + return chaptersFragment; + default: + return null; + } + } + + @Override + public int getCount() { + return NUM_CONTENT_FRAGMENTS; + } + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java deleted file mode 100644 index 287ae3568..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java +++ /dev/null @@ -1,249 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.NavUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.Spinner; -import android.widget.TextView; - -import com.squareup.picasso.Picasso; - -import org.apache.commons.lang3.StringUtils; -import org.jsoup.Jsoup; -import org.jsoup.examples.HtmlToPlainText; -import org.jsoup.nodes.Document; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; -import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.core.feed.EventDistributor; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; - -/** - * Default implementation of OnlineFeedViewActivity. Shows the downloaded feed's items with their descriptions, - * a subscribe button and a spinner for choosing alternate feed URLs. - */ -public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { - private static final String TAG = "DefaultOnlineFeedViewActivity"; - - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED | EventDistributor.FEED_LIST_UPDATE; - private volatile List<Feed> feeds; - private Feed feed; - private String selectedDownloadUrl; - - private Button subscribeButton; - - @Override - protected void onCreate(Bundle arg0) { - super.onCreate(arg0); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - Intent destIntent = new Intent(this, MainActivity.class); - if (NavUtils.shouldUpRecreateTask(this, destIntent)) { - startActivity(destIntent); - } else { - NavUtils.navigateUpFromSameTask(this); - } - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void loadData() { - super.loadData(); - feeds = DBReader.getFeedList(this); - } - - @Override - protected void beforeShowFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) { - super.beforeShowFeedInformation(feed, alternateFeedUrls); - - // remove HTML tags from descriptions - - if (BuildConfig.DEBUG) Log.d(TAG, "Removing HTML from shownotes"); - if (feed.getItems() != null) { - HtmlToPlainText formatter = new HtmlToPlainText(); - for (FeedItem item : feed.getItems()) { - if (item.getDescription() != null) { - Document description = Jsoup.parse(item.getDescription()); - item.setDescription(StringUtils.trim(formatter.getPlainText(description))); - } - } - } - } - - @Override - protected void showFeedInformation(final Feed feed, final Map<String, String> alternateFeedUrls) { - super.showFeedInformation(feed, alternateFeedUrls); - setContentView(R.layout.listview_activity); - - this.feed = feed; - this.selectedDownloadUrl = feed.getDownload_url(); - EventDistributor.getInstance().register(listener); - ListView listView = (ListView) findViewById(R.id.listview); - LayoutInflater inflater = (LayoutInflater) - getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View header = inflater.inflate(R.layout.onlinefeedview_header, listView, false); - listView.addHeaderView(header); - - listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems())); - - ImageView cover = (ImageView) header.findViewById(R.id.imgvCover); - TextView title = (TextView) header.findViewById(R.id.txtvTitle); - TextView author = (TextView) header.findViewById(R.id.txtvAuthor); - TextView description = (TextView) header.findViewById(R.id.txtvDescription); - Spinner spAlternateUrls = (Spinner) header.findViewById(R.id.spinnerAlternateUrls); - - subscribeButton = (Button) header.findViewById(R.id.butSubscribe); - - if (feed.getImage() != null && StringUtils.isNotBlank(feed.getImage().getDownload_url())) { - Picasso.with(this) - .load(feed.getImage().getDownload_url()) - .fit() - .into(cover); - } - - title.setText(feed.getTitle()); - author.setText(feed.getAuthor()); - description.setText(feed.getDescription()); - - subscribeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - Feed f = new Feed(selectedDownloadUrl, new Date(0), feed.getTitle()); - f.setPreferences(feed.getPreferences()); - DefaultOnlineFeedViewActivity.this.feed = f; - - DownloadRequester.getInstance().downloadFeed( - DefaultOnlineFeedViewActivity.this, - f); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(DefaultOnlineFeedViewActivity.this, - e.getMessage()); - } - setSubscribeButtonState(feed); - } - }); - - if (alternateFeedUrls.isEmpty()) { - spAlternateUrls.setVisibility(View.GONE); - } else { - spAlternateUrls.setVisibility(View.VISIBLE); - - final List<String> alternateUrlsList = new ArrayList<String>(); - final List<String> alternateUrlsTitleList = new ArrayList<String>(); - - alternateUrlsList.add(feed.getDownload_url()); - alternateUrlsTitleList.add(feed.getTitle()); - - - alternateUrlsList.addAll(alternateFeedUrls.keySet()); - for (String url : alternateFeedUrls.keySet()) { - alternateUrlsTitleList.add(alternateFeedUrls.get(url)); - } - ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, alternateUrlsTitleList); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spAlternateUrls.setAdapter(adapter); - spAlternateUrls.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - selectedDownloadUrl = alternateUrlsList.get(position); - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - - } - }); - - - } - setSubscribeButtonState(feed); - - } - - private boolean feedInFeedlist(Feed feed) { - if (feeds == null || feed == null) - return false; - for (Feed f : feeds) { - if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) { - return true; - } - } - return false; - } - - private void setSubscribeButtonState(Feed feed) { - if (subscribeButton != null && feed != null) { - if (DownloadRequester.getInstance().isDownloadingFile(feed.getDownload_url())) { - subscribeButton.setEnabled(false); - subscribeButton.setText(R.string.downloading_label); - } else if (feedInFeedlist(feed)) { - subscribeButton.setEnabled(false); - subscribeButton.setText(R.string.subscribed_label); - } else { - subscribeButton.setEnabled(true); - subscribeButton.setText(R.string.subscribe_label); - } - } - } - - EventDistributor.EventListener listener = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) { - new AsyncTask<Void, Void, List<Feed>>() { - @Override - protected List<Feed> doInBackground(Void... params) { - return DBReader.getFeedList(DefaultOnlineFeedViewActivity.this); - } - - @Override - protected void onPostExecute(List<Feed> feeds) { - super.onPostExecute(feeds); - DefaultOnlineFeedViewActivity.this.feeds = feeds; - setSubscribeButtonState(feed); - } - }.execute(); - } else if ((arg & EVENTS) != 0) { - setSubscribeButtonState(feed); - } - } - }; - - @Override - protected void onStop() { - super.onStop(); - EventDistributor.getInstance().unregister(listener); - } -} - diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java index 559fa0574..25dc64232 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java @@ -1,370 +1,336 @@ package de.danoeh.antennapod.activity; import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Environment; import android.os.FileObserver; import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.widget.*; -import android.widget.AdapterView.OnItemClickListener; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import de.danoeh.antennapod.BuildConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; + /** * Let's the user choose a directory on the storage device. The selected folder * will be sent back to the starting activity as an activity result. */ public class DirectoryChooserActivity extends ActionBarActivity { - private static final String TAG = "DirectoryChooserActivity"; - - private static final String CREATE_DIRECTORY_NAME = "AntennaPod"; - - public static final String RESULT_SELECTED_DIR = "selected_dir"; - public static final int RESULT_CODE_DIR_SELECTED = 1; - - private Button butConfirm; - private Button butCancel; - private ImageButton butNavUp; - private TextView txtvSelectedFolder; - private ListView listDirectories; - - private ArrayAdapter<String> listDirectoriesAdapter; - private ArrayList<String> filenames; - /** The directory that is currently being shown. */ - private File selectedDir; - private File[] filesInDir; - - private FileObserver fileObserver; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - setContentView(R.layout.directory_chooser); - butConfirm = (Button) findViewById(R.id.butConfirm); - butCancel = (Button) findViewById(R.id.butCancel); - butNavUp = (ImageButton) findViewById(R.id.butNavUp); - txtvSelectedFolder = (TextView) findViewById(R.id.txtvSelectedFolder); - listDirectories = (ListView) findViewById(R.id.directory_list); - - butConfirm.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (isValidFile(selectedDir)) { - if (selectedDir.list().length == 0) { - returnSelectedFolder(); - } else { - showNonEmptyDirectoryWarning(); - } - } - } - - private void showNonEmptyDirectoryWarning() { - AlertDialog.Builder adb = new AlertDialog.Builder( - DirectoryChooserActivity.this); - adb.setTitle(R.string.folder_not_empty_dialog_title); - adb.setMessage(R.string.folder_not_empty_dialog_msg); - adb.setNegativeButton(R.string.cancel_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - } - }); - adb.setPositiveButton(R.string.confirm_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - returnSelectedFolder(); - } - }); - adb.create().show(); - } - }); - - butCancel.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } - }); - - listDirectories.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView<?> adapter, View view, - int position, long id) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Selected index: " + position); - if (filesInDir != null && position >= 0 - && position < filesInDir.length) { - changeDirectory(filesInDir[position]); - } - } - }); - - butNavUp.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - File parent = null; - if (selectedDir != null - && (parent = selectedDir.getParentFile()) != null) { - changeDirectory(parent); - } - } - }); - - filenames = new ArrayList<String>(); - listDirectoriesAdapter = new ArrayAdapter<String>(this, - android.R.layout.simple_list_item_1, filenames); - listDirectories.setAdapter(listDirectoriesAdapter); - changeDirectory(Environment.getExternalStorageDirectory()); - } - - /** - * Finishes the activity and returns the selected folder as a result. The - * selected folder can also be null. - */ - private void returnSelectedFolder() { - if (selectedDir != null && BuildConfig.DEBUG) - Log.d(TAG, "Returning " + selectedDir.getAbsolutePath() - + " as result"); - Intent resultData = new Intent(); - if (selectedDir != null) { - resultData.putExtra(RESULT_SELECTED_DIR, - selectedDir.getAbsolutePath()); - } - setResult(RESULT_CODE_DIR_SELECTED, resultData); - finish(); - } - - @Override - protected void onPause() { - super.onPause(); - if (fileObserver != null) { - fileObserver.stopWatching(); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (fileObserver != null) { - fileObserver.startWatching(); - } - } - - /** - * Change the directory that is currently being displayed. - * - * @param dir - * The file the activity should switch to. This File must be - * non-null and a directory, otherwise the displayed directory - * will not be changed - */ - private void changeDirectory(File dir) { - if (dir != null && dir.isDirectory()) { - File[] contents = dir.listFiles(); - if (contents != null) { - int numDirectories = 0; - for (File f : contents) { - if (f.isDirectory()) { - numDirectories++; - } - } - filesInDir = new File[numDirectories]; - filenames.clear(); - for (int i = 0, counter = 0; i < numDirectories; counter++) { - if (contents[counter].isDirectory()) { - filesInDir[i] = contents[counter]; - filenames.add(contents[counter].getName()); - i++; - } - } - Arrays.sort(filesInDir); - Collections.sort(filenames); - selectedDir = dir; - txtvSelectedFolder.setText(dir.getAbsolutePath()); - listDirectoriesAdapter.notifyDataSetChanged(); - fileObserver = createFileObserver(dir.getAbsolutePath()); - fileObserver.startWatching(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Changed directory to " + dir.getAbsolutePath()); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Could not change folder: contents of dir were null"); - } - } else { - if (dir == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Could not change folder: dir was null"); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Could not change folder: dir is no directory"); - } - } - refreshButtonState(); - } - - /** - * Changes the state of the buttons depending on the currently selected file - * or folder. - */ - private void refreshButtonState() { - if (selectedDir != null) { - butConfirm.setEnabled(isValidFile(selectedDir)); - supportInvalidateOptionsMenu(); - } - } - - /** Refresh the contents of the directory that is currently shown. */ - private void refreshDirectory() { - if (selectedDir != null) { - changeDirectory(selectedDir); - } - } - - /** Sets up a FileObserver to watch the current directory. */ - private FileObserver createFileObserver(String path) { - return new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE - | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) { - - @Override - public void onEvent(int event, String path) { - if (BuildConfig.DEBUG) - Log.d(TAG, "FileObserver received event " + event); - runOnUiThread(new Runnable() { - - @Override - public void run() { - refreshDirectory(); - } - }); - } - }; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { + private static final String TAG = "DirectoryChooserActivit"; + + private static final String CREATE_DIRECTORY_NAME = "AntennaPod"; + + public static final String RESULT_SELECTED_DIR = "selected_dir"; + public static final int RESULT_CODE_DIR_SELECTED = 1; + + private Button butConfirm; + private Button butCancel; + private ImageButton butNavUp; + private TextView txtvSelectedFolder; + private ListView listDirectories; + + private ArrayAdapter<String> listDirectoriesAdapter; + private ArrayList<String> filenames; + /** The directory that is currently being shown. */ + private File selectedDir; + private File[] filesInDir; + + private FileObserver fileObserver; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + setContentView(R.layout.directory_chooser); + butConfirm = (Button) findViewById(R.id.butConfirm); + butCancel = (Button) findViewById(R.id.butCancel); + butNavUp = (ImageButton) findViewById(R.id.butNavUp); + txtvSelectedFolder = (TextView) findViewById(R.id.txtvSelectedFolder); + listDirectories = (ListView) findViewById(R.id.directory_list); + + butConfirm.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (isValidFile(selectedDir)) { + if (selectedDir.list().length == 0) { + returnSelectedFolder(); + } else { + showNonEmptyDirectoryWarning(); + } + } + } + + private void showNonEmptyDirectoryWarning() { + AlertDialog.Builder adb = new AlertDialog.Builder( + DirectoryChooserActivity.this); + adb.setTitle(R.string.folder_not_empty_dialog_title); + adb.setMessage(R.string.folder_not_empty_dialog_msg); + adb.setNegativeButton(R.string.cancel_label, + (dialog, which) -> { + dialog.dismiss(); + }); + adb.setPositiveButton(R.string.confirm_label, + (dialog, which) -> { + dialog.dismiss(); + returnSelectedFolder(); + }); + adb.create().show(); + } + }); + + butCancel.setOnClickListener(v -> { + setResult(Activity.RESULT_CANCELED); + finish(); + }); + + listDirectories.setOnItemClickListener((adapter, view, position, id) -> { + Log.d(TAG, "Selected index: " + position); + if (filesInDir != null && position >= 0 + && position < filesInDir.length) { + changeDirectory(filesInDir[position]); + } + }); + + butNavUp.setOnClickListener(v -> { + File parent = null; + if (selectedDir != null + && (parent = selectedDir.getParentFile()) != null) { + changeDirectory(parent); + } + }); + + filenames = new ArrayList<>(); + listDirectoriesAdapter = new ArrayAdapter<>(this, + android.R.layout.simple_list_item_1, filenames); + listDirectories.setAdapter(listDirectoriesAdapter); + changeDirectory(Environment.getExternalStorageDirectory()); + } + + /** + * Finishes the activity and returns the selected folder as a result. The + * selected folder can also be null. + */ + private void returnSelectedFolder() { + if (selectedDir != null && BuildConfig.DEBUG) + Log.d(TAG, "Returning " + selectedDir.getAbsolutePath() + + " as result"); + Intent resultData = new Intent(); + if (selectedDir != null) { + resultData.putExtra(RESULT_SELECTED_DIR, + selectedDir.getAbsolutePath()); + } + setResult(Activity.RESULT_OK, resultData); + finish(); + } + + @Override + protected void onPause() { + super.onPause(); + if (fileObserver != null) { + fileObserver.stopWatching(); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (fileObserver != null) { + fileObserver.startWatching(); + } + } + + @Override + public void onStop() { + super.onStop(); + listDirectoriesAdapter = null; + fileObserver = null; + } + + /** + * Change the directory that is currently being displayed. + * + * @param dir + * The file the activity should switch to. This File must be + * non-null and a directory, otherwise the displayed directory + * will not be changed + */ + private void changeDirectory(File dir) { + if (dir != null && dir.isDirectory()) { + File[] contents = dir.listFiles(); + if (contents != null) { + int numDirectories = 0; + for (File f : contents) { + if (f.isDirectory()) { + numDirectories++; + } + } + filesInDir = new File[numDirectories]; + filenames.clear(); + for (int i = 0, counter = 0; i < numDirectories; counter++) { + if (contents[counter].isDirectory()) { + filesInDir[i] = contents[counter]; + filenames.add(contents[counter].getName()); + i++; + } + } + Arrays.sort(filesInDir); + Collections.sort(filenames); + selectedDir = dir; + txtvSelectedFolder.setText(dir.getAbsolutePath()); + listDirectoriesAdapter.notifyDataSetChanged(); + fileObserver = createFileObserver(dir.getAbsolutePath()); + fileObserver.startWatching(); + Log.d(TAG, "Changed directory to " + dir.getAbsolutePath()); + } else { + Log.d(TAG, "Could not change folder: contents of dir were null"); + } + } else { + if (dir == null) { + Log.d(TAG, "Could not change folder: dir was null"); + } else { + Log.d(TAG, "Could not change folder: dir is no directory"); + } + } + refreshButtonState(); + } + + /** + * Changes the state of the buttons depending on the currently selected file + * or folder. + */ + private void refreshButtonState() { + if (selectedDir != null) { + butConfirm.setEnabled(isValidFile(selectedDir)); + supportInvalidateOptionsMenu(); + } + } + + /** Refresh the contents of the directory that is currently shown. */ + private void refreshDirectory() { + if (selectedDir != null) { + changeDirectory(selectedDir); + } + } + + /** Sets up a FileObserver to watch the current directory. */ + private FileObserver createFileObserver(String path) { + return new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE + | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) { + + @Override + public void onEvent(int event, String path) { + Log.d(TAG, "FileObserver received event " + event); + runOnUiThread(() -> refreshDirectory()); + } + }; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.new_folder_item) - .setVisible(isValidFile(selectedDir)); - return true; - } + menu.findItem(R.id.new_folder_item) + .setVisible(isValidFile(selectedDir)); + return true; + } - @Override - public boolean onCreateOptionsMenu(Menu menu) { + @Override + public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.directory_chooser, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - case R.id.new_folder_item: - openNewFolderDialog(); - return true; - case R.id.set_to_default_folder_item: - selectedDir = null; - returnSelectedFolder(); - return true; - default: - return false; - } - } - - /** - * Shows a confirmation dialog that asks the user if he wants to create a - * new folder. - */ - private void openNewFolderDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.create_folder_label); - builder.setMessage(String.format(getString(R.string.create_folder_msg), - CREATE_DIRECTORY_NAME)); - builder.setNegativeButton(R.string.cancel_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - builder.setPositiveButton(R.string.confirm_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - int msg = createFolder(); - Toast t = Toast.makeText(DirectoryChooserActivity.this, - msg, Toast.LENGTH_SHORT); - t.show(); - } - }); - builder.create().show(); - } - - /** - * Creates a new folder in the current directory with the name - * CREATE_DIRECTORY_NAME. - */ - private int createFolder() { - if (selectedDir == null) { - return R.string.create_folder_error; - } else if (selectedDir.canWrite()) { - File newDir = new File(selectedDir, CREATE_DIRECTORY_NAME); - if (!newDir.exists()) { - boolean result = newDir.mkdir(); - if (result) { - return R.string.create_folder_success; - } else { - return R.string.create_folder_error; - } - } else { - return R.string.create_folder_error_already_exists; - } - } else { - return R.string.create_folder_error_no_write_access; - } - } - - /** Returns true if the selected file or directory would be valid selection. */ - private boolean isValidFile(File file) { - return (file != null && file.isDirectory() && file.canRead() && file - .canWrite()); - } + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.directory_chooser, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + case R.id.new_folder_item: + openNewFolderDialog(); + return true; + case R.id.set_to_default_folder_item: + selectedDir = null; + returnSelectedFolder(); + return true; + default: + return false; + } + } + + /** + * Shows a confirmation dialog that asks the user if he wants to create a + * new folder. + */ + private void openNewFolderDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.create_folder_label); + builder.setMessage(String.format(getString(R.string.create_folder_msg), + CREATE_DIRECTORY_NAME)); + builder.setNegativeButton(R.string.cancel_label, + (dialog, which) -> { + dialog.dismiss(); + }); + builder.setPositiveButton(R.string.confirm_label, + (dialog, which) -> { + dialog.dismiss(); + int msg = createFolder(); + Toast t = Toast.makeText(DirectoryChooserActivity.this, + msg, Toast.LENGTH_SHORT); + t.show(); + }); + builder.create().show(); + } + + /** + * Creates a new folder in the current directory with the name + * CREATE_DIRECTORY_NAME. + */ + private int createFolder() { + if (selectedDir == null) { + return R.string.create_folder_error; + } else if (selectedDir.canWrite()) { + File newDir = new File(selectedDir, CREATE_DIRECTORY_NAME); + if (!newDir.exists()) { + boolean result = newDir.mkdir(); + if (result) { + return R.string.create_folder_success; + } else { + return R.string.create_folder_error; + } + } else { + return R.string.create_folder_error_already_exists; + } + } else { + return R.string.create_folder_error_no_write_access; + } + } + + /** Returns true if the selected file or directory would be valid selection. */ + private boolean isValidFile(File file) { + return file != null && file.isDirectory() && file.canRead() && file.canWrite(); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java index 24b684752..edb973a0c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -2,6 +2,9 @@ package de.danoeh.antennapod.activity; import android.content.ClipData; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; @@ -12,24 +15,31 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; import android.widget.CheckBox; -import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageView; +import android.widget.RadioButton; +import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; -import com.joanzapata.android.iconify.Iconify; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.joanzapata.iconify.Iconify; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedFilter; import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.LangUtils; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; @@ -38,6 +48,7 @@ import de.danoeh.antennapod.menuhandler.FeedMenuHandler; */ public class FeedInfoActivity extends ActionBarActivity { private static final String TAG = "FeedInfoActivity"; + private boolean autoDeleteChanged = false; public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; @@ -51,7 +62,13 @@ public class FeedInfoActivity extends ActionBarActivity { private TextView txtvUrl; private EditText etxtUsername; private EditText etxtPassword; + private EditText etxtFilterText; + private RadioButton rdoFilterInclude; + private RadioButton rdoFilterExclude; private CheckBox cbxAutoDownload; + private CheckBox cbxKeepUpdated; + private Spinner spnAutoDelete; + private boolean filterInclude = true; private final View.OnClickListener copyUrlToClipboard = new View.OnClickListener() { @Override @@ -89,8 +106,21 @@ public class FeedInfoActivity extends ActionBarActivity { txtvAuthor = (TextView) findViewById(R.id.txtvAuthor); txtvUrl = (TextView) findViewById(R.id.txtvUrl); cbxAutoDownload = (CheckBox) findViewById(R.id.cbxAutoDownload); + cbxKeepUpdated = (CheckBox) findViewById(R.id.cbxKeepUpdated); + spnAutoDelete = (Spinner) findViewById(R.id.spnAutoDelete); etxtUsername = (EditText) findViewById(R.id.etxtUsername); etxtPassword = (EditText) findViewById(R.id.etxtPassword); + etxtFilterText = (EditText) findViewById(R.id.etxtEpisodeFilterText); + rdoFilterInclude = (RadioButton) findViewById(R.id.radio_filter_include); + rdoFilterInclude.setOnClickListener(v -> { + filterInclude = true; + filterTextChanged = true; + }); + rdoFilterExclude = (RadioButton) findViewById(R.id.radio_filter_exclude); + rdoFilterExclude.setOnClickListener(v -> { + filterInclude = false; + filterTextChanged = true; + }); txtvUrl.setOnClickListener(copyUrlToClipboard); @@ -98,7 +128,7 @@ public class FeedInfoActivity extends ActionBarActivity { @Override protected Feed doInBackground(Long... params) { - return DBReader.getFeed(FeedInfoActivity.this, params[0]); + return DBReader.getFeed(params[0]); } @Override @@ -108,19 +138,25 @@ public class FeedInfoActivity extends ActionBarActivity { Log.d(TAG, "Language is " + feed.getLanguage()); Log.d(TAG, "Author is " + feed.getAuthor()); Log.d(TAG, "URL is " + feed.getDownload_url()); + FeedPreferences prefs = feed.getPreferences(); imgvCover.post(new Runnable() { @Override public void run() { - Picasso.with(FeedInfoActivity.this) + Glide.with(FeedInfoActivity.this) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(imgvCover); } }); txtvTitle.setText(feed.getTitle()); - txtvDescription.setText(feed.getDescription().trim()); + String description = feed.getDescription(); + txtvDescription.setText((description != null) ? description.trim() : ""); if (feed.getAuthor() != null) { txtvAuthor.setText(feed.getAuthor()); } @@ -132,23 +168,76 @@ public class FeedInfoActivity extends ActionBarActivity { Iconify.addIcons(txtvUrl); cbxAutoDownload.setEnabled(UserPreferences.isEnableAutodownload()); - cbxAutoDownload.setChecked(feed.getPreferences().getAutoDownload()); - cbxAutoDownload.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + cbxAutoDownload.setChecked(prefs.getAutoDownload()); + cbxAutoDownload.setOnCheckedChangeListener((compoundButton, checked) -> { + feed.getPreferences().setAutoDownload(checked); + feed.savePreferences(FeedInfoActivity.this); + updateAutoDownloadSettings(); + ApplyToEpisodesDialog dialog = new ApplyToEpisodesDialog(FeedInfoActivity.this, + feed, checked); + dialog.createNewDialog().show(); + }); + cbxKeepUpdated.setChecked(prefs.getKeepUpdated()); + cbxKeepUpdated.setOnCheckedChangeListener((compoundButton, checked) -> { + feed.getPreferences().setKeepUpdated(checked); + feed.savePreferences(FeedInfoActivity.this); + }); + spnAutoDelete.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { + FeedPreferences.AutoDeleteAction auto_delete_action; + switch (parent.getSelectedItemPosition()) { + case 0: + auto_delete_action = FeedPreferences.AutoDeleteAction.GLOBAL; + break; + + case 1: + auto_delete_action = FeedPreferences.AutoDeleteAction.YES; + break; + + case 2: + auto_delete_action = FeedPreferences.AutoDeleteAction.NO; + break; + + default: // TODO - add exceptions here + return; + } + feed.getPreferences().setAutoDeleteAction(auto_delete_action);// p + autoDeleteChanged = true; + } + @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { - feed.getPreferences().setAutoDownload(checked); - feed.savePreferences(FeedInfoActivity.this); + public void onNothingSelected(AdapterView<?> parent) { + // Another interface callback } }); + spnAutoDelete.setSelection(prefs.getAutoDeleteAction().ordinal()); - etxtUsername.setText(feed.getPreferences().getUsername()); - etxtPassword.setText(feed.getPreferences().getPassword()); + etxtUsername.setText(prefs.getUsername()); + etxtPassword.setText(prefs.getPassword()); etxtUsername.addTextChangedListener(authTextWatcher); etxtPassword.addTextChangedListener(authTextWatcher); - supportInvalidateOptionsMenu(); + FeedFilter filter = prefs.getFilter(); + if (filter.includeOnly()) { + etxtFilterText.setText(filter.getIncludeFilter()); + rdoFilterInclude.setChecked(true); + rdoFilterExclude.setChecked(false); + } else if (filter.excludeOnly()) { + etxtFilterText.setText(filter.getExcludeFilter()); + rdoFilterInclude.setChecked(false); + rdoFilterExclude.setChecked(true); + } else { + Log.d(TAG, "No filter set"); + rdoFilterInclude.setChecked(false); + rdoFilterExclude.setChecked(false); + etxtFilterText.setText(""); + } + etxtFilterText.addTextChangedListener(filterTextWatcher); + supportInvalidateOptionsMenu(); + updateAutoDownloadSettings(); } else { Log.e(TAG, "Activity was started with invalid arguments"); } @@ -177,16 +266,53 @@ public class FeedInfoActivity extends ActionBarActivity { } }; + private boolean filterTextChanged = false; + + private TextWatcher filterTextWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + filterTextChanged = true; + } + }; + @Override protected void onPause() { super.onPause(); - if (feed != null && authInfoChanged) { - Log.d(TAG, "Auth info changed, saving credentials"); + if (feed != null) { FeedPreferences prefs = feed.getPreferences(); - prefs.setUsername(etxtUsername.getText().toString()); - prefs.setPassword(etxtPassword.getText().toString()); - DBWriter.setFeedPreferences(this, prefs); + if (authInfoChanged) { + Log.d(TAG, "Auth info changed, saving credentials"); + prefs.setUsername(etxtUsername.getText().toString()); + prefs.setPassword(etxtPassword.getText().toString()); + } + if (filterTextChanged) { + Log.d(TAG, "Filter info changed, saving..."); + String filterText = etxtFilterText.getText().toString(); + String includeString = ""; + String excludeString = ""; + if (filterInclude) { + includeString = filterText; + } else { + excludeString = filterText; + } + prefs.setFilter(new FeedFilter(includeString, excludeString)); + } + if (authInfoChanged || autoDeleteChanged || filterTextChanged) { + DBWriter.setFeedPreferences(prefs); + } authInfoChanged = false; + autoDeleteChanged = false; + filterTextChanged = false; } } @@ -204,7 +330,8 @@ public class FeedInfoActivity extends ActionBarActivity { 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); + menu.findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null && + IntentUtils.isCallable(this, new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink())))); return true; } @@ -225,4 +352,34 @@ public class FeedInfoActivity extends ActionBarActivity { return super.onOptionsItemSelected(item); } } + + private void updateAutoDownloadSettings() { + if (feed != null && feed.getPreferences() != null) { + boolean enabled = feed.getPreferences().getAutoDownload() && UserPreferences.isEnableAutodownload(); + rdoFilterInclude.setEnabled(enabled); + rdoFilterExclude.setEnabled(enabled); + etxtFilterText.setEnabled(enabled); + } + } + + private class ApplyToEpisodesDialog extends ConfirmationDialog { + + private final Feed feed; + private final boolean autoDownload; + + public ApplyToEpisodesDialog(Context context, Feed feed, boolean autoDownload) { + super(context, R.string.auto_download_apply_to_items_title, + R.string.auto_download_apply_to_items_message); + this.feed = feed; + this.autoDownload = autoDownload; + setPositiveText(R.string.yes); + setNegativeText(R.string.no); + } + + @Override + public void onConfirmButtonPressed(DialogInterface dialog) { + DBWriter.setFeedsItemsAutoDownload(feed, autoDownload); + } + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 0be521f8b..d57199941 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -1,62 +1,82 @@ package de.danoeh.antennapod.activity; -import android.app.AlertDialog; +import android.annotation.TargetApi; +import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.DataSetObserver; import android.media.AudioManager; -import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; +import android.util.TypedValue; +import android.view.ContextMenu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; + +import com.bumptech.glide.Glide; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.Validate; + +import java.util.List; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.NavListAdapter; +import de.danoeh.antennapod.core.asynctask.FeedRemover; +import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.ProgressEvent; +import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.QueueEvent; +import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.StorageUtils; +import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.dialog.RatingDialog; import de.danoeh.antennapod.fragment.AddFeedFragment; -import de.danoeh.antennapod.fragment.AllEpisodesFragment; import de.danoeh.antennapod.fragment.DownloadsFragment; +import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.ExternalPlayerFragment; import de.danoeh.antennapod.fragment.ItemlistFragment; -import de.danoeh.antennapod.fragment.NewEpisodesFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import de.danoeh.antennapod.preferences.PreferenceController; import de.greenrobot.event.EventBus; -import java.util.List; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.Validate; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * The activity that is shown when the user launches the app. */ -public class MainActivity extends ActionBarActivity implements NavDrawerActivity { +public class MainActivity extends AppCompatActivity implements NavDrawerActivity { private static final String TAG = "MainActivity"; - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED - | EventDistributor.DOWNLOAD_QUEUED - | EventDistributor.FEED_LIST_UPDATE + private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE | EventDistributor.UNREAD_ITEMS_UPDATE; public static final String PREF_NAME = "MainActivityPrefs"; @@ -73,8 +93,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity public static final String[] NAV_DRAWER_TAGS = { QueueFragment.TAG, - NewEpisodesFragment.TAG, - AllEpisodesFragment.TAG, + EpisodesFragment.TAG, DownloadsFragment.TAG, PlaybackHistoryFragment.TAG, SubscriptionFragment.TAG, @@ -88,12 +107,16 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity private View navDrawer; private ListView navList; private NavListAdapter navAdapter; + private int mPosition = -1; private ActionBarDrawerToggle drawerToggle; - private CharSequence drawerTitle; private CharSequence currentTitle; + private ProgressDialog pd; + + private Subscription subscription; + @Override public void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getNoTitleTheme()); @@ -104,9 +127,15 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); - getSupportActionBar().setElevation(3.0f); - drawerTitle = currentTitle = getTitle(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + findViewById(R.id.shadow).setVisibility(View.GONE); + int elevation = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, + getResources().getDisplayMetrics()); + getSupportActionBar().setElevation(elevation); + } + + currentTitle = getTitle(); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); navList = (ListView) findViewById(R.id.nav_list); @@ -121,11 +150,8 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity final FragmentManager fm = getSupportFragmentManager(); - fm.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { - @Override - public void onBackStackChanged() { - drawerToggle.setDrawerIndicatorEnabled(fm.getBackStackEntryCount() == 0); - } + fm.addOnBackStackChangedListener(() -> { + drawerToggle.setDrawerIndicatorEnabled(fm.getBackStackEntryCount() == 0); }); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -135,6 +161,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity navList.setAdapter(navAdapter); navList.setOnItemClickListener(navListClickListener); navList.setOnItemLongClickListener(newListLongClickListener); + registerForContextMenu(navList); navAdapter.registerDataSetObserver(new DataSetObserver() { @Override @@ -143,12 +170,9 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity } }); - findViewById(R.id.nav_settings).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - drawerLayout.closeDrawer(navDrawer); - startActivity(new Intent(MainActivity.this, PreferenceController.getPreferenceActivity())); - } + findViewById(R.id.nav_settings).setOnClickListener(v -> { + drawerLayout.closeDrawer(navDrawer); + startActivity(new Intent(MainActivity.this, PreferenceController.getPreferenceActivity())); }); FragmentTransaction transaction = fm.beginTransaction(); @@ -158,23 +182,31 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity transaction.replace(R.id.main_view, mainFragment); } else { String lastFragment = getLastNavFragment(); - if(ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) { + if (ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) { loadFragment(lastFragment, null); + } else { + try { + loadFeedFragmentById(Integer.valueOf(lastFragment), null); + } catch (NumberFormatException e) { + // it's not a number, this happens if we removed + // a label from the NAV_DRAWER_TAGS + // give them a nice default... + loadFragment(QueueFragment.TAG, null); + } } - // else: lastFragment contains feed id - drawer data is not loaded yet, - // so loading is postponed until then } externalPlayerFragment = new ExternalPlayerFragment(); - transaction.replace(R.id.playerFragment, externalPlayerFragment); + transaction.replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG); transaction.commit(); checkFirstLaunch(); } private void saveLastNavFragment(String tag) { + Log.d(TAG, "saveLastNavFragment(tag: " + tag + ")"); SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); SharedPreferences.Editor edit = prefs.edit(); - if(tag != null) { + if (tag != null) { edit.putString(PREF_LAST_FRAGMENT_TAG, tag); } else { edit.remove(PREF_LAST_FRAGMENT_TAG); @@ -184,18 +216,18 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity private String getLastNavFragment() { SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); - return prefs.getString(PREF_LAST_FRAGMENT_TAG, QueueFragment.TAG); + String lastFragment = prefs.getString(PREF_LAST_FRAGMENT_TAG, QueueFragment.TAG); + Log.d(TAG, "getLastNavFragment() -> " + lastFragment); + return lastFragment; } private void checkFirstLaunch() { SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - drawerLayout.openDrawer(navDrawer); - } - }, 1500); + new Handler().postDelayed(() -> drawerLayout.openDrawer(navDrawer), 1500); + + // for backward compatibility, we only change defaults for fresh installs + UserPreferences.setUpdateInterval(12); SharedPreferences.Editor edit = prefs.edit(); edit.putBoolean(PREF_IS_FIRST_LAUNCH, false); @@ -217,30 +249,20 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle(R.string.drawer_preferences); - builder.setMultiChoiceItems(navLabels, checked, new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - if (isChecked) { - hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); - } else { - hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); - } + builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> { + if (isChecked) { + hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); + } else { + hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); } }); - builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - UserPreferences.setHiddenDrawerItems(MainActivity.this, hiddenDrawerItems); - } + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); }); builder.setNegativeButton(R.string.cancel_label, null); builder.create().show(); } - public ActionBar getMainActivtyActionBar() { - return getSupportActionBar(); - } - public boolean isDrawerOpen() { return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); } @@ -250,6 +272,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity } public void loadFragment(int index, Bundle args) { + Log.d(TAG, "loadFragment(index: " + index + ", args: " + args + ")"); if (index < navAdapter.getSubscriptionOffset()) { String tag = navAdapter.getTags().get(index); loadFragment(tag, args); @@ -259,18 +282,15 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity } } - public void loadFragment(final String tag, Bundle args) { - Log.d(TAG, "loadFragment(\"" + tag + "\", " + args + ")"); + public void loadFragment(String tag, Bundle args) { + Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")"); Fragment fragment = null; switch (tag) { case QueueFragment.TAG: fragment = new QueueFragment(); break; - case NewEpisodesFragment.TAG: - fragment = new NewEpisodesFragment(); - break; - case AllEpisodesFragment.TAG: - fragment = new AllEpisodesFragment(); + case EpisodesFragment.TAG: + fragment = new EpisodesFragment(); break; case DownloadsFragment.TAG: fragment = new DownloadsFragment(); @@ -286,6 +306,12 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity subscriptionFragment.setItemAccess(itemAccess); fragment = subscriptionFragment; break; + default: + // default to the queue + tag = QueueFragment.TAG; + fragment = new QueueFragment(); + args = null; + break; } currentTitle = navAdapter.getLabel(tag); getSupportActionBar().setTitle(currentTitle); @@ -298,22 +324,25 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity private void loadFeedFragmentByPosition(int relPos, Bundle args) { - if(relPos < 0) { + if (relPos < 0) { return; } Feed feed = itemAccess.getItem(relPos); - long feedId = feed.getId(); + loadFeedFragmentById(feed.getId(), args); + } + + public void loadFeedFragmentById(long feedId, Bundle args) { Fragment fragment = ItemlistFragment.newInstance(feedId); - if(args != null) { + if (args != null) { fragment.setArguments(args); } - saveLastNavFragment(String.valueOf(feed.getId())); + saveLastNavFragment(String.valueOf(feedId)); currentTitle = ""; getSupportActionBar().setTitle(currentTitle); loadChildFragment(fragment); } - private void loadFeedFragment(Feed feed){ + private void loadFeedFragment(Feed feed) { long feedId = feed.getId(); Fragment fragment = ItemlistFragment.newInstance(feedId); currentTitle = ""; @@ -321,21 +350,6 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity loadChildFragment(fragment); } - public void loadFeedFragmentById(long feedId) { - if (navDrawerData != null) { - int relPos = -1; - List<Feed> feeds = navDrawerData.feeds; - for (int i = 0; relPos < 0 && i < feeds.size(); i++) { - if (feeds.get(i).getId() == feedId) { - relPos = i; - } - } - if(relPos >= 0) { - loadFeedFragmentByPosition(relPos, null); - } - } - } - private void loadFragment(Fragment fragment) { FragmentManager fragmentManager = getSupportFragmentManager(); // clear back stack @@ -345,7 +359,13 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity FragmentTransaction t = fragmentManager.beginTransaction(); t.replace(R.id.main_view, fragment, "main"); fragmentManager.popBackStack(); - t.commit(); + // TODO: we have to allow state loss here + // since this function can get called from an AsyncTask which + // could be finishing after our app has already committed state + // and is about to get shutdown. What we *should* do is + // not commit anything in an AsyncTask, but that's a bigger + // change than we want now. + t.commitAllowingStateLoss(); if (navAdapter != null) { navAdapter.notifyDataSetChanged(); } @@ -364,24 +384,26 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity getSupportFragmentManager().popBackStack(); } - public Toolbar getToolbar() { - return toolbar; - } - private int getSelectedNavListIndex() { - String lastFragment = getLastNavFragment(); - int tagIndex = navAdapter.getTags().indexOf(lastFragment); - if(tagIndex >= 0) { + String currentFragment = getLastNavFragment(); + if (currentFragment == null) { + // should not happen, but better safe than sorry + return -1; + } + int tagIndex = navAdapter.getTags().indexOf(currentFragment); + if (tagIndex >= 0) { return tagIndex; - } else if(ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) { + } else if (ArrayUtils.contains(NAV_DRAWER_TAGS, currentFragment)) { // the fragment was just hidden return -1; } else { // last fragment was not a list, but a feed - long feedId = Long.parseLong(lastFragment); - List<Feed> feeds = navDrawerData.feeds; - for (int i = 0; i < feeds.size(); i++) { - if (feeds.get(i).getId() == feedId) { - return i + navAdapter.getSubscriptionOffset(); + long feedId = Long.parseLong(currentFragment); + if (navDrawerData != null) { + List<Feed> feeds = navDrawerData.feeds; + for (int i = 0; i < feeds.size(); i++) { + if (feeds.get(i).getId() == feedId) { + return i + navAdapter.getSubscriptionOffset(); + } } } return -1; @@ -402,10 +424,11 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity private AdapterView.OnItemLongClickListener newListLongClickListener = new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { - if(position < navAdapter.getTags().size()) { + if (position < navAdapter.getTags().size()) { showDrawerPreferencesDialog(); return true; } else { + mPosition = position; return false; } } @@ -443,6 +466,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity super.onStart(); EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); + RatingDialog.init(this); } @Override @@ -460,16 +484,35 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG))) { handleNavIntent(); } - loadData(); + RatingDialog.check(); } @Override protected void onStop() { super.onStop(); - cancelLoadTask(); EventDistributor.getInstance().unregister(contentUpdate); EventBus.getDefault().unregister(this); + if (subscription != null) { + subscription.unsubscribe(); + } + if (pd != null) { + pd.dismiss(); + } + } + + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + Glide.get(this).trimMemory(level); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + Glide.get(this).clearMemory(); } @Override @@ -486,8 +529,95 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity } } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + if (v.getId() != R.id.nav_list) { + return; + } + AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; + int position = adapterInfo.position; + if (position < navAdapter.getSubscriptionOffset()) { + return; + } + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.nav_feed_context, menu); + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + menu.setHeaderTitle(feed.getTitle()); + // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones! + } + + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int position = mPosition; + mPosition = -1; // reset + if (position < 0) { + return false; + } + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + switch (item.getItemId()) { + case R.id.mark_all_seen_item: + DBWriter.markFeedSeen(feed.getId()); + return true; + case R.id.mark_all_read_item: + DBWriter.markFeedRead(feed.getId()); + return true; + case R.id.remove_item: + final FeedRemover remover = new FeedRemover(this, feed) { + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + if (getSelectedNavListIndex() == position) { + loadFragment(EpisodesFragment.TAG, null); + } + } + }; + ConfirmationDialog conDialog = new ConfirmationDialog(this, + R.string.remove_feed_label, + R.string.feed_delete_confirmation_msg) { + @Override + public void onConfirmButtonPressed( + DialogInterface dialog) { + dialog.dismiss(); + if (externalPlayerFragment != null) { + PlaybackController controller = externalPlayerFragment.getPlaybackControllerTestingOnly(); + if (controller != null) { + Playable playable = controller.getMedia(); + if (playable != null && playable instanceof FeedMedia) { + FeedMedia media = (FeedMedia) playable; + if (media.getItem().getFeed().getId() == feed.getId()) { + Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); + remover.skipOnCompletion = true; + if (controller.getStatus() == PlayerStatus.PLAYING) { + sendBroadcast(new Intent( + PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); + } + } + } + } + } + remover.executeAsync(); + } + }; + conDialog.createNewDialog().show(); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public void onBackPressed() { + if (isDrawerOpen()) { + drawerLayout.closeDrawer(navDrawer); + } else { + super.onBackPressed(); + } + } + private DBReader.NavDrawerData navDrawerData; - private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask; private int selectedNavListIndex = 0; private NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { @@ -502,7 +632,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity @Override public Feed getItem(int position) { - if (navDrawerData != null && position < navDrawerData.feeds.size()) { + if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { return navDrawerData.feeds.get(position); } else { return null; @@ -525,47 +655,33 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity } @Override - public int getNumberOfUnreadFeedItems(long feedId) { - return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0; + public int getNumberOfDownloadedItems() { + return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0; + } + + @Override + public int getFeedCounter(long feedId) { + return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; } }; private void loadData() { - cancelLoadTask(); - loadTask = new AsyncTask<Void, Void, DBReader.NavDrawerData>() { - @Override - protected DBReader.NavDrawerData doInBackground(Void... params) { - return DBReader.getNavDrawerData(MainActivity.this); - } - - @Override - protected void onPostExecute(DBReader.NavDrawerData result) { - super.onPostExecute(navDrawerData); - boolean handleIntent = (navDrawerData == null); - - navDrawerData = result; - navAdapter.notifyDataSetChanged(); - - String lastFragment = getLastNavFragment(); - if(!ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) { - long feedId = Long.valueOf(lastFragment); - loadFeedFragmentById(feedId); - saveLastNavFragment(null); - } + subscription = Observable.fromCallable(() -> DBReader.getNavDrawerData()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + boolean handleIntent = (navDrawerData == null); - if (handleIntent) { - handleNavIntent(); - } - } - }; - loadTask.execute(); - } + navDrawerData = result; + navAdapter.notifyDataSetChanged(); - private void cancelLoadTask() { - if (loadTask != null) { - loadTask.cancel(true); - } + if (handleIntent) { + handleNavIntent(); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } public void onEvent(QueueEvent event) { @@ -573,10 +689,28 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity loadData(); } - public void onEvent(SubscriptionFragment.SubscriptionEvent event){ + public void onEvent(SubscriptionFragment.SubscriptionEvent event) { loadFeedFragment(event.feed); } + public void onEventMainThread(ProgressEvent event) { + Log.d(TAG, "onEvent(" + event + ")"); + switch (event.action) { + case START: + pd = new ProgressDialog(this); + pd.setMessage(event.message); + pd.setIndeterminate(true); + pd.setCancelable(false); + pd.show(); + break; + case END: + if (pd != null) { + pd.dismiss(); + } + break; + } + } + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override @@ -610,15 +744,4 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity super.onNewIntent(intent); setIntent(intent); } - - @Override - public void onBackPressed() { - // Make sure to have consistent behaviour across android apps - // Close the nav drawer if open on the first back button press - if(drawerLayout.isDrawerOpen(navDrawer)){ - drawerLayout.closeDrawer(navDrawer); - return; - } - super.onBackPressed(); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 0cd388b9d..bdc210651 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -1,56 +1,82 @@ package de.danoeh.antennapod.activity; -import android.app.AlertDialog; -import android.content.DialogInterface; +import android.annotation.TargetApi; import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.PixelFormat; import android.media.AudioManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; +import android.support.v4.view.ViewCompat; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; - import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; +import android.widget.Toast; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.bumptech.glide.Glide; +import com.joanzapata.iconify.IconDrawable; +import com.joanzapata.iconify.fonts.FontAwesomeIcons; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.playback.MediaPlayerError; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; -import de.danoeh.antennapod.dialog.TimeDialog; +import de.danoeh.antennapod.dialog.SleepTimerDialog; +import de.danoeh.antennapod.dialog.VariableSpeedDialog; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + /** * Provides general features which are both needed for playing audio and video * files. */ -public abstract class MediaplayerActivity extends ActionBarActivity - implements OnSeekBarChangeListener { +public abstract class MediaplayerActivity extends AppCompatActivity implements OnSeekBarChangeListener { private static final String TAG = "MediaplayerActivity"; + private static final String PREFS = "MediaPlayerActivityPreferences"; + private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft"; protected PlaybackController controller; protected TextView txtvPosition; protected TextView txtvLength; protected SeekBar sbPosition; - protected ImageButton butPlay; + protected Button butPlaybackSpeed; protected ImageButton butRev; protected TextView txtvRev; + protected ImageButton butPlay; protected ImageButton butFF; protected TextView txtvFF; + protected ImageButton butSkip; + + protected boolean showTimeLeft = false; + + private boolean isFavorite = false; private PlaybackController newPlaybackController() { return new PlaybackController(this, false) { @@ -150,7 +176,7 @@ public abstract class MediaplayerActivity extends ActionBarActivity } protected void onPlaybackSpeedChange() { - + updateButPlaybackSpeed(); } protected void onServiceQueried() { @@ -231,10 +257,17 @@ public abstract class MediaplayerActivity extends ActionBarActivity } } + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + Glide.get(this).trimMemory(level); + } + @Override - protected void onDestroy() { - super.onDestroy(); - Log.d(TAG, "onDestroy()"); + public void onLowMemory() { + super.onLowMemory(); + Glide.get(this).clearMemory(); } @Override @@ -248,27 +281,63 @@ public abstract class MediaplayerActivity extends ActionBarActivity @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); + if (controller == null) { + return false; + } Playable media = controller.getMedia(); menu.findItem(R.id.support_item).setVisible( media != null && media.getPaymentLink() != null && (media instanceof FeedMedia) && + ((FeedMedia) media).getItem() != null && ((FeedMedia) media).getItem().getFlattrStatus().flattrable() ); - menu.findItem(R.id.share_link_item).setVisible( - media != null && media.getWebsiteLink() != null); - menu.findItem(R.id.visit_website_item).setVisible( - media != null && media.getWebsiteLink() != null); - menu.findItem(R.id.skip_episode_item).setVisible(media != null); + + boolean hasWebsiteLink = media != null && media.getWebsiteLink() != null; + menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink); + + boolean isItemAndHasLink = media != null && (media instanceof FeedMedia) && + ((FeedMedia) media).getItem() != null && ((FeedMedia) media).getItem().getLink() != null; + menu.findItem(R.id.share_link_item).setVisible(isItemAndHasLink); + menu.findItem(R.id.share_link_with_position_item).setVisible(isItemAndHasLink); + + boolean isItemHasDownloadLink = media != null && (media instanceof FeedMedia) && ((FeedMedia) media).getDownload_url() != null; + menu.findItem(R.id.share_download_url_item).setVisible(isItemHasDownloadLink); + menu.findItem(R.id.share_download_url_with_position_item).setVisible(isItemHasDownloadLink); + + menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink); + + menu.findItem(R.id.add_to_favorites_item).setVisible(false); + menu.findItem(R.id.remove_from_favorites_item).setVisible(false); + if(media != null && media instanceof FeedMedia) { + menu.findItem(R.id.add_to_favorites_item).setVisible(!isFavorite); + menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite); + } + boolean sleepTimerSet = controller.sleepTimerActive(); boolean sleepTimerNotSet = controller.sleepTimerNotActive(); menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet); menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet); + + if (this instanceof AudioplayerActivity) { + int[] attrs = {R.attr.action_bar_icon_color}; + TypedArray ta = obtainStyledAttributes(UserPreferences.getTheme(), attrs); + int textColor = ta.getColor(0, Color.GRAY); + ta.recycle(); + menu.findItem(R.id.audio_controls).setIcon(new IconDrawable(this, + FontAwesomeIcons.fa_sliders).color(textColor).actionBarSize()); + } else { + menu.findItem(R.id.audio_controls).setVisible(false); + } + return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { + if (controller == null) { + return false; + } Playable media = controller.getMedia(); if (item.getItemId() == android.R.id.home) { Intent intent = new Intent(MediaplayerActivity.this, @@ -277,80 +346,235 @@ public abstract class MediaplayerActivity extends ActionBarActivity | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return true; - } else if (media != null) { - switch (item.getItemId()) { - case R.id.disable_sleeptimer_item: - if (controller.serviceAvailable()) { - AlertDialog.Builder stDialog = new AlertDialog.Builder(this); - stDialog.setTitle(R.string.sleep_timer_label); - stDialog.setMessage(getString(R.string.time_left_label) - + Converter.getDurationStringLong((int) controller - .getSleepTimerTimeLeft())); - stDialog.setPositiveButton( - R.string.disable_sleeptimer_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - controller.disableSleepTimer(); - } + } else { + if (media != null) { + switch (item.getItemId()) { + case R.id.add_to_favorites_item: + if(media instanceof FeedMedia) { + FeedItem feedItem = ((FeedMedia)media).getItem(); + if(feedItem != null) { + DBWriter.addFavoriteItem(feedItem); + isFavorite = true; + invalidateOptionsMenu(); + Toast.makeText(this, R.string.added_to_favorites, Toast.LENGTH_SHORT) + .show(); + } + } + break; + case R.id.remove_from_favorites_item: + if(media instanceof FeedMedia) { + FeedItem feedItem = ((FeedMedia)media).getItem(); + if(feedItem != null) { + DBWriter.removeFavoriteItem(feedItem); + isFavorite = false; + invalidateOptionsMenu(); + Toast.makeText(this, R.string.removed_from_favorites, Toast.LENGTH_SHORT) + .show(); + } + } + break; + case R.id.disable_sleeptimer_item: + if (controller.serviceAvailable()) { + + MaterialDialog.Builder stDialog = new MaterialDialog.Builder(this); + stDialog.title(R.string.sleep_timer_label); + stDialog.content(getString(R.string.time_left_label) + + Converter.getDurationStringLong((int) controller + .getSleepTimerTimeLeft())); + stDialog.positiveText(R.string.disable_sleeptimer_label); + stDialog.negativeText(R.string.cancel_label); + stDialog.callback(new MaterialDialog.ButtonCallback() { + @Override + public void onPositive(MaterialDialog dialog) { + dialog.dismiss(); + controller.disableSleepTimer(); } - ); - stDialog.setNegativeButton(R.string.cancel_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - } + + @Override + public void onNegative(MaterialDialog dialog) { + dialog.dismiss(); } - ); - stDialog.create().show(); - } - break; - case R.id.set_sleeptimer_item: - if (controller.serviceAvailable()) { - TimeDialog td = new TimeDialog(this, - R.string.set_sleeptimer_label, - R.string.set_sleeptimer_label) { + }); + stDialog.build().show(); + } + break; + case R.id.set_sleeptimer_item: + if (controller.serviceAvailable()) { + SleepTimerDialog td = new SleepTimerDialog(this) { + @Override + public void onTimerSet(long millis, boolean shakeToReset, boolean vibrate) { + controller.setSleepTimer(millis, shakeToReset, vibrate); + } + }; + td.createNewDialog().show(); + } + break; + case R.id.audio_controls: + MaterialDialog dialog = new MaterialDialog.Builder(this) + .title(R.string.audio_controls) + .customView(R.layout.audio_controls, false) + .neutralText(R.string.close_label) + .onNeutral((dialog1, which) -> { + final SeekBar left = (SeekBar) dialog1.findViewById(R.id.volume_left); + final SeekBar right = (SeekBar) dialog1.findViewById(R.id.volume_right); + UserPreferences.setVolume(left.getProgress(), right.getProgress()); + }) + .show(); + final SeekBar barPlaybackSpeed = (SeekBar) dialog.findViewById(R.id.playback_speed); + final Button butDecSpeed = (Button) dialog.findViewById(R.id.butDecSpeed); + butDecSpeed.setOnClickListener(v -> { + if(controller != null && controller.canSetPlaybackSpeed()) { + barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 2); + } else { + VariableSpeedDialog.showGetPluginDialog(this); + } + }); + final Button butIncSpeed = (Button) dialog.findViewById(R.id.butIncSpeed); + butIncSpeed.setOnClickListener(v -> { + if(controller != null && controller.canSetPlaybackSpeed()) { + barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 2); + } else { + VariableSpeedDialog.showGetPluginDialog(this); + } + }); + + final TextView txtvPlaybackSpeed = (TextView) dialog.findViewById(R.id.txtvPlaybackSpeed); + float currentSpeed = 1.0f; + try { + currentSpeed = Float.parseFloat(UserPreferences.getPlaybackSpeed()); + } catch (NumberFormatException e) { + Log.e(TAG, Log.getStackTraceString(e)); + UserPreferences.setPlaybackSpeed(String.valueOf(currentSpeed)); + } + txtvPlaybackSpeed.setText(String.format("%.2fx", currentSpeed)); + barPlaybackSpeed.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override - public void onTimeEntered(long millis) { - controller.setSleepTimer(millis); + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if(controller != null && controller.canSetPlaybackSpeed()) { + float playbackSpeed = (progress + 10) / 20.0f; + controller.setPlaybackSpeed(playbackSpeed); + String speed = String.format("%.2f", playbackSpeed); + UserPreferences.setPlaybackSpeed(speed); + txtvPlaybackSpeed.setText(speed + "x"); + } else if(fromUser) { + float speed = Float.valueOf(UserPreferences.getPlaybackSpeed()); + barPlaybackSpeed.post(() -> { + barPlaybackSpeed.setProgress((int) (20 * speed) - 10); + }); + } } - }; - td.show(); - break; + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if(controller != null && !controller.canSetPlaybackSpeed()) { + VariableSpeedDialog.showGetPluginDialog(MediaplayerActivity.this); + } + } - } - case R.id.visit_website_item: - Uri uri = Uri.parse(media.getWebsiteLink()); - startActivity(new Intent(Intent.ACTION_VIEW, uri)); - break; - case R.id.support_item: - if (media instanceof FeedMedia) { - FeedItem feedItem = ((FeedMedia) media).getItem(); - DBTasks.flattrItemIfLoggedIn(this, feedItem); - } - break; - case R.id.share_link_item: - ShareUtils.shareLink(this, media.getWebsiteLink()); - break; - case R.id.skip_episode_item: - sendBroadcast(new Intent( - PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); - break; - default: - return false; + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + barPlaybackSpeed.setProgress((int) (20 * currentSpeed) - 10); + + final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left); + barLeftVolume.setProgress(100); + final SeekBar barRightVolume = (SeekBar) dialog.findViewById(R.id.volume_right); + barRightVolume.setProgress(100); + final CheckBox stereoToMono = (CheckBox) dialog.findViewById(R.id.stereo_to_mono); + stereoToMono.setChecked(UserPreferences.stereoToMono()); + if (controller != null && !controller.canDownmix()) { + stereoToMono.setEnabled(false); + String sonicOnly = getString(R.string.sonic_only); + stereoToMono.setText(stereoToMono.getText() + " [" + sonicOnly + "]"); + } + + barLeftVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + float leftVolume = 1.0f, rightVolume = 1.0f; + if (progress < 100) { + leftVolume = progress / 100.0f; + } + if (barRightVolume.getProgress() < 100) { + rightVolume = barRightVolume.getProgress() / 100.0f; + } + controller.setVolume(leftVolume, rightVolume); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + barRightVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + float leftVolume = 1.0f, rightVolume = 1.0f; + if (progress < 100) { + rightVolume = progress / 100.0f; + } + if (barLeftVolume.getProgress() < 100) { + leftVolume = barLeftVolume.getProgress() / 100.0f; + } + controller.setVolume(leftVolume, rightVolume); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + stereoToMono.setOnCheckedChangeListener((buttonView, isChecked) -> { + UserPreferences.stereoToMono(isChecked); + if (controller != null) { + controller.setDownmix(isChecked); + } + }); + break; + case R.id.visit_website_item: + Uri uri = Uri.parse(media.getWebsiteLink()); + 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()); + } + break; + case R.id.share_download_url_item: + if (media instanceof FeedMedia) { + ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem()); + } + break; + case R.id.share_link_with_position_item: + if (media instanceof FeedMedia) { + ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem(), true); + } + break; + case R.id.share_download_url_with_position_item: + if (media instanceof FeedMedia) { + ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem(), true); + } + break; + default: + return false; + } + return true; + } else { + return false; } - return true; - } else { - return false; } } @@ -383,7 +607,13 @@ public abstract class MediaplayerActivity extends ActionBarActivity && controller.getMedia() != null) { txtvPosition.setText(Converter .getDurationStringLong(currentPosition)); - txtvLength.setText(Converter.getDurationStringLong(duration)); + if (showTimeLeft) { + txtvLength.setText("-" + Converter + .getDurationStringLong(duration - currentPosition)); + } else { + txtvLength.setText(Converter + .getDurationStringLong(duration)); + } updateProgressbarPosition(currentPosition, duration); } else { Log.w(TAG, "Could not react to position observer update because of invalid time"); @@ -392,7 +622,7 @@ public abstract class MediaplayerActivity extends ActionBarActivity } private void updateProgressbarPosition(int position, int duration) { - Log.d(TAG, "updateProgressbarPosition(" + position + ", " + duration +")"); + Log.d(TAG, "updateProgressbarPosition(" + position + ", " + duration + ")"); float progress = ((float) position) / duration; sbPosition.setProgress((int) (progress * sbPosition.getMax())); } @@ -406,16 +636,33 @@ public abstract class MediaplayerActivity extends ActionBarActivity protected boolean loadMediaInfo() { Log.d(TAG, "loadMediaInfo()"); Playable media = controller.getMedia(); + SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); + showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); if (media != null) { - txtvPosition.setText(Converter.getDurationStringLong((media - .getPosition()))); + txtvPosition.setText(Converter.getDurationStringLong((media.getPosition()))); if (media.getDuration() != 0) { - txtvLength.setText(Converter.getDurationStringLong(media - .getDuration())); - float progress = ((float) media.getPosition()) - / media.getDuration(); + txtvLength.setText(Converter.getDurationStringLong(media.getDuration())); + float progress = ((float) media.getPosition()) / media.getDuration(); sbPosition.setProgress((int) (progress * sbPosition.getMax())); + if (showTimeLeft) { + int timeLeft = media.getDuration() - media.getPosition(); + txtvLength.setText("-" + Converter.getDurationStringLong(timeLeft)); + } + } + checkFavorite(); + if(butPlaybackSpeed != null) { + if (controller == null) { + butPlaybackSpeed.setVisibility(View.GONE); + } else { + butPlaybackSpeed.setVisibility(View.VISIBLE); + if (controller.canSetPlaybackSpeed()) { + ViewCompat.setAlpha(butPlaybackSpeed, 1.0f); + } else { + ViewCompat.setAlpha(butPlaybackSpeed, 0.5f); + } + } + updateButPlaybackSpeed(); } return true; } else { @@ -427,18 +674,45 @@ public abstract class MediaplayerActivity extends ActionBarActivity setContentView(getContentViewResourceId()); sbPosition = (SeekBar) findViewById(R.id.sbPosition); txtvPosition = (TextView) findViewById(R.id.txtvPosition); + + SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); + showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); + Log.d("timeleft", showTimeLeft ? "true" : "false"); txtvLength = (TextView) findViewById(R.id.txtvLength); - butPlay = (ImageButton) findViewById(R.id.butPlay); + txtvLength.setOnClickListener(v -> { + showTimeLeft = !showTimeLeft; + Playable media = controller.getMedia(); + if (media == null) { + return; + } + + String length; + if (showTimeLeft) { + length = "-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition()); + } else { + length = Converter.getDurationStringLong(media.getDuration()); + } + txtvLength.setText(length); + + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft); + editor.apply(); + Log.d("timeleft on click", showTimeLeft ? "true" : "false"); + }); + + butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed); butRev = (ImageButton) findViewById(R.id.butRev); txtvRev = (TextView) findViewById(R.id.txtvRev); - if(txtvRev != null) { + if (txtvRev != null) { txtvRev.setText(String.valueOf(UserPreferences.getRewindSecs())); } + butPlay = (ImageButton) findViewById(R.id.butPlay); butFF = (ImageButton) findViewById(R.id.butFF); txtvFF = (TextView) findViewById(R.id.txtvFF); - if(txtvFF != null) { + if (txtvFF != null) { txtvFF.setText(String.valueOf(UserPreferences.getFastFowardSecs())); } + butSkip = (ImageButton) findViewById(R.id.butSkip); // SEEKBAR SETUP @@ -446,98 +720,120 @@ public abstract class MediaplayerActivity extends ActionBarActivity // BUTTON SETUP - butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); + if(butPlaybackSpeed != null) { + butPlaybackSpeed.setOnClickListener(v -> { + if (controller == null) { + return; + } + if (controller.canSetPlaybackSpeed()) { + String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); + String currentSpeed = UserPreferences.getPlaybackSpeed(); + + // Provide initial value in case the speed list has changed + // out from under us + // and our current speed isn't in the new list + String newSpeed; + if (availableSpeeds.length > 0) { + newSpeed = availableSpeeds[0]; + } else { + newSpeed = "1.00"; + } - if (butFF != null) { - butFF.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - int curr = controller.getPosition(); - controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000); + for (int i = 0; i < availableSpeeds.length; i++) { + if (availableSpeeds[i].equals(currentSpeed)) { + if (i == availableSpeeds.length - 1) { + newSpeed = availableSpeeds[0]; + } else { + newSpeed = availableSpeeds[i + 1]; + } + break; + } + } + UserPreferences.setPlaybackSpeed(newSpeed); + controller.setPlaybackSpeed(Float.parseFloat(newSpeed)); + } else { + VariableSpeedDialog.showGetPluginDialog(this); } }); - butFF.setOnLongClickListener(new View.OnLongClickListener() { + butPlaybackSpeed.setOnLongClickListener(v -> { + VariableSpeedDialog.showDialog(this); + return true; + }); + } + + if (butRev != null) { + butRev.setOnClickListener(v -> { + int curr = controller.getPosition(); + controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000); + }); + butRev.setOnLongClickListener(new View.OnLongClickListener() { int choice; @Override public boolean onLongClick(View v) { int checked = 0; - int rewindSecs = UserPreferences.getFastFowardSecs(); + int rewindSecs = UserPreferences.getRewindSecs(); final int[] values = getResources().getIntArray(R.array.seek_delta_values); final String[] choices = new String[values.length]; - for(int i=0; i < values.length; i++) { + for (int i = 0; i < values.length; i++) { if (rewindSecs == values[i]) { checked = i; } - choices[i] = String.valueOf(values[i]) + " " - + getString(R.string.time_unit_seconds); + choices[i] = String.valueOf(values[i]) + " " + getString(R.string.time_seconds); } choice = values[checked]; AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this); - builder.setTitle(R.string.pref_fast_forward); + builder.setTitle(R.string.pref_rewind); builder.setSingleChoiceItems(choices, checked, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - choice = values[which]; - } + (dialog, which) -> { + choice = values[which]; }); builder.setNegativeButton(R.string.cancel_label, null); - builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - UserPreferences.setPrefFastForwardSecs(choice); - txtvFF.setText(String.valueOf(choice)); - } + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setPrefRewindSecs(choice); + txtvRev.setText(String.valueOf(choice)); }); builder.create().show(); return true; } }); } - if (butRev != null) { - butRev.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - int curr = controller.getPosition(); - controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000); - } + + butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); + + if (butFF != null) { + butFF.setOnClickListener(v -> { + int curr = controller.getPosition(); + controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000); }); - butRev.setOnLongClickListener(new View.OnLongClickListener() { + butFF.setOnLongClickListener(new View.OnLongClickListener() { int choice; @Override public boolean onLongClick(View v) { int checked = 0; - int rewindSecs = UserPreferences.getRewindSecs(); + int rewindSecs = UserPreferences.getFastFowardSecs(); final int[] values = getResources().getIntArray(R.array.seek_delta_values); final String[] choices = new String[values.length]; - for(int i=0; i < values.length; i++) { + for (int i = 0; i < values.length; i++) { if (rewindSecs == values[i]) { checked = i; } - choices[i] = String.valueOf(values[i]) + " " - + getString(R.string.time_unit_seconds); + choices[i] = String.valueOf(values[i]) + " " + getString(R.string.time_seconds); } choice = values[checked]; AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this); - builder.setTitle(R.string.pref_rewind); + builder.setTitle(R.string.pref_fast_forward); builder.setSingleChoiceItems(choices, checked, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - choice = values[which]; - } + (dialog, which) -> { + choice = values[which]; }); builder.setNegativeButton(R.string.cancel_label, null); - builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - UserPreferences.setPrefRewindSecs(choice); - txtvRev.setText(String.valueOf(choice)); - } + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setPrefFastForwardSecs(choice); + txtvFF.setText(String.valueOf(choice)); }); builder.create().show(); return true; @@ -545,6 +841,11 @@ public abstract class MediaplayerActivity extends ActionBarActivity }); } + if (butSkip != null) { + butSkip.setOnClickListener(v -> { + sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); + }); + } } protected abstract int getContentViewResourceId(); @@ -555,12 +856,9 @@ public abstract class MediaplayerActivity extends ActionBarActivity errorDialog .setMessage(MediaPlayerError.getErrorString(this, errorCode)); errorDialog.setNeutralButton("OK", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - finish(); - } + (dialog, which) -> { + dialog.dismiss(); + finish(); } ); errorDialog.create().show(); @@ -569,11 +867,20 @@ public abstract class MediaplayerActivity extends ActionBarActivity float prog; @Override - public void onProgressChanged(SeekBar seekBar, int progress, - boolean fromUser) { + public void onProgressChanged (SeekBar seekBar,int progress, boolean fromUser) { if (controller != null) { - prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, - txtvPosition); + prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, txtvPosition); + if (showTimeLeft && prog != 0) { + int duration = controller.getDuration(); + String length = "-" + Converter.getDurationStringLong(duration - (int) (prog * duration)); + txtvLength.setText(length); + } + } + } + + private void updateButPlaybackSpeed() { + if (controller != null && butPlaybackSpeed != null) { + butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed() + "x"); } } @@ -591,4 +898,23 @@ public abstract class MediaplayerActivity extends ActionBarActivity } } + private void checkFavorite() { + Playable playable = controller.getMedia(); + if (playable != null && playable instanceof FeedMedia) { + FeedItem feedItem = ((FeedMedia) playable).getItem(); + if (feedItem != null) { + Observable.fromCallable(() -> DBReader.getFeedItem(feedItem.getId())) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(item -> { + boolean isFav = item.isTagged(FeedItem.TAG_FAVORITE); + if(isFavorite != isFav) { + isFavorite = isFav; + invalidateOptionsMenu(); + } + }); + } + } + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 2b1b13ae6..8c2b7f838 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -1,21 +1,36 @@ package de.danoeh.antennapod.activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.os.Bundle; +import android.os.Looper; +import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import com.bumptech.glide.Glide; import org.apache.commons.lang3.StringUtils; -import org.xml.sax.SAXException; +import org.jsoup.Jsoup; +import org.jsoup.examples.HtmlToPlainText; +import org.jsoup.nodes.Document; import java.io.File; import java.io.IOException; @@ -24,16 +39,23 @@ import java.util.Date; import java.util.List; import java.util.Map; -import javax.xml.parsers.ParserConfigurationException; - import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; +import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.service.download.HttpDownloader; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.syndication.handler.FeedHandler; import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult; import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException; @@ -43,6 +65,12 @@ import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.URLChecker; import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer; import de.danoeh.antennapod.dialog.AuthenticationDialog; +import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Downloads a feed from a feed URL and parses it. Subclasses can display the @@ -52,27 +80,62 @@ import de.danoeh.antennapod.dialog.AuthenticationDialog; * If the feed cannot be downloaded or parsed, an error dialog will be displayed * and the activity will finish as soon as the error dialog is closed. */ -public abstract class OnlineFeedViewActivity extends ActionBarActivity { +public class OnlineFeedViewActivity extends ActionBarActivity { + private static final String TAG = "OnlineFeedViewActivity"; + public static final String ARG_FEEDURL = "arg.feedurl"; - /** - * Optional argument: specify a title for the actionbar. - */ + // Optional argument: specify a title for the actionbar. public static final String ARG_TITLE = "title"; + private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE; + public static final int RESULT_ERROR = 2; + private volatile List<Feed> feeds; private Feed feed; - private Map<String, String> alternateFeedUrls; + private String selectedDownloadUrl; private Downloader downloader; private boolean isPaused; + private Dialog dialog; + + private Button subscribeButton; + + private Subscription download; + private Subscription parser; + private Subscription updater; + + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + setSubscribeButtonState(feed); + } + + private EventDistributor.EventListener listener = new EventDistributor.EventListener() { + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) { + updater = Observable.fromCallable(() -> DBReader.getFeedList()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(feeds -> { + OnlineFeedViewActivity.this.feeds = feeds; + setSubscribeButtonState(feed); + } + ); + } else if ((arg & EVENTS) != 0) { + setSubscribeButtonState(feed); + } + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); if (getIntent() != null && getIntent().hasExtra(ARG_TITLE)) { getSupportActionBar().setTitle(getIntent().getStringExtra(ARG_TITLE)); @@ -83,11 +146,10 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { final String feedUrl; if (getIntent().hasExtra(ARG_FEEDURL)) { feedUrl = getIntent().getStringExtra(ARG_FEEDURL); - } else if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_SEND) - || StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { - feedUrl = (StringUtils.equals(getIntent().getAction(), Intent.ACTION_SEND)) + } else if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_SEND) + || TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { + feedUrl = (TextUtils.equals(getIntent().getAction(), Intent.ACTION_SEND)) ? getIntent().getStringExtra(Intent.EXTRA_TEXT) : getIntent().getDataString(); - getSupportActionBar().setTitle(R.string.add_new_feed_label); } else { throw new IllegalArgumentException("Activity must be started with feedurl argument!"); @@ -102,16 +164,64 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { } } + /** + * Displays a progress indicator. + */ + private void setLoadingLayout() { + RelativeLayout rl = new RelativeLayout(this); + RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT); + + ProgressBar pb = new ProgressBar(this); + pb.setIndeterminate(true); + RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); + rl.addView(pb, pbLayoutParams); + addContentView(rl, rlLayoutParams); + } + @Override protected void onResume() { super.onResume(); isPaused = false; + EventDistributor.getInstance().register(listener); + EventBus.getDefault().register(this); } @Override protected void onPause() { super.onPause(); isPaused = true; + EventDistributor.getInstance().unregister(listener); + EventBus.getDefault().unregister(this); + } + + @Override + protected void onStop() { + super.onStop(); + if (downloader != null && !downloader.isFinished()) { + downloader.cancel(); + } + if(dialog != null && dialog.isShowing()) { + dialog.dismiss(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if(updater != null) { + updater.unsubscribe(); + } + if(download != null) { + download.unsubscribe(); + } + if(parser != null) { + parser.unsubscribe(); + } } @Override @@ -123,14 +233,6 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { } } - @Override - protected void onStop() { - super.onStop(); - if (downloader != null && !downloader.isFinished()) { - downloader.cancel(); - } - } - private void resetIntent(String url, String title) { Intent intent = new Intent(); intent.putExtra(ARG_FEEDURL, url); @@ -138,41 +240,19 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { setIntent(intent); } - - private void onDownloadCompleted(final Downloader downloader) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - Log.d(TAG, "Download was completed"); - DownloadStatus status = downloader.getResult(); - if (status != null) { - if (!status.isCancelled()) { - if (status.isSuccessful()) { - parseFeed(); - } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { - if (!isFinishing() && !isPaused) { - Dialog dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this, - R.string.authentication_notification_title, downloader.getDownloadRequest().getSource()); - dialog.show(); - } - } else { - String errorMsg = status.getReason().getErrorString( - OnlineFeedViewActivity.this); - if (errorMsg != null - && status.getReasonDetailed() != null) { - errorMsg += " (" + status.getReasonDetailed() + ")"; - } - showErrorDialog(errorMsg); - } - } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + Intent destIntent = new Intent(this, MainActivity.class); + if (NavUtils.shouldUpRecreateTask(this, destIntent)) { + startActivity(destIntent); } else { - Log.wtf(TAG, "DownloadStatus returned by Downloader was null"); - finish(); + NavUtils.navigateUpFromSameTask(this); } - } - }); - + return true; + } + return super.onOptionsItemSelected(item); } private void startFeedDownload(String url, String username, String password) { @@ -180,139 +260,232 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { url = URLChecker.prepareURL(url); feed = new Feed(url, new Date(0)); if (username != null && password != null) { - feed.setPreferences(new FeedPreferences(0, false, username, password)); + feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, username, password)); } String fileUrl = new File(getExternalCacheDir(), FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); feed.setFile_url(fileUrl); final DownloadRequest request = new DownloadRequest(feed.getFile_url(), - feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, true, null); - downloader = new HttpDownloader(request); - new Thread() { - @Override - public void run() { - loadData(); - downloader.call(); - onDownloadCompleted(downloader); - } - }.start(); - - - } - - /** - * Displays a progress indicator. - */ - private void setLoadingLayout() { - RelativeLayout rl = new RelativeLayout(this); - RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT); + feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, + true, null); - ProgressBar pb = new ProgressBar(this); - pb.setIndeterminate(true); - RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); - rl.addView(pb, pbLayoutParams); - addContentView(rl, rlLayoutParams); + download = Observable.create(new Observable.OnSubscribe<DownloadStatus>() { + @Override + public void call(Subscriber<? super DownloadStatus> subscriber) { + feeds = DBReader.getFeedList(); + downloader = new HttpDownloader(request); + downloader.call(); + Log.d(TAG, "Download was completed"); + subscriber.onNext(downloader.getResult()); + subscriber.onCompleted(); + } + }) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(status -> { + if (status != null) { + if (!status.isCancelled()) { + if (status.isSuccessful()) { + parseFeed(); + } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { + if (!isFinishing() && !isPaused) { + dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this, + R.string.authentication_notification_title, downloader.getDownloadRequest().getSource()); + dialog.show(); + } + } else { + String errorMsg = status.getReason().getErrorString(OnlineFeedViewActivity.this); + if (errorMsg != null && status.getReasonDetailed() != null) { + errorMsg += " (" + status.getReasonDetailed() + ")"; + } + showErrorDialog(errorMsg); + } + } + } else { + Log.wtf(TAG, "DownloadStatus returned by Downloader was null"); + finish(); + } + }); } private void parseFeed() { if (feed == null || feed.getFile_url() == null && feed.isDownloaded()) { - throw new IllegalStateException( - "feed must be non-null and downloaded when parseFeed is called"); + throw new IllegalStateException("feed must be non-null and downloaded when parseFeed is called"); } - Log.d(TAG, "Parsing feed"); - Thread thread = new Thread() { - + parser = Observable.create(new Observable.OnSubscribe<FeedHandlerResult>() { @Override - public void run() { - String reasonDetailed = ""; - boolean successful = false; - FeedHandler handler = new FeedHandler(); - try { - FeedHandlerResult result = handler.parseFeed(feed); - feed = result.feed; - alternateFeedUrls = result.alternateFeedUrls; - successful = true; - } catch (SAXException e) { - e.printStackTrace(); - reasonDetailed = e.getMessage(); - } catch (IOException e) { - e.printStackTrace(); - reasonDetailed = e.getMessage(); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - reasonDetailed = e.getMessage(); - } catch (UnsupportedFeedtypeException e) { - Log.d(TAG, "Unsupported feed type detected"); - if (StringUtils.equalsIgnoreCase("html", e.getRootElement())) { - if (showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url())) { - return; + public void call(Subscriber<? super FeedHandlerResult> subscriber) { + FeedHandler handler = new FeedHandler(); + try { + FeedHandlerResult result = handler.parseFeed(feed); + subscriber.onNext(result); + } catch (UnsupportedFeedtypeException e) { + Log.d(TAG, "Unsupported feed type detected"); + if (TextUtils.equals("html", e.getRootElement().toLowerCase())) { + showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url()); + } else { + subscriber.onError(e); + } + } catch (Exception e) { + subscriber.onError(e); + } finally { + boolean rc = new File(feed.getFile_url()).delete(); + Log.d(TAG, "Deleted feed source file. Result: " + rc); + subscriber.onCompleted(); } - } else { - e.printStackTrace(); - reasonDetailed = e.getMessage(); } - } finally { - boolean rc = new File(feed.getFile_url()).delete(); - Log.d(TAG, "Deleted feed source file. Result: " + rc); - } - - if (successful) { - beforeShowFeedInformation(feed, alternateFeedUrls); - runOnUiThread(new Runnable() { - @Override - public void run() { - showFeedInformation(feed, alternateFeedUrls); - } - }); - } else { - final String errorMsg = - DownloadError.ERROR_PARSER_EXCEPTION.getErrorString( - OnlineFeedViewActivity.this) - + " (" + reasonDetailed + ")"; - runOnUiThread(new Runnable() { - - @Override - public void run() { - showErrorDialog(errorMsg); - } - }); - } - } - }; - thread.start(); - } - - /** - * Can be used to load data asynchronously. - */ - protected void loadData() { - + }) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + beforeShowFeedInformation(result.feed); + showFeedInformation(result.feed, result.alternateFeedUrls); + }, error -> { + String errorMsg = DownloadError.ERROR_PARSER_EXCEPTION.getErrorString( + OnlineFeedViewActivity.this) + " (" + error.getMessage() + ")"; + showErrorDialog(errorMsg); + }); } /** * Called after the feed has been downloaded and parsed and before showFeedInformation is called. * This method is executed on a background thread */ - protected void beforeShowFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) { - + private void beforeShowFeedInformation(Feed feed) { + // remove HTML tags from descriptions + Log.d(TAG, "Removing HTML from shownotes"); + if (feed.getItems() != null) { + HtmlToPlainText formatter = new HtmlToPlainText(); + for (FeedItem item : feed.getItems()) { + if (item.getDescription() != null) { + Document description = Jsoup.parse(item.getDescription()); + item.setDescription(StringUtils.trim(formatter.getPlainText(description))); + } + } + } } /** * Called when feed parsed successfully. * This method is executed on the GUI thread. */ - protected void showFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) { + private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) { + setContentView(R.layout.listview_activity); + + this.feed = feed; + this.selectedDownloadUrl = feed.getDownload_url(); + EventDistributor.getInstance().register(listener); + ListView listView = (ListView) findViewById(R.id.listview); + LayoutInflater inflater = (LayoutInflater) + getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View header = inflater.inflate(R.layout.onlinefeedview_header, listView, false); + listView.addHeaderView(header); + + listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems())); + + ImageView cover = (ImageView) header.findViewById(R.id.imgvCover); + TextView title = (TextView) header.findViewById(R.id.txtvTitle); + TextView author = (TextView) header.findViewById(R.id.txtvAuthor); + TextView description = (TextView) header.findViewById(R.id.txtvDescription); + Spinner spAlternateUrls = (Spinner) header.findViewById(R.id.spinnerAlternateUrls); + + subscribeButton = (Button) header.findViewById(R.id.butSubscribe); + + if (feed.getImage() != null && StringUtils.isNotBlank(feed.getImage().getDownload_url())) { + Glide.with(this) + .load(feed.getImage().getDownload_url()) + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(cover); + } + + title.setText(feed.getTitle()); + author.setText(feed.getAuthor()); + description.setText(feed.getDescription()); + + subscribeButton.setOnClickListener(v -> { + try { + Feed f = new Feed(selectedDownloadUrl, new Date(0), feed.getTitle()); + f.setPreferences(feed.getPreferences()); + this.feed = f; + + DownloadRequester.getInstance().downloadFeed(this, f); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(OnlineFeedViewActivity.this, + e.getMessage()); + } + setSubscribeButtonState(feed); + }); + + if (alternateFeedUrls.isEmpty()) { + spAlternateUrls.setVisibility(View.GONE); + } else { + spAlternateUrls.setVisibility(View.VISIBLE); + + final List<String> alternateUrlsList = new ArrayList<>(); + final List<String> alternateUrlsTitleList = new ArrayList<>(); + + alternateUrlsList.add(feed.getDownload_url()); + alternateUrlsTitleList.add(feed.getTitle()); + + alternateUrlsList.addAll(alternateFeedUrls.keySet()); + for (String url : alternateFeedUrls.keySet()) { + alternateUrlsTitleList.add(alternateFeedUrls.get(url)); + } + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, alternateUrlsTitleList); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spAlternateUrls.setAdapter(adapter); + spAlternateUrls.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + selectedDownloadUrl = alternateUrlsList.get(position); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + + } + }); + } + setSubscribeButtonState(feed); + } + + private void setSubscribeButtonState(Feed feed) { + if (subscribeButton != null && feed != null) { + if (DownloadRequester.getInstance().isDownloadingFile(feed.getDownload_url())) { + subscribeButton.setEnabled(false); + subscribeButton.setText(R.string.downloading_label); + } else if (feedInFeedlist(feed)) { + subscribeButton.setEnabled(false); + subscribeButton.setText(R.string.subscribed_label); + } else { + subscribeButton.setEnabled(true); + subscribeButton.setText(R.string.subscribe_label); + } + } + } + + private boolean feedInFeedlist(Feed feed) { + if (feeds == null || feed == null) { + return false; + } + for (Feed f : feeds) { + if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) { + return true; + } + } + return false; } private void showErrorDialog(String errorMsg) { + assert(Looper.myLooper() == Looper.getMainLooper()); // run on UI thread if (!isFinishing() && !isPaused) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.error_label); @@ -322,85 +495,71 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { builder.setMessage(R.string.error_msg_prefix); } builder.setNeutralButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } + (dialog, which) -> { + dialog.cancel(); } ); - builder.setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - setResult(RESULT_ERROR); - finish(); - } + builder.setOnCancelListener(dialog -> { + setResult(RESULT_ERROR); + finish(); }); - builder.show(); + if(dialog != null && dialog.isShowing()) { + dialog.dismiss(); + } + dialog = builder.show(); } } - private boolean showFeedDiscoveryDialog(File feedFile, String baseUrl) { + private void showFeedDiscoveryDialog(File feedFile, String baseUrl) { FeedDiscoverer fd = new FeedDiscoverer(); final Map<String, String> urlsMap; try { urlsMap = fd.findLinks(feedFile, baseUrl); if (urlsMap == null || urlsMap.isEmpty()) { - return false; + return; } } catch (IOException e) { e.printStackTrace(); - return false; + return; } - runOnUiThread(new Runnable() { - @Override - public void run() { - if (isPaused || isFinishing()) { - return; - } + if (isPaused || isFinishing()) { + return; + } - final List<String> titles = new ArrayList<String>(); - final List<String> urls = new ArrayList<String>(); + final List<String> titles = new ArrayList<>(); + final List<String> urls = new ArrayList<>(); - urls.addAll(urlsMap.keySet()); - for (String url : urls) { - titles.add(urlsMap.get(url)); - } + urls.addAll(urlsMap.keySet()); + for (String url : urls) { + titles.add(urlsMap.get(url)); + } - final ArrayAdapter<String> adapter = new ArrayAdapter<String>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); - DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String selectedUrl = urls.get(which); - dialog.dismiss(); - resetIntent(selectedUrl, titles.get(which)); - FeedPreferences prefs = feed.getPreferences(); - if(prefs != null) { - startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword()); - } else { - startFeedDownload(selectedUrl, null, null); - } - } - }; - - AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this) - .setTitle(R.string.feeds_label) - .setCancelable(true) - .setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - finish(); - } - }) - .setAdapter(adapter, onClickListener); - ab.show(); + final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); + DialogInterface.OnClickListener onClickListener = (dialog, which) -> { + String selectedUrl = urls.get(which); + dialog.dismiss(); + resetIntent(selectedUrl, titles.get(which)); + FeedPreferences prefs = feed.getPreferences(); + if(prefs != null) { + startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword()); + } else { + startFeedDownload(selectedUrl, null, null); } - }); + }; + AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this) + .setTitle(R.string.feeds_label) + .setCancelable(true) + .setOnCancelListener(dialog -> finish()) + .setAdapter(adapter, onClickListener); - return true; + runOnUiThread(() -> { + if(dialog != null && dialog.isShowing()) { + dialog.dismiss(); + } + dialog = ab.show(); + }); } private class FeedViewAuthenticationDialog extends AuthenticationDialog { @@ -423,4 +582,5 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { startFeedDownload(feedUrl, username, password); } } + } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java index e42072ead..46e5f0e8e 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java @@ -1,18 +1,27 @@ package de.danoeh.antennapod.activity; -import android.app.AlertDialog; +import android.net.Uri; import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.util.Log; + import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.LangUtils; import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; import java.io.InputStreamReader; +import java.io.Reader; import java.net.URL; /** Lets the user start the OPML-import process. */ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { - @Override + private static final String TAG = "OpmlImportFromIntentAct"; + + + @Override protected void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); @@ -20,10 +29,10 @@ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); try { - URL mOpmlURL = new URL(getIntent().getData().toString()); - BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream(), - LangUtils.UTF_8)); - startImport(in); + Uri uri = getIntent().getData(); + + Reader mReader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); + startImport(mReader); } catch (Exception e) { new AlertDialog.Builder(this).setMessage("Cannot open XML - Reason: " + e.getMessage()).show(); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java index c1bbb7e52..6e3991739 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java @@ -2,14 +2,12 @@ package de.danoeh.antennapod.activity; import android.content.ActivityNotFoundException; import android.content.Intent; -import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; @@ -18,11 +16,10 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.io.Reader; -import java.util.List; -import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.LangUtils; import de.danoeh.antennapod.core.util.StorageUtils; @@ -31,7 +28,7 @@ import de.danoeh.antennapod.core.util.StorageUtils; */ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { - private static final String TAG = "OpmlImportFromPathActivity"; + private static final String TAG = "OpmlImportFromPathAct"; private static final int CHOOSE_OPML_FILE = 1; @@ -53,59 +50,44 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { final TextView txtvHeaderExplanation3 = (TextView) findViewById(R.id.txtvHeadingExplanation3); Button butChooseFilesystem = (Button) findViewById(R.id.butChooseFileFromFilesystem); - butChooseFilesystem.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - chooseFileFromFilesystem(); - } - - }); + butChooseFilesystem.setOnClickListener(v -> chooseFileFromFilesystem()); Button butChooseExternal = (Button) findViewById(R.id.butChooseFileFromExternal); - butChooseExternal.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - chooseFileFromExternal(); - } - }); - - int nextOption = 1; + butChooseExternal.setOnClickListener(v -> chooseFileFromExternal()); + + int nextOption = 1; + String optionLabel = getString(R.string.opml_import_option); intentPickAction = new Intent(Intent.ACTION_PICK); intentPickAction.setData(Uri.parse("file://")); - List<ResolveInfo> intentActivities = getPackageManager() - .queryIntentActivities(intentPickAction, CHOOSE_OPML_FILE); - if(intentActivities.size() == 0) { - intentPickAction.setData(null); - intentActivities = getPackageManager() - .queryIntentActivities(intentPickAction, CHOOSE_OPML_FILE); - if(intentActivities.size() == 0) { - txtvHeaderExplanation1.setVisibility(View.GONE); - txtvExplanation1.setVisibility(View.GONE); - findViewById(R.id.divider1).setVisibility(View.GONE); - butChooseFilesystem.setVisibility(View.GONE); - } + + if(false == IntentUtils.isCallable(getApplicationContext(), intentPickAction)) { + intentPickAction.setData(null); + if(false == IntentUtils.isCallable(getApplicationContext(), intentPickAction)) { + txtvHeaderExplanation1.setVisibility(View.GONE); + txtvExplanation1.setVisibility(View.GONE); + findViewById(R.id.divider1).setVisibility(View.GONE); + butChooseFilesystem.setVisibility(View.GONE); } + } if(txtvExplanation1.getVisibility() == View.VISIBLE) { - txtvHeaderExplanation1.setText("Option " + nextOption); - nextOption++; - } + txtvHeaderExplanation1.setText(String.format(optionLabel, nextOption)); + nextOption++; + } intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); intentGetContentAction.setType("*/*"); - intentActivities = getPackageManager() - .queryIntentActivities(intentGetContentAction, CHOOSE_OPML_FILE); - if(intentActivities.size() == 0) { - txtvHeaderExplanation2.setVisibility(View.GONE); - txtvExplanation2.setVisibility(View.GONE); - findViewById(R.id.divider2).setVisibility(View.GONE); - butChooseExternal.setVisibility(View.GONE); - } else { - txtvHeaderExplanation2.setText("Option " + nextOption); - nextOption++; - } + if(false == IntentUtils.isCallable(getApplicationContext(), intentGetContentAction)) { + txtvHeaderExplanation2.setVisibility(View.GONE); + txtvExplanation2.setVisibility(View.GONE); + findViewById(R.id.divider2).setVisibility(View.GONE); + butChooseExternal.setVisibility(View.GONE); + } else { + txtvHeaderExplanation2.setText(String.format(optionLabel, nextOption)); + nextOption++; + } - txtvHeaderExplanation3.setText("Option " + nextOption); + txtvHeaderExplanation3.setText(String.format(optionLabel, nextOption)); } @Override @@ -137,7 +119,7 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { try { mReader = new InputStreamReader(new FileInputStream(file), LangUtils.UTF_8); - if (BuildConfig.DEBUG) Log.d(TAG, "Parsing " + file.toString()); + Log.d(TAG, "Parsing " + file.toString()); startImport(mReader); } catch (FileNotFoundException e) { Log.d(TAG, "File not found which really should be there"); @@ -172,8 +154,14 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && requestCode == CHOOSE_OPML_FILE) { - String filename = data.getData().getPath(); - startImport(new File(filename)); + Uri uri = data.getData(); + + try { + Reader mReader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); + startImport(mReader); + } catch (FileNotFoundException e) { + Log.d(TAG, "File not found"); + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index 3802de2a6..80ccb7c99 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -14,6 +14,8 @@ import android.view.MenuItem; import android.view.ViewGroup; import android.widget.FrameLayout; +import java.lang.ref.WeakReference; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.preferences.PreferenceController; @@ -26,7 +28,7 @@ public class PreferenceActivity extends ActionBarActivity { private PreferenceController preferenceController; private MainFragment prefFragment; - private static PreferenceActivity instance; + private static WeakReference<PreferenceActivity> instance; private final PreferenceController.PreferenceUI preferenceUI = new PreferenceController.PreferenceUI() { @@ -45,9 +47,12 @@ public class PreferenceActivity extends ActionBarActivity { @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override protected void onCreate(Bundle savedInstanceState) { + // This must be the FIRST thing we do, otherwise other code may not have the + // reference it needs + instance = new WeakReference<PreferenceActivity>(this); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); - instance = this; ActionBar ab = getSupportActionBar(); if (ab != null) { @@ -60,10 +65,13 @@ public class PreferenceActivity extends ActionBarActivity { root.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); setContentView(root); - prefFragment = new MainFragment(); - getFragmentManager().beginTransaction().replace(R.id.content, prefFragment).commit(); + // we need to create the PreferenceController before the MainFragment + // since the MainFragment depends on the preferenceController already being created preferenceController = new PreferenceController(preferenceUI); + + prefFragment = new MainFragment(); + getFragmentManager().beginTransaction().replace(R.id.content, prefFragment).commit(); } @Override @@ -96,13 +104,19 @@ public class PreferenceActivity extends ActionBarActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); - instance.preferenceController.onCreate(); + PreferenceActivity activity = instance.get(); + if(activity != null && activity.preferenceController != null) { + activity.preferenceController.onCreate(); + } } @Override public void onResume() { super.onResume(); - instance.preferenceController.onResume(); + PreferenceActivity activity = instance.get(); + if(activity != null && activity.preferenceController != null) { + activity.preferenceController.onResume(); + } } } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java index 173bec6b2..b02e82f0b 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java @@ -6,10 +6,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; +import android.text.TextUtils; import android.util.Log; -import org.apache.commons.lang3.StringUtils; - import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -40,7 +39,7 @@ public class StorageErrorActivity extends ActionBarActivity { @Override protected void onResume() { super.onResume(); - if (StorageUtils.storageAvailable(this)) { + if (StorageUtils.storageAvailable()) { leaveErrorState(); } else { registerReceiver(mediaUpdate, new IntentFilter( @@ -57,7 +56,7 @@ public class StorageErrorActivity extends ActionBarActivity { @Override public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), Intent.ACTION_MEDIA_MOUNTED)) { + if (TextUtils.equals(intent.getAction(), Intent.ACTION_MEDIA_MOUNTED)) { if (intent.getBooleanExtra("read-only", true)) { if (BuildConfig.DEBUG) Log.d(TAG, "Media was mounted; Finishing activity"); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java index 60eb290b5..ee459dbc6 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -3,9 +3,9 @@ package de.danoeh.antennapod.activity; import android.annotation.SuppressLint; import android.content.Intent; import android.graphics.drawable.ColorDrawable; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.support.v4.view.WindowCompat; import android.util.Log; import android.util.Pair; @@ -19,7 +19,9 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.SeekBar; -import de.danoeh.antennapod.BuildConfig; +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.service.playback.PlaybackService; @@ -39,8 +41,12 @@ public class VideoplayerActivity extends MediaplayerActivity { */ private boolean videoControlsShowing = true; private boolean videoSurfaceCreated = false; - private VideoControlsHider videoControlsToggler; + private VideoControlsHider videoControlsHider = new VideoControlsHider(this); + + private AtomicBoolean isSetup = new AtomicBoolean(false); + + private LinearLayout controls; private LinearLayout videoOverlay; private AspectRatioVideoView videoview; private ProgressBar progressIndicator; @@ -61,25 +67,12 @@ public class VideoplayerActivity extends MediaplayerActivity { } @Override - protected void onPause() { - super.onPause(); - if (videoControlsToggler != null) { - videoControlsToggler.cancel(true); - } - if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) { - controller.pause(); - } - } - - @Override protected void onResume() { super.onResume(); if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_VIEW)) { Intent intent = getIntent(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Received VIEW intent: " - + intent.getData().getPath()); + Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath()); ExternalMedia media = new ExternalMedia(intent.getData().getPath(), MediaType.VIDEO); Intent launchIntent = new Intent(this, PlaybackService.class); @@ -94,6 +87,22 @@ public class VideoplayerActivity extends MediaplayerActivity { } @Override + protected void onPause() { + super.onPause(); + videoControlsHider.stop(); + if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) { + controller.pause(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + videoControlsHider.stop(); + videoControlsHider = null; + } + + @Override protected boolean loadMediaInfo() { if (!super.loadMediaInfo()) { return false; @@ -104,14 +113,17 @@ public class VideoplayerActivity extends MediaplayerActivity { getSupportActionBar().setTitle(media.getFeedTitle()); return true; } - return false; } @Override protected void setupGUI() { + if(isSetup.getAndSet(true)) { + return; + } super.setupGUI(); getSupportActionBar().setDisplayHomeAsUpEnabled(true); + controls = (LinearLayout) findViewById(R.id.controls); videoOverlay = (LinearLayout) findViewById(R.id.overlay); videoview = (AspectRatioVideoView) findViewById(R.id.videoview); progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator); @@ -133,14 +145,11 @@ public class VideoplayerActivity extends MediaplayerActivity { @Override protected void onAwaitingVideoSurface() { if (videoSurfaceCreated) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Videosurface already created, setting videosurface now"); + Log.d(TAG, "Videosurface already created, setting videosurface now"); Pair<Integer, Integer> videoSize = controller.getVideoSize(); if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second); + Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second); videoview.setVideoSize(videoSize.first, videoSize.second); } else { Log.e(TAG, "Could not determine video size"); @@ -156,7 +165,6 @@ public class VideoplayerActivity extends MediaplayerActivity { } else { progressIndicator.setVisibility(View.INVISIBLE); } - } @Override @@ -164,38 +172,23 @@ public class VideoplayerActivity extends MediaplayerActivity { progressIndicator.setVisibility(View.INVISIBLE); } - View.OnTouchListener onVideoviewTouched = new View.OnTouchListener() { - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (videoControlsToggler != null) { - videoControlsToggler.cancel(true); - } - toggleVideoControlsVisibility(); - if (videoControlsShowing) { - setupVideoControlsToggler(); - } - - return true; - } else { - return false; + View.OnTouchListener onVideoviewTouched = (v, event) -> { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + videoControlsHider.stop(); + toggleVideoControlsVisibility(); + if (videoControlsShowing) { + setupVideoControlsToggler(); } + return true; + } else { + return false; } }; @SuppressLint("NewApi") void setupVideoControlsToggler() { - if (videoControlsToggler != null) { - videoControlsToggler.cancel(true); - } - videoControlsToggler = new VideoControlsHider(); - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - videoControlsToggler - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - videoControlsToggler.execute(); - } + videoControlsHider.stop(); + videoControlsHider.start(); } private void toggleVideoControlsVisibility() { @@ -209,46 +202,6 @@ public class VideoplayerActivity extends MediaplayerActivity { videoControlsShowing = !videoControlsShowing; } - /** - * Hides the videocontrols after a certain period of time. - */ - public class VideoControlsHider extends AsyncTask<Void, Void, Void> { - @Override - protected void onCancelled() { - videoControlsToggler = null; - } - - @Override - protected void onPostExecute(Void result) { - videoControlsToggler = null; - } - - private static final int WAITING_INTERVALL = 5000; - private static final String TAG = "VideoControlsToggler"; - - @Override - protected void onProgressUpdate(Void... values) { - if (videoControlsShowing) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Hiding video controls"); - getSupportActionBar().hide(); - hideVideoControls(); - videoControlsShowing = false; - } - } - - @Override - protected Void doInBackground(Void... params) { - try { - Thread.sleep(WAITING_INTERVALL); - } catch (InterruptedException e) { - return null; - } - publishProgress(); - return null; - } - - } private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -259,15 +212,13 @@ public class VideoplayerActivity extends MediaplayerActivity { @Override public void surfaceCreated(SurfaceHolder holder) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Videoview holder created"); + Log.d(TAG, "Videoview holder created"); videoSurfaceCreated = true; if (controller.getStatus() == PlayerStatus.PLAYING) { if (controller.serviceAvailable()) { controller.setVideoSurface(holder); } else { - Log.e(TAG, - "Could'nt attach surface to mediaplayer - reference to service was null"); + Log.e(TAG, "Could'nt attach surface to mediaplayer - reference to service was null"); } } @@ -275,8 +226,7 @@ public class VideoplayerActivity extends MediaplayerActivity { @Override public void surfaceDestroyed(SurfaceHolder holder) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Videosurface was destroyed"); + Log.d(TAG, "Videosurface was destroyed"); videoSurfaceCreated = false; controller.notifyVideoSurfaceAbandoned(); } @@ -286,9 +236,7 @@ public class VideoplayerActivity extends MediaplayerActivity { @Override protected void onReloadNotification(int notificationCode) { if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "ReloadNotification received, switching to Audioplayer now"); + Log.d(TAG, "ReloadNotification received, switching to Audioplayer now"); finish(); startActivity(new Intent(this, AudioplayerActivity.class)); } @@ -297,9 +245,7 @@ public class VideoplayerActivity extends MediaplayerActivity { @Override public void onStartTrackingTouch(SeekBar seekBar) { super.onStartTrackingTouch(seekBar); - if (videoControlsToggler != null) { - videoControlsToggler.cancel(true); - } + videoControlsHider.stop(); } @Override @@ -321,12 +267,11 @@ public class VideoplayerActivity extends MediaplayerActivity { @SuppressLint("NewApi") private void showVideoControls() { videoOverlay.setVisibility(View.VISIBLE); - butPlay.setVisibility(View.VISIBLE); - final Animation animation = AnimationUtils.loadAnimation(this, - R.anim.fade_in); + controls.setVisibility(View.VISIBLE); + final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_in); if (animation != null) { videoOverlay.startAnimation(animation); - butPlay.startAnimation(animation); + controls.startAnimation(animation); } if (Build.VERSION.SDK_INT >= 14) { videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); @@ -335,11 +280,10 @@ public class VideoplayerActivity extends MediaplayerActivity { @SuppressLint("NewApi") private void hideVideoControls() { - final Animation animation = AnimationUtils.loadAnimation(this, - R.anim.fade_out); + final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_out); if (animation != null) { videoOverlay.startAnimation(animation); - butPlay.startAnimation(animation); + controls.startAnimation(animation); } if (Build.VERSION.SDK_INT >= 14) { int videoviewFlag = (Build.VERSION.SDK_INT >= 16) ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0; @@ -348,7 +292,7 @@ public class VideoplayerActivity extends MediaplayerActivity { videoOverlay.setFitsSystemWindows(true); } videoOverlay.setVisibility(View.GONE); - butPlay.setVisibility(View.GONE); + controls.setVisibility(View.GONE); } @Override @@ -356,7 +300,6 @@ public class VideoplayerActivity extends MediaplayerActivity { return R.layout.videoplayer_activity; } - @Override protected void setScreenOn(boolean enable) { super.setScreenOn(enable); @@ -366,4 +309,38 @@ public class VideoplayerActivity extends MediaplayerActivity { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } + + private static class VideoControlsHider extends Handler { + + private static final int DELAY = 5000; + + private WeakReference<VideoplayerActivity> activity; + + public VideoControlsHider(VideoplayerActivity activity) { + this.activity = new WeakReference<>(activity); + } + + private final Runnable hideVideoControls = () -> { + VideoplayerActivity vpa = activity.get(); + if(vpa == null) { + return; + } + if (vpa.videoControlsShowing) { + Log.d(TAG, "Hiding video controls"); + vpa.getSupportActionBar().hide(); + vpa.hideVideoControls(); + vpa.videoControlsShowing = false; + } + }; + + public void start() { + this.postDelayed(hideVideoControls, DELAY); + } + + public void stop() { + this.removeCallbacks(hideVideoControls); + } + + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java index 511115b3c..28c2b7206 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java @@ -97,7 +97,7 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); + finish(); return true; } return super.onOptionsItemSelected(item); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java b/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java index 8e347a819..1a8f0a67a 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java @@ -10,9 +10,7 @@ import org.apache.commons.lang3.Validate; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.LongList; /** * Utility methods for the action button that is displayed on the right hand side @@ -27,7 +25,7 @@ public class ActionButtonUtils { public ActionButtonUtils(Context context) { Validate.notNull(context); - this.context = context; + this.context = context.getApplicationContext(); drawables = context.obtainStyledAttributes(new int[] { R.attr.av_play, R.attr.navigation_cancel, @@ -49,7 +47,7 @@ public class ActionButtonUtils { * Sets the displayed bitmap and content description of the given * action button so that it matches the state of the FeedItem. */ - public void configureActionButton(ImageButton butSecondary, FeedItem item) { + public void configureActionButton(ImageButton butSecondary, FeedItem item, boolean isInQueue) { Validate.isTrue(butSecondary != null && item != null, "butSecondary or item was null"); final FeedMedia media = item.getMedia(); @@ -64,9 +62,8 @@ public class ActionButtonUtils { butSecondary.setContentDescription(context.getString(labels[1])); } else { // item is not downloaded and not being downloaded - LongList queueIds = DBReader.getQueueIDList(context); if(DefaultActionButtonCallback.userAllowedMobileDownloads() || - !DefaultActionButtonCallback.userChoseAddToQueue() || queueIds.contains(item.getId())) { + !DefaultActionButtonCallback.userChoseAddToQueue() || isInQueue) { butSecondary.setVisibility(View.VISIBLE); butSecondary.setImageDrawable(drawables.getDrawable(2)); butSecondary.setContentDescription(context.getString(labels[2])); @@ -88,7 +85,7 @@ public class ActionButtonUtils { butSecondary.setContentDescription(context.getString(labels[0])); } } else { - if (item.isRead()) { + if (item.isPlayed()) { butSecondary.setVisibility(View.INVISIBLE); } else { butSecondary.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java b/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java index e22b31361..1ea7daaa3 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java @@ -1,19 +1,24 @@ package de.danoeh.antennapod.adapter; -import android.content.res.Resources; +import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; +import com.joanzapata.iconify.Iconify; + import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.NetworkUtils; /** * Utility methods for adapters */ public class AdapterUtils { + private static final String TAG = AdapterUtils.class.getSimpleName(); + private AdapterUtils() { } @@ -21,7 +26,7 @@ public class AdapterUtils { /** * Updates the contents of the TextView that shows the current playback position and the ProgressBar. */ - public static void updateEpisodePlaybackProgress(FeedItem item, Resources res, TextView txtvPos, ProgressBar episodeProgress) { + public static void updateEpisodePlaybackProgress(FeedItem item, TextView txtvPos, ProgressBar episodeProgress) { FeedMedia media = item.getMedia(); episodeProgress.setVisibility(View.GONE); if (media == null) { @@ -36,18 +41,34 @@ public class AdapterUtils { || state == FeedItem.State.IN_PROGRESS) { if (media.getDuration() > 0) { episodeProgress.setVisibility(View.VISIBLE); - episodeProgress - .setProgress((int) (((double) media + episodeProgress.setProgress((int) (((double) media .getPosition()) / media.getDuration() * 100)); - txtvPos.setText(Converter - .getDurationStringLong(media.getDuration() + txtvPos.setText(Converter.getDurationStringLong(media.getDuration() - media.getPosition())); } } else if (!media.isDownloaded()) { - txtvPos.setText(Converter.byteToString(media.getSize())); + if (media.getSize() > 0) { + txtvPos.setText(Converter.byteToString(media.getSize())); + } else if(false == media.checkedOnSizeButUnknown()) { + txtvPos.setText("{fa-spinner}"); + Iconify.addIcons(txtvPos); + NetworkUtils.getFeedMediaSizeObservable(media) + .subscribe( + size -> { + if (size > 0) { + txtvPos.setText(Converter.byteToString(size)); + } else { + txtvPos.setText(""); + } + }, error -> { + txtvPos.setText(""); + Log.e(TAG, Log.getStackTraceString(error)); + }); + } else { + txtvPos.setText(""); + } } else { - txtvPos.setText(Converter.getDurationStringLong(media - .getDuration())); + txtvPos.setText(Converter.getDurationStringLong(media.getDuration())); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java deleted file mode 100644 index ea0c96be9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java +++ /dev/null @@ -1,185 +0,0 @@ -package de.danoeh.antennapod.adapter; - -import android.content.Context; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.squareup.picasso.Picasso; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.Converter; - -/** - * List adapter for the list of new episodes - */ -public class AllEpisodesListAdapter extends BaseAdapter { - - private final Context context; - private final ItemAccess itemAccess; - private final ActionButtonCallback actionButtonCallback; - private final ActionButtonUtils actionButtonUtils; - private final boolean showOnlyNewEpisodes; - - public AllEpisodesListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback, - boolean showOnlyNewEpisodes) { - super(); - this.context = context; - this.itemAccess = itemAccess; - this.actionButtonUtils = new ActionButtonUtils(context); - this.actionButtonCallback = actionButtonCallback; - this.showOnlyNewEpisodes = showOnlyNewEpisodes; - } - - @Override - public int getCount() { - return itemAccess.getCount(); - } - - @Override - public Object getItem(int position) { - return itemAccess.getItem(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - final FeedItem item = (FeedItem) getItem(position); - if (item == null) return null; - - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.new_episodes_listitem, - parent, false); - holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); - holder.pubDate = (TextView) convertView - .findViewById(R.id.txtvPublished); - holder.statusUnread = convertView.findViewById(R.id.statusUnread); - holder.butSecondary = (ImageButton) convertView - .findViewById(R.id.butSecondaryAction); - holder.queueStatus = (ImageView) convertView - .findViewById(R.id.imgvInPlaylist); - holder.downloadProgress = (ProgressBar) convertView - .findViewById(R.id.pbar_download_progress); - holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); - holder.txtvDuration = (TextView) convertView.findViewById(R.id.txtvDuration); - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - holder.title.setText(item.getTitle()); - holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL)); - if (showOnlyNewEpisodes || item.isRead() || false == itemAccess.isNew(item)) { - holder.statusUnread.setVisibility(View.INVISIBLE); - } else { - holder.statusUnread.setVisibility(View.VISIBLE); - } - - FeedMedia media = item.getMedia(); - if (media != null) { - final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); - - if (media.getDuration() > 0) { - holder.txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); - } else if (media.getSize() > 0) { - holder.txtvDuration.setText(Converter.byteToString(media.getSize())); - } else { - holder.txtvDuration.setText(""); - } - - if (isDownloadingMedia) { - holder.downloadProgress.setVisibility(View.VISIBLE); - holder.txtvDuration.setVisibility(View.GONE); - holder.pubDate.setVisibility(View.GONE); - } else { - holder.txtvDuration.setVisibility(View.VISIBLE); - holder.pubDate.setVisibility(View.VISIBLE); - holder.downloadProgress.setVisibility(View.GONE); - } - - if (!media.isDownloaded()) { - if (isDownloadingMedia) { - // item is being downloaded - holder.downloadProgress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); - } - } - } else { - holder.downloadProgress.setVisibility(View.GONE); - holder.txtvDuration.setVisibility(View.GONE); - } - - if (itemAccess.isInQueue(item)) { - holder.queueStatus.setVisibility(View.VISIBLE); - } else { - holder.queueStatus.setVisibility(View.INVISIBLE); - } - - actionButtonUtils.configureActionButton(holder.butSecondary, item); - holder.butSecondary.setFocusable(false); - holder.butSecondary.setTag(item); - holder.butSecondary.setOnClickListener(secondaryActionListener); - - Picasso.with(context) - .load(item.getImageUri()) - .fit() - .into(holder.imageView); - - return convertView; - } - - private View.OnClickListener secondaryActionListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - FeedItem item = (FeedItem) v.getTag(); - actionButtonCallback.onActionButtonPressed(item); - } - }; - - - static class Holder { - TextView title; - TextView pubDate; - View statusUnread; - ImageView queueStatus; - ImageView imageView; - ProgressBar downloadProgress; - TextView txtvDuration; - ImageButton butSecondary; - } - - public interface ItemAccess { - - int getCount(); - - FeedItem getItem(int position); - - int getItemDownloadProgressPercent(FeedItem item); - - boolean isInQueue(FeedItem item); - - boolean isNew(FeedItem item); - - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java new file mode 100644 index 000000000..d749b0313 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -0,0 +1,375 @@ +package de.danoeh.antennapod.adapter; + +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; +import com.joanzapata.iconify.Iconify; +import com.nineoldandroids.view.ViewHelper; + +import java.lang.ref.WeakReference; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.util.LongList; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.fragment.ItemFragment; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; + +/** + * List adapter for the list of new episodes + */ +public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesRecycleAdapter.Holder> { + + private static final String TAG = AllEpisodesRecycleAdapter.class.getSimpleName(); + + private final WeakReference<MainActivity> mainActivityRef; + private final ItemAccess itemAccess; + private final ActionButtonCallback actionButtonCallback; + private final ActionButtonUtils actionButtonUtils; + private final boolean showOnlyNewEpisodes; + + private int position = -1; + + private final int playingBackGroundColor; + private final int normalBackGroundColor; + + public AllEpisodesRecycleAdapter(MainActivity mainActivity, + ItemAccess itemAccess, + ActionButtonCallback actionButtonCallback, + boolean showOnlyNewEpisodes) { + super(); + this.mainActivityRef = new WeakReference<>(mainActivity); + this.itemAccess = itemAccess; + this.actionButtonUtils = new ActionButtonUtils(mainActivity); + this.actionButtonCallback = actionButtonCallback; + this.showOnlyNewEpisodes = showOnlyNewEpisodes; + + if(UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { + playingBackGroundColor = mainActivity.getResources().getColor(R.color.highlight_dark); + } else { + playingBackGroundColor = mainActivity.getResources().getColor(R.color.highlight_light); + } + normalBackGroundColor = mainActivity.getResources().getColor(android.R.color.transparent); + } + + @Override + public Holder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.new_episodes_listitem, parent, false); + Holder holder = new Holder(view); + holder.container = (FrameLayout) view.findViewById(R.id.container); + holder.placeholder = (TextView) view.findViewById(R.id.txtvPlaceholder); + holder.title = (TextView) view.findViewById(R.id.txtvTitle); + holder.pubDate = (TextView) view + .findViewById(R.id.txtvPublished); + holder.statusUnread = view.findViewById(R.id.statusUnread); + holder.butSecondary = (ImageButton) view + .findViewById(R.id.butSecondaryAction); + holder.queueStatus = (ImageView) view + .findViewById(R.id.imgvInPlaylist); + holder.progress = (ProgressBar) view + .findViewById(R.id.pbar_progress); + holder.cover = (ImageView) view.findViewById(R.id.imgvCover); + holder.txtvDuration = (TextView) view.findViewById(R.id.txtvDuration); + holder.item = null; + holder.mainActivityRef = mainActivityRef; + holder.position = -1; + // so we can grab this later + view.setTag(holder); + + return holder; + } + + @Override + public void onBindViewHolder(final Holder holder, int position) { + final FeedItem item = itemAccess.getItem(position); + if (item == null) return; + holder.itemView.setOnLongClickListener(v -> { + this.position = position; + return false; + }); + holder.item = item; + holder.position = position; + holder.placeholder.setVisibility(View.VISIBLE); + holder.placeholder.setText(item.getFeed().getTitle()); + holder.title.setText(item.getTitle()); + String pubDateStr = DateUtils.formatAbbrev(mainActivityRef.get(), item.getPubDate()); + holder.pubDate.setText(pubDateStr); + if (showOnlyNewEpisodes || false == item.isNew()) { + holder.statusUnread.setVisibility(View.INVISIBLE); + } else { + holder.statusUnread.setVisibility(View.VISIBLE); + } + + FeedMedia media = item.getMedia(); + if (media != null) { + final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); + + if (media.getDuration() > 0) { + holder.txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); + } else if (media.getSize() > 0) { + holder.txtvDuration.setText(Converter.byteToString(media.getSize())); + } else if(false == media.checkedOnSizeButUnknown()) { + holder.txtvDuration.setText("{fa-spinner}"); + Iconify.addIcons(holder.txtvDuration); + NetworkUtils.getFeedMediaSizeObservable(media) + .subscribe( + size -> { + if (size > 0) { + holder.txtvDuration.setText(Converter.byteToString(size)); + } else { + holder.txtvDuration.setText(""); + } + }, error -> { + holder.txtvDuration.setText(""); + Log.e(TAG, Log.getStackTraceString(error)); + }); + } else { + holder.txtvDuration.setText(""); + } + + FeedItem.State state = item.getState(); + if (isDownloadingMedia) { + holder.progress.setVisibility(View.VISIBLE); + // item is being downloaded + holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); + } else if (state == FeedItem.State.PLAYING + || state == FeedItem.State.IN_PROGRESS) { + if (media.getDuration() > 0) { + int progress = (int) (100.0 * media.getPosition() / media.getDuration()); + holder.progress.setProgress(progress); + holder.progress.setVisibility(View.VISIBLE); + } + } else { + holder.progress.setVisibility(View.GONE); + } + + if(media.isCurrentlyPlaying()) { + holder.container.setBackgroundColor(playingBackGroundColor); + } else { + holder.container.setBackgroundColor(normalBackGroundColor); + } + } else { + holder.progress.setVisibility(View.GONE); + holder.txtvDuration.setVisibility(View.GONE); + } + + boolean isInQueue = itemAccess.isInQueue(item); + if (isInQueue) { + holder.queueStatus.setVisibility(View.VISIBLE); + } else { + holder.queueStatus.setVisibility(View.INVISIBLE); + } + + actionButtonUtils.configureActionButton(holder.butSecondary, item, isInQueue); + holder.butSecondary.setFocusable(false); + holder.butSecondary.setTag(item); + holder.butSecondary.setOnClickListener(secondaryActionListener); + + Glide.with(mainActivityRef.get()) + .load(item.getImageUri()) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(item.getFeed().getImageUri(), holder.placeholder, holder.cover)); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return itemAccess.getCount(); + } + + public FeedItem getItem(int position) { + return itemAccess.getItem(position); + } + + public int getPosition() { + int pos = position; + position = -1; // reset + return pos; + } + + private class CoverTarget extends GlideDrawableImageViewTarget { + + private final WeakReference<Uri> fallback; + private final WeakReference<TextView> placeholder; + private final WeakReference<ImageView> cover; + + public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) { + super(imgvCover); + fallback = new WeakReference<>(fallbackUri); + placeholder = new WeakReference<>(txtvPlaceholder); + cover = new WeakReference<>(imgvCover); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + Uri fallbackUri = fallback.get(); + TextView txtvPlaceholder = placeholder.get(); + ImageView imgvCover = cover.get(); + if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { + Glide.with(mainActivityRef.get()) + .load(fallbackUri) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(null, txtvPlaceholder, imgvCover)); + } + } + + @Override + public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { + super.onResourceReady(drawable, anim); + TextView txtvPlaceholder = placeholder.get(); + if(txtvPlaceholder != null) { + txtvPlaceholder.setVisibility(View.INVISIBLE); + } + } + } + + private View.OnClickListener secondaryActionListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + FeedItem item = (FeedItem) v.getTag(); + actionButtonCallback.onActionButtonPressed(item); + } + }; + + public class Holder extends RecyclerView.ViewHolder + implements View.OnClickListener, + View.OnCreateContextMenuListener, + ItemTouchHelperViewHolder { + FrameLayout container; + TextView placeholder; + TextView title; + TextView pubDate; + View statusUnread; + ImageView queueStatus; + ImageView cover; + ProgressBar progress; + TextView txtvDuration; + ImageButton butSecondary; + FeedItem item; + WeakReference<MainActivity> mainActivityRef; + int position; + + public Holder(View itemView) { + super(itemView); + itemView.setOnClickListener(this); + itemView.setOnCreateContextMenuListener(this); + } + + @Override + public void onClick(View v) { + MainActivity mainActivity = mainActivityRef.get(); + if (mainActivity != null) { + mainActivity.loadChildFragment(ItemFragment.newInstance(item.getId())); + } + } + + @Override + public void onItemSelected() { + ViewHelper.setAlpha(itemView, 0.5f); + } + + @Override + public void onItemClear() { + ViewHelper.setAlpha(itemView, 1.0f); + } + + public FeedItem getFeedItem() { return item; } + + @Override + public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + FeedItem item = itemAccess.getItem(getAdapterPosition()); + + MenuInflater inflater = mainActivityRef.get().getMenuInflater(); + inflater.inflate(R.menu.allepisodes_context, menu); + + if (item != null) { + menu.setHeaderTitle(item.getTitle()); + } + + FeedItemMenuHandler.MenuInterface contextMenuInterface = (id, visible) -> { + if (menu == null) { + return; + } + MenuItem item1 = menu.findItem(id); + if (item1 != null) { + item1.setVisible(visible); + } + }; + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, + itemAccess.getQueueIds(), itemAccess.getFavoritesIds()); + } + + } + + public interface ItemAccess { + + int getCount(); + + FeedItem getItem(int position); + + int getItemDownloadProgressPercent(FeedItem item); + + boolean isInQueue(FeedItem item); + + LongList getQueueIds(); + + LongList getFavoritesIds(); + + } + + /** + * Notifies a View Holder of relevant callbacks from + * {@link ItemTouchHelper.Callback}. + */ + public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an + * item as being moved or swiped. + * Implementations should update the item view to indicate + * it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the + * move or swipe, and the active item state should be cleared. + */ + void onItemClear(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ChapterListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/ChapterListAdapter.java deleted file mode 100644 index 67fb4c3b1..000000000 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChapterListAdapter.java +++ /dev/null @@ -1,197 +0,0 @@ -package de.danoeh.antennapod.adapter; - -import android.content.Context; -import android.text.Layout; -import android.text.Selection; -import android.text.Spannable; -import android.text.Spanned; -import android.text.style.ClickableSpan; -import android.text.util.Linkify; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageButton; -import android.widget.TextView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.util.ChapterUtils; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.playback.Playable; - -import java.util.List; - -public class ChapterListAdapter extends ArrayAdapter<Chapter> { - - private static final String TAG = "ChapterListAdapter"; - - private List<Chapter> chapters; - private Playable media; - - private int defaultTextColor; - private final Callback callback; - - public ChapterListAdapter(Context context, int textViewResourceId, - List<Chapter> objects, Playable media, Callback callback) { - super(context, textViewResourceId, objects); - this.chapters = objects; - this.media = media; - this.callback = callback; - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - Holder holder; - - Chapter sc = getItem(position); - - // Inflate Layout - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.simplechapter_item, parent, false); - holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); - defaultTextColor = holder.title.getTextColors().getDefaultColor(); - holder.start = (TextView) convertView.findViewById(R.id.txtvStart); - holder.link = (TextView) convertView.findViewById(R.id.txtvLink); - holder.butPlayChapter = (ImageButton) convertView.findViewById(R.id.butPlayChapter); - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - - } - - holder.title.setText(sc.getTitle()); - holder.start.setText(Converter.getDurationStringLong((int) sc - .getStart())); - if (sc.getLink() != null) { - holder.link.setVisibility(View.VISIBLE); - holder.link.setText(sc.getLink()); - Linkify.addLinks(holder.link, Linkify.WEB_URLS); - } else { - holder.link.setVisibility(View.GONE); - } - holder.link.setMovementMethod(null); - holder.link.setOnTouchListener(new OnTouchListener() { - - @Override - public boolean onTouch(View v, MotionEvent event) { - // from - // http://stackoverflow.com/questions/7236840/android-textview-linkify-intercepts-with-parent-view-gestures - TextView widget = (TextView) v; - Object text = widget.getText(); - if (text instanceof Spanned) { - Spannable buffer = (Spannable) text; - - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP - || action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - - ClickableSpan[] link = buffer.getSpans(off, off, - ClickableSpan.class); - - if (link.length != 0) { - if (action == MotionEvent.ACTION_UP) { - link[0].onClick(widget); - } else if (action == MotionEvent.ACTION_DOWN) { - Selection.setSelection(buffer, - buffer.getSpanStart(link[0]), - buffer.getSpanEnd(link[0])); - } - return true; - } - } - - } - - return false; - - } - }); - holder.butPlayChapter.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (callback != null) { - callback.onPlayChapterButtonClicked(position); - } - } - }); - Chapter current = ChapterUtils.getCurrentChapter(media); - if (current != null) { - if (current == sc) { - holder.title.setTextColor(convertView.getResources().getColor( - R.color.bright_blue)); - holder.start.setTextColor(convertView.getResources().getColor( - R.color.bright_blue)); - } else { - holder.title.setTextColor(defaultTextColor); - holder.start.setTextColor(defaultTextColor); - } - } else { - Log.w(TAG, "Could not find out what the current chapter is."); - } - - return convertView; - } - - static class Holder { - TextView title; - TextView start; - TextView link; - ImageButton butPlayChapter; - } - - @Override - public int getCount() { - // ignore invalid chapters - int counter = 0; - for (Chapter chapter : chapters) { - if (!ignoreChapter(chapter)) { - counter++; - } - } - return counter; - } - - private boolean ignoreChapter(Chapter c) { - return media.getDuration() > 0 && media.getDuration() < c.getStart(); - } - - @Override - public Chapter getItem(int position) { - int i = 0; - for (Chapter chapter : chapters) { - if (!ignoreChapter(chapter)) { - if (i == position) { - return chapter; - } else { - i++; - } - } - } - return super.getItem(position); - } - - public static interface Callback { - public void onPlayChapterButtonClicked(int position); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java new file mode 100644 index 000000000..1c9439ee6 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java @@ -0,0 +1,198 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.Spanned; +import android.text.style.ClickableSpan; +import android.text.util.Linkify; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.TextView; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.Chapter; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.ChapterUtils; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.playback.Playable; + +public class ChaptersListAdapter extends ArrayAdapter<Chapter> { + + private static final String TAG = "ChapterListAdapter"; + + private Playable media; + + private int defaultTextColor; + private final Callback callback; + + public ChaptersListAdapter(Context context, int textViewResourceId, Callback callback) { + super(context, textViewResourceId); + this.callback = callback; + } + + public void setMedia(Playable media) { + this.media = media; + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + Holder holder; + + Chapter sc = getItem(position); + + // Inflate Layout + if (convertView == null) { + holder = new Holder(); + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + convertView = inflater.inflate(R.layout.simplechapter_item, parent, false); + holder.view = convertView; + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + defaultTextColor = holder.title.getTextColors().getDefaultColor(); + holder.start = (TextView) convertView.findViewById(R.id.txtvStart); + holder.link = (TextView) convertView.findViewById(R.id.txtvLink); + holder.butPlayChapter = (ImageButton) convertView.findViewById(R.id.butPlayChapter); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + + } + + holder.title.setText(sc.getTitle()); + holder.start.setText(Converter.getDurationStringLong((int) sc + .getStart())); + if (sc.getLink() != null) { + holder.link.setVisibility(View.VISIBLE); + holder.link.setText(sc.getLink()); + Linkify.addLinks(holder.link, Linkify.WEB_URLS); + } else { + holder.link.setVisibility(View.GONE); + } + holder.link.setMovementMethod(null); + holder.link.setOnTouchListener((v, event) -> { + // from + // http://stackoverflow.com/questions/7236840/android-textview-linkify-intercepts-with-parent-view-gestures + TextView widget = (TextView) v; + Object text = widget.getText(); + if (text instanceof Spanned) { + Spannable buffer = (Spannable) text; + + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] link = buffer.getSpans(off, off, + ClickableSpan.class); + + if (link.length != 0) { + if (action == MotionEvent.ACTION_UP) { + link[0].onClick(widget); + } else if (action == MotionEvent.ACTION_DOWN) { + Selection.setSelection(buffer, + buffer.getSpanStart(link[0]), + buffer.getSpanEnd(link[0])); + } + return true; + } + } + + } + + return false; + + }); + holder.butPlayChapter.setOnClickListener(v -> { + if (callback != null) { + callback.onPlayChapterButtonClicked(position); + } + }); + Chapter current = ChapterUtils.getCurrentChapter(media); + if (current != null) { + if (current == sc) { + int playingBackGroundColor; + if(UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { + playingBackGroundColor = getContext().getResources().getColor(R.color.highlight_dark); + } else { + playingBackGroundColor = getContext().getResources().getColor(R.color.highlight_light); + } + holder.view.setBackgroundColor(playingBackGroundColor); + } else { + holder.view.setBackgroundColor(getContext().getResources().getColor(android.R.color.transparent)); + holder.title.setTextColor(defaultTextColor); + holder.start.setTextColor(defaultTextColor); + } + } else { + Log.w(TAG, "Could not find out what the current chapter is."); + } + + return convertView; + } + + static class Holder { + View view; + TextView title; + TextView start; + TextView link; + ImageButton butPlayChapter; + } + + @Override + public int getCount() { + if(media == null || media.getChapters() == null) { + return 0; + } + // ignore invalid chapters + int counter = 0; + for (Chapter chapter : media.getChapters()) { + if (!ignoreChapter(chapter)) { + counter++; + } + } + return counter; + } + + private boolean ignoreChapter(Chapter c) { + return media.getDuration() > 0 && media.getDuration() < c.getStart(); + } + + @Override + public Chapter getItem(int position) { + int i = 0; + for (Chapter chapter : media.getChapters()) { + if (!ignoreChapter(chapter)) { + if (i == position) { + return chapter; + } else { + i++; + } + } + } + return super.getItem(position); + } + + public interface Callback { + void onPlayChapterButtonClicked(int position); + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java b/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java index 3d233817b..469a807e1 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java @@ -1,22 +1,21 @@ package de.danoeh.antennapod.adapter; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.support.v7.app.AlertDialog; import android.widget.Toast; +import com.afollestad.materialdialogs.MaterialDialog; + import org.apache.commons.lang3.Validate; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; - import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; @@ -60,8 +59,8 @@ public class DefaultActionButtonCallback implements ActionButtonCallback { final FeedMedia media = item.getMedia(); boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); if (!isDownloading && !media.isDownloaded()) { - LongList queueIds = DBReader.getQueueIDList(context); - if (NetworkUtils.isDownloadAllowed(context) || userAllowedMobileDownloads()) { + LongList queueIds = DBReader.getQueueIDList(); + if (NetworkUtils.isDownloadAllowed() || userAllowedMobileDownloads()) { try { DBTasks.downloadFeedItems(context, item); Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); @@ -70,7 +69,7 @@ public class DefaultActionButtonCallback implements ActionButtonCallback { DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage()); } } else if(userChoseAddToQueue() && !queueIds.contains(item.getId())) { - DBWriter.addQueueItem(context, item.getId()); + DBWriter.addQueueItem(context, item); Toast.makeText(context, R.string.added_to_queue_label, Toast.LENGTH_SHORT).show(); } else { confirmMobileDownload(context, item); @@ -78,7 +77,7 @@ public class DefaultActionButtonCallback implements ActionButtonCallback { } else if (isDownloading) { DownloadRequester.getInstance().cancelDownload(context, media); if(UserPreferences.isEnableAutodownload()) { - DBWriter.setFeedItemAutoDownload(context, media.getItem(), false); + DBWriter.setFeedItemAutoDownload(media.getItem(), false); Toast.makeText(context, R.string.download_canceled_autodownload_enabled_msg, Toast.LENGTH_LONG).show(); } else { Toast.makeText(context, R.string.download_canceled_msg, Toast.LENGTH_LONG).show(); @@ -95,61 +94,40 @@ public class DefaultActionButtonCallback implements ActionButtonCallback { } } } else { - if (!item.isRead()) { - DBWriter.markItemRead(context, item, true, true); - - if(GpodnetPreferences.loggedIn()) { - // gpodder: send played action - FeedMedia media = item.getMedia(); - GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(media.getDuration() / 1000) - .position(media.getDuration() / 1000) - .total(media.getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(action); - } + if (!item.isPlayed()) { + DBWriter.markItemPlayed(item, FeedItem.PLAYED, true); } } } private void confirmMobileDownload(final Context context, final FeedItem item) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); + MaterialDialog.Builder builder = new MaterialDialog.Builder(context); builder - .setTitle(R.string.confirm_mobile_download_dialog_title) - .setMessage(context.getText(R.string.confirm_mobile_download_dialog_message)) - .setPositiveButton(R.string.confirm_mobile_download_dialog_enable_temporarily, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - allowMobileDownloadsTimestamp = System.currentTimeMillis(); - try { - DBTasks.downloadFeedItems(context, item); - Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage()); - } - } - }); - LongList queueIds = DBReader.getQueueIDList(context); + .title(R.string.confirm_mobile_download_dialog_title) + .content(R.string.confirm_mobile_download_dialog_message) + .positiveText(context.getText(R.string.confirm_mobile_download_dialog_enable_temporarily)) + .onPositive((dialog, which) -> { + allowMobileDownloadsTimestamp = System.currentTimeMillis(); + try { + DBTasks.downloadFeedItems(context, item); + Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage()); + } + }); + LongList queueIds = DBReader.getQueueIDList(); if(!queueIds.contains(item.getId())) { - builder.setNeutralButton(R.string.confirm_mobile_download_dialog_only_add_to_queue, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - onlyAddToQueueTimeStamp = System.currentTimeMillis(); - DBWriter.addQueueItem(context, item.getId()); - Toast.makeText(context, R.string.added_to_queue_label, Toast.LENGTH_SHORT).show(); - } - }) - .setMessage(context.getText(R.string.confirm_mobile_download_dialog_message_not_in_queue)); - } else { - builder.setMessage(context.getText(R.string.confirm_mobile_download_dialog_message)); + builder + .content(R.string.confirm_mobile_download_dialog_message_not_in_queue) + .neutralText(R.string.confirm_mobile_download_dialog_only_add_to_queue) + .onNeutral((dialog, which) -> { + onlyAddToQueueTimeStamp = System.currentTimeMillis(); + DBWriter.addQueueItem(context, item); + Toast.makeText(context, R.string.added_to_queue_label, Toast.LENGTH_SHORT).show(); + }); } - builder.setNegativeButton(R.string.cancel_label, null) - .create() - .show(); + builder.show(); } + } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index f29cfdf2f..582538fb8 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -7,11 +7,11 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import android.widget.Button; import android.widget.TextView; import android.widget.Toast; -import com.joanzapata.android.iconify.Iconify; +import com.joanzapata.iconify.Iconify; +import com.joanzapata.iconify.widget.IconButton; import java.util.Date; @@ -50,7 +50,7 @@ public class DownloadLogAdapter extends BaseAdapter { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.downloadlog_item, parent, false); holder.icon = (TextView) convertView.findViewById(R.id.txtvIcon); - holder.retry = (Button) convertView.findViewById(R.id.btnRetry); + holder.retry = (IconButton) convertView.findViewById(R.id.btnRetry); holder.date = (TextView) convertView.findViewById(R.id.txtvDate); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); holder.type = (TextView) convertView.findViewById(R.id.txtvType); @@ -96,8 +96,6 @@ public class DownloadLogAdapter extends BaseAdapter { if(status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE && !newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) { holder.retry.setVisibility(View.VISIBLE); - holder.retry.setText("{fa-repeat}"); - Iconify.addIcons(holder.retry); holder.retry.setOnClickListener(clickListener); ButtonHolder btnHolder; if(holder.retry.getTag() != null) { @@ -123,21 +121,29 @@ public class DownloadLogAdapter extends BaseAdapter { public void onClick(View v) { ButtonHolder holder = (ButtonHolder) v.getTag(); if(holder.typeId == Feed.FEEDFILETYPE_FEED) { - Feed feed = DBReader.getFeed(context, holder.id); - feed.setLastUpdate(new Date(0)); // force refresh - try { - DBTasks.refreshFeed(context, feed); - } catch (DownloadRequestException e) { - e.printStackTrace(); + Feed feed = DBReader.getFeed(holder.id); + if (feed != null) { + feed.setLastUpdate(new Date(0)); // force refresh + try { + DBTasks.refreshFeed(context, feed); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + } else { + Log.wtf(TAG, "Could not find feed for feed id: " + holder.id); } } else if(holder.typeId == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - FeedMedia media = DBReader.getFeedMedia(context, holder.id); - try { - DBTasks.downloadFeedItems(context, media.getItem()); - Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage()); + FeedMedia media = DBReader.getFeedMedia(holder.id); + if (media != null) { + try { + DBTasks.downloadFeedItems(context, media.getItem()); + Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage()); + } + } else { + Log.wtf(TAG, "Could not find media for id: " + holder.id); } } else { Log.wtf(TAG, "Unexpected type id: " + holder.typeId); @@ -157,7 +163,7 @@ public class DownloadLogAdapter extends BaseAdapter { static class Holder { TextView icon; - Button retry; + IconButton retry; TextView title; TextView type; TextView date; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java index 15e0a7a33..ca747b9b0 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.adapter; import android.content.Context; -import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,11 +9,13 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.DateUtils; /** * Shows a list of downloaded episodes @@ -73,7 +74,8 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { } holder.title.setText(item.getTitle()); - holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL)); + String pubDateStr = DateUtils.formatAbbrev(context, item.getPubDate()); + holder.pubDate.setText(pubDateStr); holder.txtvSize.setText(Converter.byteToString(item.getMedia().getSize())); FeedItem.State state = item.getState(); @@ -88,9 +90,13 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { holder.butSecondary.setOnClickListener(secondaryActionListener); - Picasso.with(context) + Glide.with(context) .load(item.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(holder.imageView); return convertView; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java index b39e23d42..e483738b4 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java @@ -2,7 +2,6 @@ package de.danoeh.antennapod.adapter; import android.content.Context; import android.content.res.TypedArray; -import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -11,6 +10,7 @@ import android.widget.Adapter; import android.widget.BaseAdapter; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -20,7 +20,9 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.DateUtils; import de.danoeh.antennapod.core.util.ThemeUtils; /** @@ -33,13 +35,20 @@ public class FeedItemlistAdapter extends BaseAdapter { private final Context context; private boolean showFeedtitle; private int selectedItemIndex; + /** true if played items should be made partially transparent */ + private boolean makePlayedItemsTransparent; private final ActionButtonUtils actionButtonUtils; public static final int SELECTION_NONE = -1; + private final int playingBackGroundColor; + private final int normalBackGroundColor; + public FeedItemlistAdapter(Context context, ItemAccess itemAccess, - ActionButtonCallback callback, boolean showFeedtitle) { + ActionButtonCallback callback, + boolean showFeedtitle, + boolean makePlayedItemsTransparent) { super(); this.callback = callback; this.context = context; @@ -47,6 +56,14 @@ public class FeedItemlistAdapter extends BaseAdapter { this.showFeedtitle = showFeedtitle; this.selectedItemIndex = SELECTION_NONE; this.actionButtonUtils = new ActionButtonUtils(context); + this.makePlayedItemsTransparent = makePlayedItemsTransparent; + + if(UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { + playingBackGroundColor = context.getResources().getColor(R.color.highlight_dark); + } else { + playingBackGroundColor = context.getResources().getColor(R.color.highlight_light); + } + normalBackGroundColor = context.getResources().getColor(android.R.color.transparent); } @Override @@ -75,6 +92,8 @@ public class FeedItemlistAdapter extends BaseAdapter { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.feeditemlist_item, parent, false); + holder.container = (LinearLayout) convertView + .findViewById(R.id.container); holder.title = (TextView) convertView .findViewById(R.id.txtvItemname); holder.lenSize = (TextView) convertView @@ -86,7 +105,7 @@ public class FeedItemlistAdapter extends BaseAdapter { holder.inPlaylist = (ImageView) convertView .findViewById(R.id.imgvInPlaylist); holder.type = (ImageView) convertView.findViewById(R.id.imgvType); - holder.statusUnread = (View) convertView + holder.statusUnread = convertView .findViewById(R.id.statusUnread); holder.episodeProgress = (ProgressBar) convertView .findViewById(R.id.pbar_episode_progress); @@ -95,6 +114,7 @@ public class FeedItemlistAdapter extends BaseAdapter { } else { holder = (Holder) convertView.getTag(); } + if (!(getItemViewType(position) == Adapter.IGNORE_ITEM_VIEW_TYPE)) { convertView.setVisibility(View.VISIBLE); if (position == selectedItemIndex) { @@ -106,25 +126,25 @@ public class FeedItemlistAdapter extends BaseAdapter { StringBuilder buffer = new StringBuilder(item.getTitle()); if (showFeedtitle) { - buffer.append("("); + buffer.append(" ("); buffer.append(item.getFeed().getTitle()); buffer.append(")"); } holder.title.setText(buffer.toString()); - if(false == item.isRead() && itemAccess.isNew(item)) { + if(item.isNew()) { holder.statusUnread.setVisibility(View.VISIBLE); } else { holder.statusUnread.setVisibility(View.INVISIBLE); } - if(item.isRead()) { + if(item.isPlayed() && makePlayedItemsTransparent) { ViewHelper.setAlpha(convertView, 0.5f); } else { ViewHelper.setAlpha(convertView, 1.0f); } - holder.published.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL)); - + String pubDateStr = DateUtils.formatAbbrev(context, item.getPubDate()); + holder.published.setText(pubDateStr); FeedMedia media = item.getMedia(); if (media == null) { @@ -134,18 +154,17 @@ public class FeedItemlistAdapter extends BaseAdapter { holder.lenSize.setVisibility(View.INVISIBLE); } else { - AdapterUtils.updateEpisodePlaybackProgress(item, context.getResources(), holder.lenSize, holder.episodeProgress); + AdapterUtils.updateEpisodePlaybackProgress(item, holder.lenSize, holder.episodeProgress); - if (((ItemAccess) itemAccess).isInQueue(item)) { + if (itemAccess.isInQueue(item)) { holder.inPlaylist.setVisibility(View.VISIBLE); } else { holder.inPlaylist.setVisibility(View.INVISIBLE); } - if (DownloadRequester.getInstance().isDownloadingFile( - item.getMedia())) { + if (DownloadRequester.getInstance().isDownloadingFile(item.getMedia())) { holder.episodeProgress.setVisibility(View.VISIBLE); - holder.episodeProgress.setProgress(((ItemAccess) itemAccess).getItemDownloadProgressPercent(item)); + holder.episodeProgress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); } else { if(media.getPosition() == 0) { holder.episodeProgress.setVisibility(View.GONE); @@ -169,9 +188,18 @@ public class FeedItemlistAdapter extends BaseAdapter { holder.type.setImageBitmap(null); holder.type.setVisibility(View.GONE); } + + if(media.isCurrentlyPlaying()) { + if(media.isCurrentlyPlaying()) { + holder.container.setBackgroundColor(playingBackGroundColor); + } else { + holder.container.setBackgroundColor(normalBackGroundColor); + } + } } - actionButtonUtils.configureActionButton(holder.butAction, item); + boolean isInQueue = itemAccess.isInQueue(item); + actionButtonUtils.configureActionButton(holder.butAction, item, isInQueue); holder.butAction.setFocusable(false); holder.butAction.setTag(item); holder.butAction.setOnClickListener(butActionListener); @@ -180,7 +208,6 @@ public class FeedItemlistAdapter extends BaseAdapter { convertView.setVisibility(View.GONE); } return convertView; - } private final OnClickListener butActionListener = new OnClickListener() { @@ -192,6 +219,7 @@ public class FeedItemlistAdapter extends BaseAdapter { }; static class Holder { + LinearLayout container; TextView title; TextView published; TextView lenSize; @@ -202,15 +230,6 @@ public class FeedItemlistAdapter extends BaseAdapter { ProgressBar episodeProgress; } - public int getSelectedItemIndex() { - return selectedItemIndex; - } - - public void setSelectedItemIndex(int selectedItemIndex) { - this.selectedItemIndex = selectedItemIndex; - notifyDataSetChanged(); - } - public interface ItemAccess { boolean isInQueue(FeedItem item); @@ -221,8 +240,6 @@ public class FeedItemlistAdapter extends BaseAdapter { FeedItem getItem(int position); - boolean isNew(FeedItem item); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java index 81d997d65..b20af1773 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -6,15 +6,18 @@ import android.content.res.TypedArray; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.preference.PreferenceManager; +import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import android.widget.IconTextView; import android.widget.ImageView; +import android.widget.RelativeLayout; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.joanzapata.iconify.Iconify; +import com.joanzapata.iconify.widget.IconTextView; import de.danoeh.antennapod.fragment.SubscriptionFragment; import org.apache.commons.lang3.ArrayUtils; @@ -27,10 +30,12 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.fragment.AddFeedFragment; import de.danoeh.antennapod.fragment.AllEpisodesFragment; import de.danoeh.antennapod.fragment.DownloadsFragment; +import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.NewEpisodesFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; @@ -92,6 +97,9 @@ public class NavListAdapter extends BaseAdapter case NewEpisodesFragment.TAG: icon = R.attr.ic_new; break; + case EpisodesFragment.TAG: + icon = R.attr.feed; + break; case AllEpisodesFragment.TAG: icon = R.attr.feed; break; @@ -205,7 +213,8 @@ public class NavListAdapter extends BaseAdapter holder.title.setText(title); - if (tags.get(position).equals(QueueFragment.TAG)) { + String tag = tags.get(position); + if (tag.equals(QueueFragment.TAG)) { int queueSize = itemAccess.getQueueSize(); if (queueSize > 0) { holder.count.setVisibility(View.VISIBLE); @@ -213,7 +222,7 @@ public class NavListAdapter extends BaseAdapter } else { holder.count.setVisibility(View.GONE); } - } else if (tags.get(position).equals(NewEpisodesFragment.TAG)) { + } else if (tag.equals(EpisodesFragment.TAG)) { int unreadItems = itemAccess.getNumberOfNewItems(); if (unreadItems > 0) { holder.count.setVisibility(View.VISIBLE); @@ -221,6 +230,22 @@ public class NavListAdapter extends BaseAdapter } else { holder.count.setVisibility(View.GONE); } + } else if(tag.equals(DownloadsFragment.TAG) && UserPreferences.isEnableAutodownload()) { + int epCacheSize = UserPreferences.getEpisodeCacheSize(); + if(itemAccess.getNumberOfDownloadedItems() >= epCacheSize) { + holder.count.setText("{md-disc-full 150%}"); + Iconify.addIcons(holder.count); + holder.count.setVisibility(View.VISIBLE); + holder.count.setOnClickListener(v -> { + new AlertDialog.Builder(context) + .setTitle(R.string.episode_cache_full_title) + .setMessage(R.string.episode_cache_full_message) + .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) + .show(); + }); + } else { + holder.count.setVisibility(View.GONE); + } } else { holder.count.setVisibility(View.GONE); } @@ -262,26 +287,34 @@ public class NavListAdapter extends BaseAdapter holder = (FeedHolder) convertView.getTag(); } - Picasso.with(context) + Glide.with(context) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(holder.image); holder.title.setText(feed.getTitle()); if(feed.hasLastUpdateFailed()) { + RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) holder.title.getLayoutParams(); + p.addRule(RelativeLayout.LEFT_OF, R.id.itxtvFailure); holder.failure.setVisibility(View.VISIBLE); } else { + RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) holder.title.getLayoutParams(); + p.addRule(RelativeLayout.LEFT_OF, R.id.txtvCount); holder.failure.setVisibility(View.GONE); } - int feedUnreadItems = itemAccess.getNumberOfUnreadFeedItems(feed.getId()); - if(feedUnreadItems > 0) { + int counter = itemAccess.getFeedCounter(feed.getId()); + if(counter > 0) { holder.count.setVisibility(View.VISIBLE); - holder.count.setText(String.valueOf(feedUnreadItems)); + holder.count.setText(String.valueOf(counter)); holder.count.setTypeface(holder.title.getTypeface()); } else { - holder.count.setVisibility(View.INVISIBLE); + holder.count.setVisibility(View.GONE); } return convertView; } @@ -305,7 +338,8 @@ public class NavListAdapter extends BaseAdapter int getSelectedItemIndex(); int getQueueSize(); int getNumberOfNewItems(); - int getNumberOfUnreadFeedItems(long feedId); + int getNumberOfDownloadedItems(); + int getFeedCounter(long feedId); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java deleted file mode 100644 index bba5a00a9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java +++ /dev/null @@ -1,178 +0,0 @@ -package de.danoeh.antennapod.adapter; - -import android.content.Context; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.squareup.picasso.Picasso; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.Converter; - -/** - * List adapter for the queue. - */ -public class QueueListAdapter extends BaseAdapter { - - - private final Context context; - private final ItemAccess itemAccess; - private final ActionButtonCallback actionButtonCallback; - private final ActionButtonUtils actionButtonUtils; - - private boolean locked; - - - public QueueListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) { - super(); - this.context = context; - this.itemAccess = itemAccess; - this.actionButtonUtils = new ActionButtonUtils(context); - this.actionButtonCallback = actionButtonCallback; - locked = UserPreferences.isQueueLocked(); - } - - public void setLocked(boolean locked) { - this.locked = locked; - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return itemAccess.getCount(); - } - - @Override - public Object getItem(int position) { - return itemAccess.getItem(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - final FeedItem item = (FeedItem) getItem(position); - if (item == null) return null; - - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.queue_listitem, - parent, false); - holder.dragHandle = (ImageView) convertView.findViewById(R.id.drag_handle); - holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); - holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); - holder.pubDate = (TextView) convertView.findViewById(R.id.txtvPubDate); - holder.progressLeft = (TextView) convertView.findViewById(R.id.txtvProgressLeft); - holder.progressRight = (TextView) convertView - .findViewById(R.id.txtvProgressRight); - holder.butSecondary = (ImageButton) convertView - .findViewById(R.id.butSecondaryAction); - holder.progress = (ProgressBar) convertView - .findViewById(R.id.progressBar); - holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - if(locked) { - holder.dragHandle.setVisibility(View.GONE); - } else { - holder.dragHandle.setVisibility(View.VISIBLE); - } - - holder.title.setText(item.getTitle()); - FeedMedia media = item.getMedia(); - - - holder.title.setText(item.getTitle()); - String pubDate = DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL); - holder.pubDate.setText(pubDate.replace(" ", "\n")); - - if (media != null) { - final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); - FeedItem.State state = item.getState(); - if (isDownloadingMedia) { - holder.progressLeft.setText(Converter.byteToString(itemAccess.getItemDownloadedBytes(item))); - if(itemAccess.getItemDownloadSize(item) > 0) { - holder.progressRight.setText(Converter.byteToString(itemAccess.getItemDownloadSize(item))); - } else { - holder.progressRight.setText(Converter.byteToString(media.getSize())); - } - holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); - holder.progress.setVisibility(View.VISIBLE); - } else if (state == FeedItem.State.PLAYING - || state == FeedItem.State.IN_PROGRESS) { - if (media.getDuration() > 0) { - int progress = (int) (100.0 * media.getPosition() / media.getDuration()); - holder.progress.setProgress(progress); - holder.progress.setVisibility(View.VISIBLE); - holder.progressLeft.setText(Converter - .getDurationStringLong(media.getPosition())); - holder.progressRight.setText(Converter.getDurationStringLong(media.getDuration())); - } - } else { - holder.progressLeft.setText(Converter.byteToString(media.getSize())); - holder.progressRight.setText(Converter.getDurationStringLong(media.getDuration())); - holder.progress.setVisibility(View.GONE); - } - } - - actionButtonUtils.configureActionButton(holder.butSecondary, item); - holder.butSecondary.setFocusable(false); - holder.butSecondary.setTag(item); - holder.butSecondary.setOnClickListener(secondaryActionListener); - - Picasso.with(context) - .load(item.getImageUri()) - .fit() - .into(holder.imageView); - - return convertView; - } - - private View.OnClickListener secondaryActionListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - FeedItem item = (FeedItem) v.getTag(); - actionButtonCallback.onActionButtonPressed(item); - } - }; - - - static class Holder { - ImageView dragHandle; - ImageView imageView; - TextView title; - TextView pubDate; - TextView progressLeft; - TextView progressRight; - ProgressBar progress; - ImageButton butSecondary; - } - - public interface ItemAccess { - FeedItem getItem(int position); - int getCount(); - long getItemDownloadedBytes(FeedItem item); - long getItemDownloadSize(FeedItem item); - int getItemDownloadProgressPercent(FeedItem item); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java new file mode 100644 index 000000000..d0266be6d --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -0,0 +1,378 @@ +package de.danoeh.antennapod.adapter; + +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.support.v4.view.MotionEventCompat; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; +import com.joanzapata.iconify.Iconify; +import com.nineoldandroids.view.ViewHelper; + +import org.apache.commons.lang3.StringUtils; + +import java.lang.ref.WeakReference; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.util.LongList; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.fragment.ItemFragment; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; + +/** + * List adapter for the queue. + */ +public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdapter.ViewHolder> { + + private static final String TAG = QueueRecyclerAdapter.class.getSimpleName(); + + private WeakReference<MainActivity> mainActivity; + private final ItemAccess itemAccess; + private final ActionButtonCallback actionButtonCallback; + private final ActionButtonUtils actionButtonUtils; + private final ItemTouchHelper itemTouchHelper; + + private boolean locked; + + private FeedItem selectedItem; + + private final int playingBackGroundColor; + private final int normalBackGroundColor; + + public QueueRecyclerAdapter(MainActivity mainActivity, + ItemAccess itemAccess, + ActionButtonCallback actionButtonCallback, + ItemTouchHelper itemTouchHelper) { + super(); + this.mainActivity = new WeakReference<>(mainActivity); + this.itemAccess = itemAccess; + this.actionButtonUtils = new ActionButtonUtils(mainActivity); + this.actionButtonCallback = actionButtonCallback; + this.itemTouchHelper = itemTouchHelper; + locked = UserPreferences.isQueueLocked(); + + if(UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { + playingBackGroundColor = mainActivity.getResources().getColor(R.color.highlight_dark); + } else { + playingBackGroundColor = mainActivity.getResources().getColor(R.color.highlight_light); + } + normalBackGroundColor = mainActivity.getResources().getColor(android.R.color.transparent); + } + + public void setLocked(boolean locked) { + this.locked = locked; + notifyDataSetChanged(); + } + + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.queue_listitem, parent, false); + return new ViewHolder(view); + } + + public void onBindViewHolder(ViewHolder holder, int pos) { + FeedItem item = itemAccess.getItem(pos); + holder.bind(item); + holder.itemView.setOnLongClickListener(v -> { + selectedItem = item; + return false; + }); + } + + @Nullable + public FeedItem getSelectedItem() { + return selectedItem; + } + + public int getItemCount() { + return itemAccess.getCount(); + } + + public class ViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener, + View.OnCreateContextMenuListener, + ItemTouchHelperViewHolder { + + private final FrameLayout container; + private final ImageView dragHandle; + private final TextView placeholder; + private final ImageView cover; + private final TextView title; + private final TextView pubDate; + private final TextView progressLeft; + private final TextView progressRight; + private final ProgressBar progressBar; + private final ImageButton butSecondary; + + private FeedItem item; + + public ViewHolder(View v) { + super(v); + container = (FrameLayout) v.findViewById(R.id.container); + dragHandle = (ImageView) v.findViewById(R.id.drag_handle); + placeholder = (TextView) v.findViewById(R.id.txtvPlaceholder); + cover = (ImageView) v.findViewById(R.id.imgvCover); + title = (TextView) v.findViewById(R.id.txtvTitle); + pubDate = (TextView) v.findViewById(R.id.txtvPubDate); + progressLeft = (TextView) v.findViewById(R.id.txtvProgressLeft); + progressRight = (TextView) v.findViewById(R.id.txtvProgressRight); + butSecondary = (ImageButton) v.findViewById(R.id.butSecondaryAction); + progressBar = (ProgressBar) v.findViewById(R.id.progressBar); + v.setTag(this); + v.setOnClickListener(this); + v.setOnCreateContextMenuListener(this); + dragHandle.setOnTouchListener((v1, event) -> { + if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { + Log.d(TAG, "startDrag()"); + itemTouchHelper.startDrag(ViewHolder.this); + } + return false; + }); + } + + @Override + public void onClick(View v) { + MainActivity activity = mainActivity.get(); + if (activity != null) { + activity.loadChildFragment(ItemFragment.newInstance(item.getId())); + } + } + + @Override + public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + FeedItem item = itemAccess.getItem(getAdapterPosition()); + + MenuInflater inflater = mainActivity.get().getMenuInflater(); + inflater.inflate(R.menu.queue_context, menu); + + if (item != null) { + menu.setHeaderTitle(item.getTitle()); + } + + FeedItemMenuHandler.MenuInterface contextMenuInterface = (id, visible) -> { + if (menu == null) { + return; + } + MenuItem item1 = menu.findItem(id); + if (item1 != null) { + item1.setVisible(visible); + } + }; + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, + itemAccess.getQueueIds(), itemAccess.getFavoritesIds()); + } + + @Override + public void onItemSelected() { + ViewHelper.setAlpha(itemView, 0.5f); + } + + @Override + public void onItemClear() { + ViewHelper.setAlpha(itemView, 1.0f); + } + + public void bind(FeedItem item) { + this.item = item; + if(locked) { + dragHandle.setVisibility(View.GONE); + } else { + dragHandle.setVisibility(View.VISIBLE); + } + + placeholder.setText(item.getFeed().getTitle()); + + title.setText(item.getTitle()); + FeedMedia media = item.getMedia(); + + title.setText(item.getTitle()); + String pubDateStr = DateUtils.formatAbbrev(mainActivity.get(), item.getPubDate()); + int index = 0; + if(StringUtils.countMatches(pubDateStr, ' ') == 1 || StringUtils.countMatches(pubDateStr, ' ') == 2) { + index = pubDateStr.lastIndexOf(' '); + } else if(StringUtils.countMatches(pubDateStr, '.') == 2) { + index = pubDateStr.lastIndexOf('.'); + } else if(StringUtils.countMatches(pubDateStr, '-') == 2) { + index = pubDateStr.lastIndexOf('-'); + } else if(StringUtils.countMatches(pubDateStr, '/') == 2) { + index = pubDateStr.lastIndexOf('/'); + } + if(index > 0) { + pubDateStr = pubDateStr.substring(0, index+1).trim() + "\n" + pubDateStr.substring(index+1); + } + pubDate.setText(pubDateStr); + + if (media != null) { + final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); + FeedItem.State state = item.getState(); + if (isDownloadingMedia) { + progressLeft.setText(Converter.byteToString(itemAccess.getItemDownloadedBytes(item))); + if(itemAccess.getItemDownloadSize(item) > 0) { + progressRight.setText(Converter.byteToString(itemAccess.getItemDownloadSize(item))); + } else { + progressRight.setText(Converter.byteToString(media.getSize())); + } + progressBar.setProgress(itemAccess.getItemDownloadProgressPercent(item)); + progressBar.setVisibility(View.VISIBLE); + } else if (state == FeedItem.State.PLAYING + || state == FeedItem.State.IN_PROGRESS) { + if (media.getDuration() > 0) { + int progress = (int) (100.0 * media.getPosition() / media.getDuration()); + progressBar.setProgress(progress); + progressBar.setVisibility(View.VISIBLE); + progressLeft.setText(Converter + .getDurationStringLong(media.getPosition())); + progressRight.setText(Converter.getDurationStringLong(media.getDuration())); + } + } else { + if(media.getSize() > 0) { + progressLeft.setText(Converter.byteToString(media.getSize())); + } else if(false == media.checkedOnSizeButUnknown()) { + progressLeft.setText("{fa-spinner}"); + Iconify.addIcons(progressLeft); + NetworkUtils.getFeedMediaSizeObservable(media) + .subscribe( + size -> { + if (size > 0) { + progressLeft.setText(Converter.byteToString(size)); + } else { + progressLeft.setText(""); + } + }, error -> { + progressLeft.setText(""); + Log.e(TAG, Log.getStackTraceString(error)); + }); + } else { + progressLeft.setText(""); + } + progressRight.setText(Converter.getDurationStringLong(media.getDuration())); + progressBar.setVisibility(View.GONE); + } + + if(media.isCurrentlyPlaying()) { + container.setBackgroundColor(playingBackGroundColor); + } else { + container.setBackgroundColor(normalBackGroundColor); + } + } + + actionButtonUtils.configureActionButton(butSecondary, item, true); + butSecondary.setFocusable(false); + butSecondary.setTag(item); + butSecondary.setOnClickListener(secondaryActionListener); + + Glide.with(mainActivity.get()) + .load(item.getImageUri()) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(item.getFeed().getImageUri(), placeholder, cover)); + } + + } + + + private class CoverTarget extends GlideDrawableImageViewTarget { + + private final WeakReference<Uri> fallback; + private final WeakReference<TextView> placeholder; + private final WeakReference<ImageView> cover; + + public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) { + super(imgvCover); + fallback = new WeakReference<>(fallbackUri); + placeholder = new WeakReference<>(txtvPlaceholder); + cover = new WeakReference<>(imgvCover); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + Uri fallbackUri = fallback.get(); + TextView txtvPlaceholder = placeholder.get(); + ImageView imgvCover = cover.get(); + if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { + Glide.with(mainActivity.get()) + .load(fallbackUri) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(null, txtvPlaceholder, imgvCover)); + } + } + + @Override + public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { + super.onResourceReady(drawable, anim); + TextView txtvPlaceholder = placeholder.get(); + if(txtvPlaceholder != null) { + txtvPlaceholder.setVisibility(View.INVISIBLE); + } + } + } + + private View.OnClickListener secondaryActionListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + FeedItem item = (FeedItem) v.getTag(); + actionButtonCallback.onActionButtonPressed(item); + } + }; + + + public interface ItemAccess { + FeedItem getItem(int position); + int getCount(); + long getItemDownloadedBytes(FeedItem item); + long getItemDownloadSize(FeedItem item); + int getItemDownloadProgressPercent(FeedItem item); + LongList getQueueIds(); + LongList getFavoritesIds(); + } + + /** + * Notifies a View Holder of relevant callbacks from + * {@link ItemTouchHelper.Callback}. + */ + public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an + * item as being moved or swiped. + * Implementations should update the item view to indicate + * it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the + * move or swipe, and the active item state should be cleared. + */ + void onItemClear(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java index cedce7903..83f5dcb4d 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java @@ -8,13 +8,15 @@ import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.SearchResult; +import de.danoeh.antennapod.core.glide.ApGlideSettings; /** * List adapter for search activity. @@ -73,9 +75,13 @@ public class SearchlistAdapter extends BaseAdapter { holder.title.setText(feed.getTitle()); holder.subtitle.setVisibility(View.GONE); - Picasso.with(context) + Glide.with(context) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(holder.cover); } else if (component.getClass() == FeedItem.class) { @@ -86,9 +92,13 @@ public class SearchlistAdapter extends BaseAdapter { holder.subtitle.setText(result.getSubtitle()); } - Picasso.with(context) + Glide.with(context) .load(item.getFeed().getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(holder.cover); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java index 5928ad119..0ff976b98 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java @@ -60,7 +60,7 @@ public class SubscriptionsAdapter extends BaseAdapter { holder = (Holder) convertView.getTag(); } - holder.itemView.setFeed(item, mItemAccess.getNumberOfUnreadFeedItems(item.getId())); + holder.itemView.setFeed(item); return convertView; } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java index b85709c5e..743f9fc86 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java @@ -8,13 +8,15 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.apache.commons.lang3.StringUtils; import java.util.List; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; /** @@ -49,9 +51,13 @@ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> { } if (StringUtils.isNotBlank(podcast.getLogoUrl())) { - Picasso.with(convertView.getContext()) + Glide.with(convertView.getContext()) .load(podcast.getLogoUrl()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(holder.image); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java index 4fc2838b7..47ac4c757 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java @@ -1,23 +1,19 @@ package de.danoeh.antennapod.adapter.itunes; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.AsyncTask; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; + +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.io.IOException; import java.util.List; import de.danoeh.antennapod.R; @@ -46,55 +42,6 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { this.context = context; } - /** - * Updates the given ImageView with the image in the given Podcast's imageUrl - */ - class FetchImageTask extends AsyncTask<Void,Void,Bitmap>{ - /** - * Current podcast - */ - private final Podcast podcast; - - /** - * ImageView to be updated - */ - private final ImageView imageView; - - /** - * Constructor - * - * @param podcast Podcast that has the image - * @param imageView UI image to be updated - */ - FetchImageTask(Podcast podcast, ImageView imageView){ - this.podcast = podcast; - this.imageView = imageView; - } - - //Get the image from the url - @Override - protected Bitmap doInBackground(Void... params) { - HttpClient client = new DefaultHttpClient(); - HttpGet get = new HttpGet(podcast.imageUrl); - try { - HttpResponse response = client.execute(get); - return BitmapFactory.decodeStream(response.getEntity().getContent()); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - //Set the background image for the podcast - @Override - protected void onPostExecute(Bitmap img) { - super.onPostExecute(img); - if(img!=null) { - imageView.setImageBitmap(img); - } - } - } - @Override public View getView(int position, View convertView, ViewGroup parent) { //Current podcast @@ -119,9 +66,21 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { //Set the title viewHolder.titleView.setText(podcast.title); + if(!podcast.feedUrl.contains("itunes.apple.com")) { + viewHolder.urlView.setText(podcast.feedUrl); + viewHolder.urlView.setVisibility(View.VISIBLE); + } else { + viewHolder.urlView.setVisibility(View.GONE); + } //Update the empty imageView with the image from the feed - new FetchImageTask(podcast,viewHolder.coverView).execute(); + Glide.with(context) + .load(podcast.imageUrl) + .placeholder(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .fitCenter() + .dontAnimate() + .into(viewHolder.coverView); //Feed the grid view return view; @@ -142,6 +101,8 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { */ public final TextView titleView; + public final TextView urlView; + /** * Constructor @@ -150,6 +111,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { PodcastViewHolder(View view){ coverView = (ImageView) view.findViewById(R.id.imgvCover); titleView = (TextView) view.findViewById(R.id.txtvTitle); + urlView = (TextView) view.findViewById(R.id.txtvUrl); } } @@ -172,16 +134,47 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { */ public final String feedUrl; + + private Podcast(String title, String imageUrl, String feedUrl) { + this.title = title; + this.imageUrl = imageUrl; + this.feedUrl = feedUrl; + } + /** - * Constructor. + * Constructs a Podcast instance from a iTunes search result * * @param json object holding the podcast information * @throws JSONException */ - public Podcast(JSONObject json) throws JSONException { - title = json.getString("collectionName"); - imageUrl = json.getString("artworkUrl100"); - feedUrl = json.getString("feedUrl"); + public static Podcast fromSearch(JSONObject json) throws JSONException { + String title = json.getString("collectionName"); + String imageUrl = json.getString("artworkUrl100"); + String feedUrl = json.getString("feedUrl"); + return new Podcast(title, imageUrl, feedUrl); } + + /** + * 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.valueOf(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); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java index 6bba956a6..5c24c2822 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java @@ -1,11 +1,13 @@ package de.danoeh.antennapod.asynctask; import android.annotation.SuppressLint; -import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; import android.os.AsyncTask; +import android.support.v7.app.AlertDialog; import android.util.Log; import java.io.File; @@ -47,7 +49,7 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { OpmlWriter opmlWriter = new OpmlWriter(); if (output == null) { output = new File( - UserPreferences.getDataFolder(context, EXPORT_DIR), + UserPreferences.getDataFolder(EXPORT_DIR), DEFAULT_OUTPUT_NAME); if (output.exists()) { Log.w(TAG, "Overwriting previously exported file."); @@ -57,7 +59,7 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { OutputStreamWriter writer = null; try { writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8); - opmlWriter.writeDocument(DBReader.getFeedList(context), writer); + opmlWriter.writeDocument(DBReader.getFeedList(), writer); } catch (IOException e) { e.printStackTrace(); exception = e; @@ -93,7 +95,17 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { alert.setTitle(R.string.opml_export_success_title); alert.setMessage(context .getString(R.string.opml_export_success_sum) - + output.toString()); + + output.toString()) + .setPositiveButton(R.string.send_label, (dialog, which) -> { + Uri outputUri = Uri.fromFile(output); + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, + context.getResources().getText(R.string.opml_export_label)); + sendIntent.putExtra(Intent.EXTRA_STREAM, outputUri); + sendIntent.setType("text/plain"); + context.startActivity(Intent.createChooser(sendIntent, + context.getResources().getText(R.string.send_label))); + }); } alert.create().show(); } diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java index 5486bc4fb..86636485d 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -1,12 +1,12 @@ package de.danoeh.antennapod.asynctask; import android.annotation.SuppressLint; -import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.os.AsyncTask; +import android.support.v7.app.AlertDialog; import android.util.Log; import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.R; @@ -37,8 +37,7 @@ public class OpmlImportWorker extends @Override protected ArrayList<OpmlElement> doInBackground(Void... params) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting background work"); + Log.d(TAG, "Starting background work"); if (mReader==null) { return null; @@ -72,21 +71,14 @@ public class OpmlImportWorker extends } progDialog.dismiss(); if (exception != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "An error occurred while trying to parse the opml document"); + Log.d(TAG, "An error occurred while trying to parse the opml document"); AlertDialog.Builder alert = new AlertDialog.Builder(context); alert.setTitle(R.string.error_label); alert.setMessage(context.getString(R.string.opml_reader_error) + exception.getMessage()); - alert.setNeutralButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - - }); + alert.setNeutralButton(android.R.string.ok, (dialog, which) -> { + dialog.dismiss(); + }); alert.create().show(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java index 4d9be5d78..008aacfa5 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java @@ -8,7 +8,6 @@ import android.content.Intent; import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.activity.StorageErrorActivity; import de.danoeh.antennapod.core.ApplicationCallbacks; -import de.danoeh.antennapod.core.preferences.UserPreferences; public class ApplicationCallbacksImpl implements ApplicationCallbacks { @@ -22,8 +21,4 @@ public class ApplicationCallbacksImpl implements ApplicationCallbacks { return new Intent(context, StorageErrorActivity.class); } - @Override - public void setUpdateInterval(long updateInterval) { - UserPreferences.restartUpdateAlarm(updateInterval, updateInterval); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index 10666aa36..932b9d22f 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -14,7 +14,6 @@ public class ClientConfigurator { ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); ClientConfig.gpodnetCallbacks = new GpodnetCallbacksImpl(); ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); - ClientConfig.storageCallbacks = new StorageCallbacksImpl(); ClientConfig.flattrCallbacks = new FlattrCallbacksImpl(); ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl(); } diff --git a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java index 75dcb2ef1..9f8af1142 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.config; import de.danoeh.antennapod.core.DBTasksCallbacks; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.APCleanupAlgorithm; import de.danoeh.antennapod.core.storage.APDownloadAlgorithm; import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm; @@ -15,6 +16,6 @@ public class DBTasksCallbacksImpl implements DBTasksCallbacks { @Override public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() { - return new APCleanupAlgorithm(); + return UserPreferences.getEpisodeCleanupAlgorithm(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java deleted file mode 100644 index 943e05690..000000000 --- a/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java +++ /dev/null @@ -1,153 +0,0 @@ -package de.danoeh.antennapod.config; - - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - -import de.danoeh.antennapod.core.StorageCallbacks; -import de.danoeh.antennapod.core.storage.PodDBAdapter; - -public class StorageCallbacksImpl implements StorageCallbacks { - - @Override - public int getDatabaseVersion() { - return 15; - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " - + newVersion + "."); - if (oldVersion <= 1) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " - + PodDBAdapter.KEY_TYPE + " TEXT"); - } - if (oldVersion <= 2) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + PodDBAdapter.KEY_LINK + " TEXT"); - } - if (oldVersion <= 3) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + PodDBAdapter.KEY_ITEM_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 4) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " - + PodDBAdapter.KEY_FEED_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 5) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + PodDBAdapter.KEY_REASON_DETAILED + " TEXT"); - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE + " TEXT"); - } - if (oldVersion <= 6) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + PodDBAdapter.KEY_CHAPTER_TYPE + " INTEGER"); - } - if (oldVersion <= 7) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE - + " INTEGER"); - } - if (oldVersion <= 8) { - final int KEY_ID_POSITION = 0; - final int KEY_MEDIA_POSITION = 1; - - // Add feeditem column to feedmedia table - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + PodDBAdapter.KEY_FEEDITEM - + " INTEGER"); - Cursor feeditemCursor = db.query(PodDBAdapter.TABLE_NAME_FEED_ITEMS, - new String[]{PodDBAdapter.KEY_ID, PodDBAdapter.KEY_MEDIA}, "? > 0", - new String[]{PodDBAdapter.KEY_MEDIA}, null, null, null); - if (feeditemCursor.moveToFirst()) { - db.beginTransaction(); - ContentValues contentValues = new ContentValues(); - do { - long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION); - contentValues.put(PodDBAdapter.KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION)); - db.update(PodDBAdapter.TABLE_NAME_FEED_MEDIA, contentValues, PodDBAdapter.KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); - contentValues.clear(); - } while (feeditemCursor.moveToNext()); - db.setTransactionSuccessful(); - db.endTransaction(); - } - feeditemCursor.close(); - } - if (oldVersion <= 9) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD - + " INTEGER DEFAULT 1"); - } - if (oldVersion <= 10) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS - + " INTEGER"); - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS - + " INTEGER"); - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + PodDBAdapter.KEY_PLAYED_DURATION - + " INTEGER"); - } - if (oldVersion <= 11) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_USERNAME - + " TEXT"); - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_PASSWORD - + " TEXT"); - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE - + " INTEGER"); - } - if (oldVersion <= 12) { - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_IS_PAGED + " INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_NEXT_PAGE_LINK + " TEXT"); - } - if (oldVersion <= 13) { - // remove duplicate rows in "Chapters" table that were created because of a bug. - db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " + - "(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)", - PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS, - PodDBAdapter.KEY_ID, - PodDBAdapter.KEY_ID, - PodDBAdapter.KEY_ID, - PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS, - PodDBAdapter.KEY_TITLE, - PodDBAdapter.KEY_START, - PodDBAdapter.KEY_FEEDITEM, - PodDBAdapter.KEY_LINK, - PodDBAdapter.KEY_CHAPTER_TYPE)); - } - if(oldVersion <= 14) { - - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER"); - db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " = " - + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD - + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS - + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID - + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")"); - - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_HIDE + " TEXT"); - - - db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0"); - - // create indexes - db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED); - db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_IMAGE); - db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM); - db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM); - db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java index 1585f9b86..75b1bc8d2 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java @@ -2,9 +2,9 @@ package de.danoeh.antennapod.dialog; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.support.v7.app.AlertDialog; import android.view.View; import android.widget.CheckBox; import android.widget.SeekBar; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java new file mode 100644 index 000000000..6432ebd4e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -0,0 +1,376 @@ +package de.danoeh.antennapod.dialog; + +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.Fragment; +import android.support.v4.util.ArrayMap; +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.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +import com.joanzapata.iconify.Icon; +import com.joanzapata.iconify.IconDrawable; +import com.joanzapata.iconify.fonts.FontAwesomeIcons; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.util.LongList; + +public class EpisodesApplyActionFragment extends Fragment { + + public String TAG = "EpisodeActionFragment"; + + private ListView mListView; + private ArrayAdapter<String> mAdapter; + + private Button btnAddToQueue; + private Button btnMarkAsPlayed; + private Button btnMarkAsUnplayed; + private Button btnDownload; + private Button btnDelete; + + private final Map<Long,FeedItem> idMap; + private final List<FeedItem> episodes; + private final List<String> titles = new ArrayList(); + private final LongList checkedIds = new LongList(); + + private MenuItem mSelectToggle; + + private int textColor; + + public EpisodesApplyActionFragment() { + this.episodes = new ArrayList<>(); + this.idMap = new ArrayMap<>(); + } + + public void setEpisodes(List<FeedItem> episodes) { + this.episodes.clear(); + this.episodes.addAll(episodes); + this.idMap.clear(); + for(FeedItem episode : episodes) { + this.idMap.put(episode.getId(), episode); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.episodes_apply_action_fragment, container, false); + + mListView = (ListView) view.findViewById(android.R.id.list); + mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + mListView.setOnItemClickListener((ListView, view1, position, rowId) -> { + long id = episodes.get(position).getId(); + if (checkedIds.contains(id)) { + checkedIds.remove(id); + } else { + checkedIds.add(id); + } + refreshCheckboxes(); + }); + + for(FeedItem episode : episodes) { + titles.add(episode.getTitle()); + } + + mAdapter = new ArrayAdapter<>(getActivity(), + android.R.layout.simple_list_item_multiple_choice, titles); + mListView.setAdapter(mAdapter); + checkAll(); + + btnAddToQueue = (Button) view.findViewById(R.id.btnAddToQueue); + btnAddToQueue.setOnClickListener(v -> queueChecked()); + btnMarkAsPlayed = (Button) view.findViewById(R.id.btnMarkAsPlayed); + btnMarkAsPlayed.setOnClickListener(v -> markedCheckedPlayed()); + btnMarkAsUnplayed = (Button) view.findViewById(R.id.btnMarkAsUnplayed); + btnMarkAsUnplayed.setOnClickListener(v -> markedCheckedUnplayed()); + btnDownload = (Button) view.findViewById(R.id.btnDownload); + btnDownload.setOnClickListener(v -> downloadChecked()); + btnDelete = (Button) view.findViewById(R.id.btnDelete); + btnDelete.setOnClickListener(v -> deleteChecked()); + + return view; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.episodes_apply_action_options, menu); + + int[] attrs = { android.R.attr.textColor }; + TypedArray ta = getActivity().obtainStyledAttributes(attrs); + textColor = ta.getColor(0, Color.GRAY); + ta.recycle(); + + mSelectToggle = menu.findItem(R.id.select_toggle); + mSelectToggle.setOnMenuItemClickListener(item -> { + if (checkedIds.size() == episodes.size()) { + checkNone(); + } else { + checkAll(); + } + return true; + }); + } + + @Override + public void onPrepareOptionsMenu (Menu menu) { + /* + * Prepare icon for select toggle button + */ + + // Find icon attribute + int[] icon = new int[1]; + if(checkedIds.size() == episodes.size()) icon[0] = R.attr.ic_check_box; + else if(checkedIds.size() == 0) icon[0] = R.attr.ic_check_box_outline; + else icon[0] = R.attr.ic_indeterminate_check_box; + + // Get Drawable from attribute + TypedArray a = getActivity().obtainStyledAttributes(icon); + Drawable iconDrawable = a.getDrawable(0); + a.recycle(); + + // Set icon + mSelectToggle.setIcon(iconDrawable); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int resId = 0; + switch(item.getItemId()) { + case R.id.select_options: + return true; + case R.id.check_all: + checkAll(); + resId = R.string.selected_all_label; + break; + case R.id.check_none: + checkNone(); + resId = R.string.deselected_all_label; + break; + case R.id.check_played: + checkPlayed(true); + resId = R.string.selected_played_label; + break; + case R.id.check_unplayed: + checkPlayed(false); + resId = R.string.selected_unplayed_label; + break; + case R.id.check_downloaded: + checkDownloaded(true); + resId = R.string.selected_downloaded_label; + break; + case R.id.check_not_downloaded: + checkDownloaded(false); + resId = R.string.selected_not_downloaded_label; + break; + case R.id.sort_title_a_z: + sortByTitle(false); + return true; + case R.id.sort_title_z_a: + sortByTitle(true); + return true; + case R.id.sort_date_new_old: + sortByDate(true); + return true; + case R.id.sort_date_old_new: + sortByDate(false); + return true; + case R.id.sort_duration_long_short: + sortByDuration(true); + return true; + case R.id.sort_duration_short_long: + sortByDuration(false); + return true; + } + if(resId != 0) { + Toast.makeText(getActivity(), resId, Toast.LENGTH_SHORT).show(); + return true; + } else { + return false; + } + } + + private void sortByTitle(final boolean reverse) { + Collections.sort(episodes, (lhs, rhs) -> { + if (reverse) { + return -1 * lhs.getTitle().compareTo(rhs.getTitle()); + } else { + return lhs.getTitle().compareTo(rhs.getTitle()); + } + }); + refreshTitles(); + refreshCheckboxes(); + } + + private void sortByDate(final boolean reverse) { + Collections.sort(episodes, (lhs, rhs) -> { + if (lhs.getPubDate() == null) { + return -1; + } else if (rhs.getPubDate() == null) { + return 1; + } + int code = lhs.getPubDate().compareTo(rhs.getPubDate()); + if (reverse) { + return -1 * code; + } else { + return code; + } + }); + refreshTitles(); + refreshCheckboxes(); + } + + private void sortByDuration(final boolean reverse) { + Collections.sort(episodes, (lhs, rhs) -> { + int ordering; + if (false == lhs.hasMedia()) { + ordering = 1; + } else if (false == rhs.hasMedia()) { + ordering = -1; + } else { + ordering = lhs.getMedia().getDuration() - rhs.getMedia().getDuration(); + } + if(reverse) { + return -1 * ordering; + } else { + return ordering; + } + }); + refreshTitles(); + refreshCheckboxes(); + } + + private void checkAll() { + for (FeedItem episode : episodes) { + if(false == checkedIds.contains(episode.getId())) { + checkedIds.add(episode.getId()); + } + } + refreshCheckboxes(); + } + + private void checkNone() { + checkedIds.clear(); + refreshCheckboxes(); + } + + private void checkPlayed(boolean isPlayed) { + for (FeedItem episode : episodes) { + if(episode.isPlayed() == isPlayed) { + if(!checkedIds.contains(episode.getId())) { + checkedIds.add(episode.getId()); + } + } else { + if(checkedIds.contains(episode.getId())) { + checkedIds.remove(episode.getId()); + } + } + } + refreshCheckboxes(); + } + + private void checkDownloaded(boolean isDownloaded) { + for (FeedItem episode : episodes) { + if(episode.hasMedia() && episode.getMedia().isDownloaded() == isDownloaded) { + if(!checkedIds.contains(episode.getId())) { + checkedIds.add(episode.getId()); + } + } else { + if(checkedIds.contains(episode.getId())) { + checkedIds.remove(episode.getId()); + } + } + } + refreshCheckboxes(); + } + + private void refreshTitles() { + titles.clear(); + for(FeedItem episode : episodes) { + titles.add(episode.getTitle()); + } + mAdapter.notifyDataSetChanged(); + } + + private void refreshCheckboxes() { + for (int i = 0; i < episodes.size(); i++) { + FeedItem episode = episodes.get(i); + boolean checked = checkedIds.contains(episode.getId()); + mListView.setItemChecked(i, checked); + } + ActivityCompat.invalidateOptionsMenu(EpisodesApplyActionFragment.this.getActivity()); + } + + private void queueChecked() { + DBWriter.addQueueItem(getActivity(), true, checkedIds.toArray()); + close(); + } + + private void markedCheckedPlayed() { + DBWriter.markItemPlayed(FeedItem.PLAYED, checkedIds.toArray()); + close(); + } + + private void markedCheckedUnplayed() { + DBWriter.markItemPlayed(FeedItem.UNPLAYED, checkedIds.toArray()); + close(); + } + + private void downloadChecked() { + // download the check episodes in the same order as they are currently displayed + List<FeedItem> toDownload = new ArrayList<FeedItem>(checkedIds.size()); + for(FeedItem episode : episodes) { + if(checkedIds.contains(episode.getId())) { + toDownload.add(episode); + } + } + try { + DBTasks.downloadFeedItems(getActivity(), toDownload.toArray(new FeedItem[0])); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); + } + close(); + } + + private void deleteChecked() { + for(long id : checkedIds.toArray()) { + FeedItem episode = idMap.get(id); + if(episode.hasMedia()) { + DBWriter.deleteFeedMediaOfItem(getActivity(), episode.getMedia().getId()); + } + } + close(); + } + + private void close() { + getActivity().getSupportFragmentManager().popBackStack(); + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java index 16fb77f2a..5f531e88f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.dialog; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.InputType; import android.view.View; @@ -26,28 +26,19 @@ public class GpodnetSetHostnameDialog { et.setInputType(InputType.TYPE_TEXT_VARIATION_URI); dialog.setTitle(R.string.pref_gpodnet_sethostname_title) .setView(setupContentView(context, et)) - .setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final Editable e = et.getText(); - if (e != null) { - GpodnetPreferences.setHostname(e.toString()); - } - dialog.dismiss(); + .setPositiveButton(R.string.confirm_label, (dialog1, which) -> { + final Editable e = et.getText(); + if (e != null) { + GpodnetPreferences.setHostname(e.toString()); } + dialog1.dismiss(); }) - .setNegativeButton(R.string.cancel_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } + .setNegativeButton(R.string.cancel_label, (dialog1, which) -> { + dialog1.cancel(); }) - .setNeutralButton(R.string.pref_gpodnet_sethostname_use_default_host, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST); - dialog.dismiss(); - } + .setNeutralButton(R.string.pref_gpodnet_sethostname_use_default_host, (dialog1, which) -> { + GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST); + dialog1.dismiss(); }) .setCancelable(true); return dialog.show(); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java new file mode 100644 index 000000000..ed0db92a4 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java @@ -0,0 +1,136 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.afollestad.materialdialogs.MaterialDialog; + +import java.lang.ref.WeakReference; +import java.util.concurrent.TimeUnit; + +import de.danoeh.antennapod.R; + +public class RatingDialog { + + private static final String TAG = RatingDialog.class.getSimpleName(); + private static final int AFTER_DAYS = 7; + + private static WeakReference<Context> mContext; + private static SharedPreferences mPreferences; + private static Dialog mDialog; + + private static final String PREFS_NAME = "RatingPrefs"; + private static final String KEY_RATED = "KEY_WAS_RATED"; + private static final String KEY_FIRST_START_DATE = "KEY_FIRST_HIT_DATE"; + + public static void init(Context context) { + mContext = new WeakReference<>(context); + mPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + + long firstDate = mPreferences.getLong(KEY_FIRST_START_DATE, 0); + if (firstDate == 0) { + resetStartDate(); + } + } + + public static void check() { + if (mDialog != null && mDialog.isShowing()) { + return; + } + if (shouldShow()) { + try { + mDialog = createDialog(); + if (mDialog != null) { + mDialog.show(); + } + } catch (Exception e) { + Log.e(TAG, Log.getStackTraceString(e)); + } + } + } + + public static void rateNow() { + Context context = mContext.get(); + if(context == null) { + return; + } + final String appPackage = "de.danoeh.antennapod"; + final Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=" + appPackage); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + saveRated(); + } + + public static boolean rated() { + return mPreferences.getBoolean(KEY_RATED, false); + } + + public static void saveRated() { + mPreferences + .edit() + .putBoolean(KEY_RATED, true) + .apply(); + } + + private static void resetStartDate() { + mPreferences + .edit() + .putLong(KEY_FIRST_START_DATE, System.currentTimeMillis()) + .apply(); + } + + private static boolean shouldShow() { + if (rated()) { + return false; + } + + long now = System.currentTimeMillis(); + long firstDate = mPreferences.getLong(KEY_FIRST_START_DATE, now); + long diff = now - firstDate; + long diffDays = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); + if (diffDays >= AFTER_DAYS) { + return true; + } else { + return false; + } + } + + @Nullable + private static MaterialDialog createDialog() { + Context context = mContext.get(); + if(context == null) { + return null; + } + MaterialDialog dialog = new MaterialDialog.Builder(context) + .title(R.string.rating_title) + .content(R.string.rating_message) + .positiveText(R.string.rating_now_label) + .negativeText(R.string.rating_never_label) + .neutralText(R.string.rating_later_label) + .callback(new MaterialDialog.ButtonCallback() { + @Override + public void onPositive(MaterialDialog dialog) { + rateNow(); + } + + @Override + public void onNegative(MaterialDialog dialog) { + saveRated(); + } + + @Override + public void onNeutral(MaterialDialog dialog) { + resetStartDate(); + } + }) + .cancelListener(dialog1 -> resetStartDate()) + .build(); + return dialog; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java new file mode 100644 index 000000000..930079e40 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -0,0 +1,154 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.Toast; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; + +import java.util.concurrent.TimeUnit; + +import de.danoeh.antennapod.R; + +public abstract class SleepTimerDialog { + + private static final String TAG = SleepTimerDialog.class.getSimpleName(); + + private static final int DEFAULT_SPINNER_POSITION = 1; + + private Context context; + private String PREF_NAME = "SleepTimerDialog"; + private String PREF_VALUE = "LastValue"; + private String PREF_TIME_UNIT = "LastTimeUnit"; + private String PREF_VIBRATE = "Vibrate"; + private String PREF_SHAKE_TO_RESET = "ShakeToReset"; + private SharedPreferences prefs; + + private MaterialDialog dialog; + private EditText etxtTime; + private Spinner spTimeUnit; + private CheckBox cbShakeToReset; + private CheckBox cbVibrate; + + + private TimeUnit[] units = { TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS }; + + public SleepTimerDialog(Context context) { + this.context = context; + prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + } + + public MaterialDialog createNewDialog() { + MaterialDialog.Builder builder = new MaterialDialog.Builder(context); + builder.title(R.string.set_sleeptimer_label); + builder.customView(R.layout.time_dialog, false); + builder.positiveText(R.string.set_sleeptimer_label); + builder.negativeText(R.string.cancel_label); + builder.callback(new MaterialDialog.ButtonCallback() { + @Override + public void onNegative(MaterialDialog dialog) { + dialog.dismiss(); + } + + @Override + public void onPositive(MaterialDialog dialog) { + try { + savePreferences(); + long input = readTimeMillis(); + onTimerSet(input, cbShakeToReset.isChecked(), cbVibrate.isChecked()); + dialog.dismiss(); + } catch (NumberFormatException e) { + e.printStackTrace(); + Toast toast = Toast.makeText(context, R.string.time_dialog_invalid_input, + Toast.LENGTH_LONG); + toast.show(); + } + } + }); + dialog = builder.build(); + + View view = dialog.getView(); + etxtTime = (EditText) view.findViewById(R.id.etxtTime); + spTimeUnit = (Spinner) view.findViewById(R.id.spTimeUnit); + cbShakeToReset = (CheckBox) view.findViewById(R.id.cbShakeToReset); + cbVibrate = (CheckBox) view.findViewById(R.id.cbVibrate); + + etxtTime.setText(prefs.getString(PREF_VALUE, "15")); + etxtTime.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + checkInputLength(s.length()); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + etxtTime.postDelayed(() -> { + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT); + }, 100); + + String[] spinnerContent = new String[] { + context.getString(R.string.time_seconds), + context.getString(R.string.time_minutes), + context.getString(R.string.time_hours) }; + ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, spinnerContent); + spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spTimeUnit.setAdapter(spinnerAdapter); + int selection = prefs.getInt(PREF_TIME_UNIT, DEFAULT_SPINNER_POSITION); + spTimeUnit.setSelection(selection); + + cbShakeToReset.setChecked(prefs.getBoolean(PREF_SHAKE_TO_RESET, true)); + cbVibrate.setChecked(prefs.getBoolean(PREF_VIBRATE, true)); + + return dialog; + } + + private void checkInputLength(int length) { + if (length > 0) { + Log.d(TAG, "Length is larger than 0, enabling confirm button"); + dialog.getActionButton(DialogAction.POSITIVE).setEnabled(true); + } else { + Log.d(TAG, "Length is smaller than 0, disabling confirm button"); + dialog.getActionButton(DialogAction.POSITIVE).setEnabled(false); + } + } + + public abstract void onTimerSet(long millis, boolean shakeToReset, boolean vibrate); + + private long readTimeMillis() { + TimeUnit selectedUnit = units[spTimeUnit.getSelectedItemPosition()]; + long value = Long.valueOf(etxtTime.getText().toString()); + return selectedUnit.toMillis(value); + } + + private void savePreferences() { + prefs.edit() + .putString(PREF_VALUE, etxtTime.getText().toString()) + .putInt(PREF_TIME_UNIT, spTimeUnit.getSelectedItemPosition()) + .putBoolean(PREF_SHAKE_TO_RESET, cbShakeToReset.isChecked()) + .putBoolean(PREF_VIBRATE, cbVibrate.isChecked()) + .apply(); + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java deleted file mode 100644 index 6561d501e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java +++ /dev/null @@ -1,138 +0,0 @@ -package de.danoeh.antennapod.dialog; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.View; -import android.view.Window; -import android.view.inputmethod.InputMethodManager; -import android.widget.*; -import de.danoeh.antennapod.core.BuildConfig; -import de.danoeh.antennapod.R; - -import java.util.concurrent.TimeUnit; - -public abstract class TimeDialog extends Dialog { - private static final String TAG = "TimeDialog"; - - private static final int DEFAULT_SPINNER_POSITION = 1; - - private Context context; - - private EditText etxtTime; - private Spinner spTimeUnit; - private Button butConfirm; - private Button butCancel; - - private TimeUnit[] units = {TimeUnit.SECONDS, TimeUnit.MINUTES, - TimeUnit.HOURS}; - - public TimeDialog(Context context, int titleTextId, int leftButtonTextId) { - super(context); - this.context = context; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - String[] spinnerContent = new String[]{context.getString(R.string.time_unit_seconds), - context.getString(R.string.time_unit_minutes), - context.getString(R.string.time_unit_hours)}; - - setContentView(R.layout.time_dialog); - etxtTime = (EditText) findViewById(R.id.etxtTime); - spTimeUnit = (Spinner) findViewById(R.id.spTimeUnit); - butConfirm = (Button) findViewById(R.id.butConfirm); - butCancel = (Button) findViewById(R.id.butCancel); - - butConfirm.setText(R.string.set_sleeptimer_label); - butCancel.setText(R.string.cancel_label); - setTitle(R.string.set_sleeptimer_label); - ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>( - this.getContext(), android.R.layout.simple_spinner_item, - spinnerContent); - spinnerAdapter - .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spTimeUnit.setAdapter(spinnerAdapter); - spTimeUnit.setSelection(DEFAULT_SPINNER_POSITION); - butCancel.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - dismiss(); - } - }); - butConfirm.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - try { - long input = readTimeMillis(); - onTimeEntered(input); - dismiss(); - } catch (NumberFormatException e) { - e.printStackTrace(); - Toast toast = Toast.makeText(context, - R.string.time_dialog_invalid_input, - Toast.LENGTH_LONG); - toast.show(); - } - } - }); - etxtTime.addTextChangedListener(new TextWatcher() { - - @Override - public void afterTextChanged(Editable s) { - checkInputLength(s.length()); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - - } - }); - checkInputLength(etxtTime.getText().length()); - etxtTime.postDelayed(new Runnable() { - @Override - public void run() { - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT); - } - }, 100); - - - - } - - private void checkInputLength(int length) { - if (length > 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Length is larger than 0, enabling confirm button"); - butConfirm.setEnabled(true); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Length is smaller than 0, disabling confirm button"); - butConfirm.setEnabled(false); - } - } - - public abstract void onTimeEntered(long millis); - - private long readTimeMillis() { - TimeUnit selectedUnit = units[spTimeUnit.getSelectedItemPosition()]; - long value = Long.valueOf(etxtTime.getText().toString()); - return selectedUnit.toMillis(value); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java index 8eba51540..3ed82b9bd 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -1,100 +1,124 @@ package de.danoeh.antennapod.dialog; -import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; +import android.os.Build; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.view.View; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; import java.util.Arrays; import java.util.List; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.IntentUtils; + public class VariableSpeedDialog { - private VariableSpeedDialog() { - } - - public static void showDialog(final Context context) { - if (com.aocate.media.MediaPlayer.isPrestoLibraryInstalled(context)) { - showSpeedSelectorDialog(context); - } else { - showGetPluginDialog(context); - } - } - - private static void showGetPluginDialog(final Context context) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.no_playback_plugin_title); - builder.setMessage(R.string.no_playback_plugin_msg); - builder.setNegativeButton(R.string.close_label, null); - builder.setPositiveButton(R.string.download_plugin_label, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - try { - Intent playStoreIntent = new Intent( - Intent.ACTION_VIEW, - Uri.parse("market://details?id=com.falconware.prestissimo")); - context.startActivity(playStoreIntent); - } catch (ActivityNotFoundException e) { - // this is usually thrown on an emulator if the Android market is not installed - e.printStackTrace(); - } - } - }); - builder.create().show(); - } - - private static void showSpeedSelectorDialog(final Context context) { - final String[] speedValues = context.getResources().getStringArray( - R.array.playback_speed_values); - // According to Java spec these get initialized to false on creation - final boolean[] speedChecked = new boolean[speedValues.length]; - - // Build the "isChecked" array so that multiChoice dialog is - // populated correctly - List<String> selectedSpeedList = Arrays.asList(UserPreferences - .getPlaybackSpeedArray()); - for (int i = 0; i < speedValues.length; i++) { - speedChecked[i] = selectedSpeedList.contains(speedValues[i]); - } - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.set_playback_speed_label); - builder.setMultiChoiceItems(R.array.playback_speed_values, - speedChecked, new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, - boolean isChecked) { - speedChecked[which] = isChecked; - } - - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int choiceCount = 0; - for (int i = 0; i < speedChecked.length; i++) { - if (speedChecked[i]) { - choiceCount++; - } - } - String[] newSpeedValues = new String[choiceCount]; - int newSpeedIndex = 0; - for (int i = 0; i < speedChecked.length; i++) { - if (speedChecked[i]) { - newSpeedValues[newSpeedIndex++] = speedValues[i]; - } - } - - UserPreferences.setPlaybackSpeedArray(newSpeedValues); - - } - }); - builder.create().show(); - } + + private static final String TAG = VariableSpeedDialog.class.getSimpleName(); + + private static final Intent playStoreIntent = new Intent(Intent.ACTION_VIEW, + Uri.parse("market://details?id=com.falconware.prestissimo")); + + private VariableSpeedDialog() { + } + + public static void showDialog(final Context context) { + if (org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(context) + || UserPreferences.useSonic() + || Build.VERSION.SDK_INT >= 23) { + showSpeedSelectorDialog(context); + } else { + showGetPluginDialog(context, true); + } + } + + public static void showGetPluginDialog(final Context context) { + showGetPluginDialog(context, false); + } + + private static void showGetPluginDialog(final Context context, boolean showSpeedSelector) { + MaterialDialog.Builder builder = new MaterialDialog.Builder(context); + builder.title(R.string.no_playback_plugin_title); + builder.content(R.string.no_playback_plugin_or_sonic_msg); + builder.positiveText(R.string.enable_sonic); + builder.negativeText(R.string.download_plugin_label); + builder.neutralText(R.string.close_label); + builder.onPositive((dialog, which) -> { + if (Build.VERSION.SDK_INT >= 16) { // just to be safe + UserPreferences.enableSonic(true); + if(showSpeedSelector) { + showSpeedSelectorDialog(context); + } + } + }); + builder.onNegative((dialog, which) -> { + try { + context.startActivity(playStoreIntent); + } catch (ActivityNotFoundException e) { + // this is usually thrown on an emulator if the Android market is not installed + Log.e(TAG, Log.getStackTraceString(e)); + } + }); + builder.forceStacking(true); + MaterialDialog dialog = builder.show(); + if (Build.VERSION.SDK_INT < 16) { + View pos = dialog.getActionButton(DialogAction.POSITIVE); + pos.setEnabled(false); + } + if(!IntentUtils.isCallable(context.getApplicationContext(), playStoreIntent)) { + View pos = dialog.getActionButton(DialogAction.NEGATIVE); + pos.setEnabled(false); + } + } + + private static void showSpeedSelectorDialog(final Context context) { + final String[] speedValues = context.getResources().getStringArray( + R.array.playback_speed_values); + // According to Java spec these get initialized to false on creation + final boolean[] speedChecked = new boolean[speedValues.length]; + + // Build the "isChecked" array so that multiChoice dialog is + // populated correctly + List<String> selectedSpeedList = Arrays.asList(UserPreferences + .getPlaybackSpeedArray()); + for (int i = 0; i < speedValues.length; i++) { + speedChecked[i] = selectedSpeedList.contains(speedValues[i]); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.set_playback_speed_label); + builder.setMultiChoiceItems(R.array.playback_speed_values, + speedChecked, (dialog, which, isChecked) -> { + speedChecked[which] = isChecked; + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setPositiveButton(android.R.string.ok, + (dialog, which) -> { + int choiceCount = 0; + for (int i = 0; i < speedChecked.length; i++) { + if (speedChecked[i]) { + choiceCount++; + } + } + String[] newSpeedValues = new String[choiceCount]; + int newSpeedIndex = 0; + for (int i = 0; i < speedChecked.length; i++) { + if (speedChecked[i]) { + newSpeedValues[newSpeedIndex++] = speedValues[i]; + } + } + + UserPreferences.setPlaybackSpeedArray(newSpeedValues); + + }); + builder.create().show(); + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index bbe6fab46..d979dc382 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -10,7 +10,6 @@ import android.widget.Button; import android.widget.EditText; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; @@ -46,7 +45,7 @@ public class AddFeedFragment extends Fragment { Button butSearchITunes = (Button) root.findViewById(R.id.butSearchItunes); final MainActivity activity = (MainActivity) getActivity(); - activity.getMainActivtyActionBar().setTitle(R.string.add_feed_label); + activity.getSupportActionBar().setTitle(R.string.add_feed_label); butSearchITunes.setOnClickListener(new View.OnClickListener() { @Override @@ -73,7 +72,7 @@ public class AddFeedFragment extends Fragment { butConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent intent = new Intent(getActivity(), DefaultOnlineFeedViewActivity.class); + 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); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index ff5485251..273c75240 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -4,36 +4,36 @@ import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; -import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; +import android.support.v7.widget.SimpleItemAnimator; import android.util.Log; -import android.view.ContextMenu; 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.AdapterView; import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; -import com.mobeta.android.dslv.DragSortListView; +import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; -import de.danoeh.antennapod.adapter.AllEpisodesListAdapter; -import de.danoeh.antennapod.core.asynctask.DownloadObserver; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; +import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; @@ -45,9 +45,15 @@ import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Shows unread or recently published episodes @@ -56,90 +62,74 @@ public class AllEpisodesFragment extends Fragment { public static final String TAG = "AllEpisodesFragment"; - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | - EventDistributor.DOWNLOAD_QUEUED | + private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE | EventDistributor.UNREAD_ITEMS_UPDATE | EventDistributor.PLAYER_STATUS_UPDATE; private static final int RECENT_EPISODES_LIMIT = 150; private static final String DEFAULT_PREF_NAME = "PrefAllEpisodesFragment"; - private static final String PREF_KEY_LIST_TOP = "list_top"; - private static final String PREF_KEY_LIST_SELECTION = "list_selection"; + private static final String PREF_SCROLL_POSITION = "scroll_position"; + private static final String PREF_SCROLL_OFFSET = "scroll_offset"; - private String prefName; - protected DragSortListView listView; - private AllEpisodesListAdapter listAdapter; - private TextView txtvEmpty; + protected RecyclerView recyclerView; + private AllEpisodesRecycleAdapter listAdapter; private ProgressBar progLoading; - private ContextMenu contextMenu; private List<FeedItem> episodes; - private LongList queuedItemsIds; - private LongList newItemsIds; private List<Downloader> downloaderList; private boolean itemsLoaded = false; private boolean viewsCreated = false; - private final boolean showOnlyNewEpisodes; private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>(); - private DownloadObserver downloadObserver = null; - private boolean isUpdatingFeeds; - public AllEpisodesFragment() { - // by default we show all the episodes - this(false, DEFAULT_PREF_NAME); - } + protected Subscription subscription; + private LinearLayoutManager layoutManager; - // this is only going to be called by our sub-class. - // The Android docs say to avoid non-default constructors - // but I think this will be OK since it will only be invoked - // from a fragment via a default constructor - protected AllEpisodesFragment(boolean showOnlyNewEpisodes, String prefName) { - this.showOnlyNewEpisodes = showOnlyNewEpisodes; - this.prefName = prefName; - } + protected boolean showOnlyNewEpisodes() { return false; } + protected String getPrefName() { return DEFAULT_PREF_NAME; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setRetainInstance(true); setHasOptionsMenu(true); } @Override - public void onResume() { - super.onResume(); - startItemLoader(); - } - - @Override public void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); this.activity.set((MainActivity) getActivity()); - if (downloadObserver != null) { - downloadObserver.setActivity(getActivity()); - downloadObserver.onResume(); - } if (viewsCreated && itemsLoaded) { onFragmentLoaded(); } } @Override + public void onResume() { + super.onResume(); + EventBus.getDefault().registerSticky(this); + loadItems(); + registerForContextMenu(recyclerView); + } + + @Override public void onPause() { super.onPause(); + EventBus.getDefault().unregister(this); saveScrollPosition(); + unregisterForContextMenu(recyclerView); } @Override public void onStop() { super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); - stopItemLoader(); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override @@ -155,25 +145,32 @@ public class AllEpisodesFragment extends Fragment { } private void saveScrollPosition() { - SharedPreferences prefs = getActivity().getSharedPreferences(prefName, Context.MODE_PRIVATE); + int firstItem = layoutManager.findFirstVisibleItemPosition(); + View firstItemView = layoutManager.findViewByPosition(firstItem); + float topOffset; + if(firstItemView == null) { + topOffset = 0; + } else { + topOffset = firstItemView.getTop(); + } + + SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - View v = listView.getChildAt(0); - int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); - editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition()); - editor.putInt(PREF_KEY_LIST_TOP, top); + editor.putInt(PREF_SCROLL_POSITION, firstItem); + editor.putFloat(PREF_SCROLL_OFFSET, topOffset); editor.commit(); } private void restoreScrollPosition() { - SharedPreferences prefs = getActivity().getSharedPreferences(prefName, Context.MODE_PRIVATE); - int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0); - int top = prefs.getInt(PREF_KEY_LIST_TOP, 0); - if (listSelection > 0 || top > 0) { - listView.setSelectionFromTop(listSelection, top); + SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE); + int position = prefs.getInt(PREF_SCROLL_POSITION, 0); + float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f); + if (position > 0 || offset > 0) { + layoutManager.scrollToPositionWithOffset(position, (int) offset); // restore once, then forget SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(PREF_KEY_LIST_SELECTION, 0); - editor.putInt(PREF_KEY_LIST_TOP, 0); + editor.putInt(PREF_SCROLL_POSITION, 0); + editor.putFloat(PREF_SCROLL_OFFSET, 0.0f); editor.commit(); } } @@ -182,9 +179,6 @@ public class AllEpisodesFragment extends Fragment { listAdapter = null; activity.set(null); viewsCreated = false; - if (downloadObserver != null) { - downloadObserver.onPause(); - } } @@ -197,6 +191,9 @@ public class AllEpisodesFragment extends Fragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if(!isAdded()) { + return; + } super.onCreateOptionsMenu(menu, inflater); if (itemsLoaded) { inflater.inflate(R.menu.new_episodes, menu); @@ -252,7 +249,7 @@ public class AllEpisodesFragment extends Fragment { public void onConfirmButtonPressed( DialogInterface dialog) { dialog.dismiss(); - DBWriter.markAllItemsRead(getActivity()); + DBWriter.markAllItemsRead(); Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show(); } }; @@ -268,41 +265,62 @@ public class AllEpisodesFragment extends Fragment { } @Override + public boolean onContextItemSelected(MenuItem item) { + Log.d(TAG, "onContextItemSelected() called with: " + "item = [" + 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 + } + int pos = listAdapter.getPosition(); + if(pos < 0) { + return false; + } + FeedItem selectedItem = itemAccess.getItem(pos); + + if (selectedItem == null) { + Log.i(TAG, "Selected item at position " + pos + " was null, ignoring selection"); + return super.onContextItemSelected(item); + } + + try { + return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); + return true; + } + } + + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return onCreateViewHelper(inflater, container, savedInstanceState, - R.layout.all_episodes_fragment, R.string.all_episodes_label); + R.layout.all_episodes_fragment); } protected View onCreateViewHelper(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState, - int fragmentResource, - int titleString) { + int fragmentResource) { super.onCreateView(inflater, container, savedInstanceState); - ((MainActivity) getActivity()).getSupportActionBar().setTitle(titleString); View root = inflater.inflate(fragmentResource, container, false); - listView = (DragSortListView) root.findViewById(android.R.id.list); - txtvEmpty = (TextView) root.findViewById(android.R.id.empty); - progLoading = (ProgressBar) root.findViewById(R.id.progLoading); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount()); - if (item != null) { - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId())); - } - - } - }); + recyclerView = (RecyclerView) root.findViewById(android.R.id.list); + 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()); - registerForContextMenu(listView); + progLoading = (ProgressBar) root.findViewById(R.id.progLoading); if (!itemsLoaded) { progLoading.setVisibility(View.VISIBLE); - txtvEmpty.setVisibility(View.GONE); } viewsCreated = true; @@ -314,63 +332,12 @@ public class AllEpisodesFragment extends Fragment { return root; } - private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() { - @Override - public void setItemVisibility(int id, boolean visible) { - if(contextMenu == null) { - return; - } - MenuItem item = contextMenu.findItem(id); - if (item != null) { - item.setVisible(visible); - } - } - }; - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - FeedItem item = itemAccess.getItem(adapterInfo.position); - - MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.allepisodes_context, menu); - - if (item != null) { - menu.setHeaderTitle(item.getTitle()); - } - - contextMenu = menu; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - FeedItem selectedItem = itemAccess.getItem(menuInfo.position); - - if (selectedItem == null) { - Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection"); - return super.onContextItemSelected(item); - } - - try { - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); - return true; - } - } - private void onFragmentLoaded() { if (listAdapter == null) { - listAdapter = new AllEpisodesListAdapter(activity.get(), itemAccess, - new DefaultActionButtonCallback(activity.get()), showOnlyNewEpisodes); - listView.setAdapter(listAdapter); - listView.setEmptyView(txtvEmpty); - downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback); - downloadObserver.onResume(); + MainActivity mainActivity = activity.get(); + listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, + new DefaultActionButtonCallback(mainActivity), showOnlyNewEpisodes()); + recyclerView.setAdapter(listAdapter); } listAdapter.notifyDataSetChanged(); restoreScrollPosition(); @@ -378,28 +345,11 @@ public class AllEpisodesFragment extends Fragment { updateShowOnlyEpisodesListViewState(); } - private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { - @Override - public void onContentChanged() { - if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); - } - } - - @Override - public void onDownloadDataAvailable(List<Downloader> downloaderList) { - AllEpisodesFragment.this.downloaderList = downloaderList; - if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); - } - } - }; - - private AllEpisodesListAdapter.ItemAccess itemAccess = new AllEpisodesListAdapter.ItemAccess() { + protected AllEpisodesRecycleAdapter.ItemAccess itemAccess = new AllEpisodesRecycleAdapter.ItemAccess() { @Override public int getCount() { - if (itemsLoaded) { + if (episodes != null) { return episodes.size(); } return 0; @@ -407,7 +357,7 @@ public class AllEpisodesFragment extends Fragment { @Override public FeedItem getItem(int position) { - if (itemsLoaded) { + if (episodes != null && 0 <= position && position < episodes.size()) { return episodes.get(position); } return null; @@ -428,112 +378,118 @@ public class AllEpisodesFragment extends Fragment { @Override public boolean isInQueue(FeedItem item) { - if (itemsLoaded) { - return queuedItemsIds.contains(item.getId()); - } else { - return false; + if (item != null) { + return item.isTagged(FeedItem.TAG_QUEUE); } + return false; } @Override - public boolean isNew(FeedItem item) { - if (itemsLoaded) { - // should actually never be called in NewEpisodesFragment, but better safe than sorry - return showOnlyNewEpisodes || newItemsIds.contains(item.getId()); - } else { - return false; + public LongList getQueueIds() { + LongList queueIds = new LongList(); + if(episodes == null) { + return queueIds; + } + for(FeedItem item : episodes) { + if(item.isTagged(FeedItem.TAG_QUEUE)) { + queueIds.add(item.getId()); + } } + return queueIds; } - - }; - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EVENTS) != 0) { - startItemLoader(); - if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { - getActivity().supportInvalidateOptionsMenu(); + public LongList getFavoritesIds() { + LongList favoritesIds = new LongList(); + if(episodes == null) { + return favoritesIds; + } + for(FeedItem item : episodes) { + if(item.isTagged(FeedItem.TAG_FAVORITE)) { + favoritesIds.add(item.getId()); } } + return favoritesIds; } }; - private void updateShowOnlyEpisodesListViewState() { - if (showOnlyNewEpisodes) { - listView.setEmptyView(null); - txtvEmpty.setVisibility(View.GONE); - } else { - listView.setEmptyView(txtvEmpty); + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if(episodes == null || listAdapter == null) { + return; } - } - - private ItemLoader itemLoader; - - protected void startItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); - } - itemLoader = new ItemLoader(); - itemLoader.execute(); - } - - protected void stopItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); + for(int i=0, size = event.items.size(); i < size; i++) { + FeedItem item = event.items.get(i); + int pos = FeedItemUtil.indexOfItemWithId(episodes, item.getId()); + if(pos >= 0) { + episodes.remove(pos); + episodes.add(pos, item); + listAdapter.notifyItemChanged(pos); + } } } - private class ItemLoader extends AsyncTask<Void, Void, Object[]> { - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (viewsCreated && !itemsLoaded) { - listView.setVisibility(View.GONE); - txtvEmpty.setVisibility(View.GONE); - progLoading.setVisibility(View.VISIBLE); - } + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + DownloaderUpdate update = event.update; + downloaderList = update.downloaders; + if (isUpdatingFeeds != update.feedIds.length > 0) { + getActivity().supportInvalidateOptionsMenu(); } - - @Override - protected Object[] doInBackground(Void... params) { - Context context = activity.get(); - if (context != null) { - if(showOnlyNewEpisodes) { - return new Object[] { - DBReader.getNewItemsList(context), - DBReader.getQueueIDList(context), - null // see ItemAccess.isNew - }; - } else { - return new Object[]{ - DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT), - DBReader.getQueueIDList(context), - DBReader.getNewItemIds(context) - }; + if(listAdapter != null && update.mediaIds.length > 0) { + for(long mediaId : update.mediaIds) { + int pos = FeedItemUtil.indexOfItemWithMediaId(episodes, mediaId); + if(pos >= 0) { + listAdapter.notifyItemChanged(pos); } - } else { - return null; } } + } + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override - protected void onPostExecute(Object[] lists) { - super.onPostExecute(lists); - listView.setVisibility(View.VISIBLE); - progLoading.setVisibility(View.GONE); - - if (lists != null) { - episodes = (List<FeedItem>) lists[0]; - queuedItemsIds = (LongList) lists[1]; - newItemsIds = (LongList) lists[2]; - itemsLoaded = true; - if (viewsCreated && activity.get() != null) { - onFragmentLoaded(); + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EVENTS) != 0) { + loadItems(); + if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { + getActivity().supportInvalidateOptionsMenu(); } } } + }; + + private void updateShowOnlyEpisodesListViewState() { + } + + protected void loadItems() { + if(subscription != null) { + subscription.unsubscribe(); + } + if (viewsCreated && !itemsLoaded) { + recyclerView.setVisibility(View.GONE); + progLoading.setVisibility(View.VISIBLE); + } + subscription = Observable.fromCallable(() -> loadData()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(data -> { + recyclerView.setVisibility(View.VISIBLE); + progLoading.setVisibility(View.GONE); + if (data != null) { + episodes = data; + itemsLoaded = true; + if (viewsCreated && activity.get() != null) { + onFragmentLoaded(); + } + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } + + protected List<FeedItem> loadData() { + return DBReader.getRecentlyPublishedEpisodes(RECENT_EPISODES_LIMIT); } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java new file mode 100644 index 000000000..ce1d753e8 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -0,0 +1,77 @@ +package de.danoeh.antennapod.fragment; + +import android.os.Bundle; +import android.support.v4.app.ListFragment; +import android.view.View; +import android.widget.ListView; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; +import de.danoeh.antennapod.adapter.ChaptersListAdapter; +import de.danoeh.antennapod.core.feed.Chapter; +import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.core.util.playback.PlaybackController; + + +public class ChaptersFragment extends ListFragment implements AudioplayerContentFragment { + + private Playable media; + private PlaybackController controller; + + private ChaptersListAdapter adapter; + + public static ChaptersFragment newInstance(Playable media, PlaybackController controller) { + ChaptersFragment f = new ChaptersFragment(); + f.media = media; + f.controller = controller; + return f; + } + + @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); + + adapter = new ChaptersListAdapter(getActivity(), 0, pos -> { + Chapter chapter = (Chapter) getListAdapter().getItem(pos); + controller.seekToChapter(chapter); + }); + setListAdapter(adapter); + } + + @Override + public void onResume() { + super.onResume(); + adapter.setMedia(media); + adapter.notifyDataSetChanged(); + if(media == null || media.getChapters() == null) { + setEmptyText(getString(R.string.no_chapters_label)); + } else { + setEmptyText(null); + } + } + + public void onDestroy() { + super.onDestroy(); + adapter = null; + } + + @Override + public void onMediaChanged(Playable media) { + if(this.media == media || adapter == null) { + return; + } + this.media = media; + adapter.setMedia(media); + adapter.notifyDataSetChanged(); + if(media.getChapters() == null) { + setEmptyText(getString(R.string.no_items_label)); + } else { + setEmptyText(null); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index 3ca5b3c89..954c4c9e2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -1,14 +1,12 @@ package de.danoeh.antennapod.fragment; import android.app.Activity; -import android.content.Context; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.ListFragment; +import android.util.Log; import android.view.View; import android.widget.ListView; -import java.util.Collections; import java.util.List; import de.danoeh.antennapod.R; @@ -18,11 +16,18 @@ import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Displays all running downloads and provides a button to delete them */ public class CompletedDownloadsFragment extends ListFragment { + + private static final String TAG = CompletedDownloadsFragment.class.getSimpleName(); + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOADLOG_UPDATE | @@ -34,11 +39,12 @@ public class CompletedDownloadsFragment extends ListFragment { private boolean viewCreated = false; private boolean itemsLoaded = false; + private Subscription subscription; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - startItemLoader(); + loadItems(); } @Override @@ -51,13 +57,17 @@ public class CompletedDownloadsFragment extends ListFragment { public void onStop() { super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); - stopItemLoader(); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override public void onDetach() { super.onDetach(); - stopItemLoader(); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override @@ -65,7 +75,9 @@ public class CompletedDownloadsFragment extends ListFragment { super.onDestroyView(); listAdapter = null; viewCreated = false; - stopItemLoader(); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override @@ -119,7 +131,11 @@ public class CompletedDownloadsFragment extends ListFragment { @Override public FeedItem getItem(int position) { - return (items != null) ? items.get(position) : null; + if (items != null && 0 <= position && position < items.size()) { + return items.get(position); + } else { + return null; + } } @Override @@ -132,56 +148,32 @@ public class CompletedDownloadsFragment extends ListFragment { @Override public void update(EventDistributor eventDistributor, Integer arg) { if ((arg & EVENTS) != 0) { - startItemLoader(); + loadItems(); } } }; - private ItemLoader itemLoader; - - private void startItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); + private void loadItems() { + if(subscription != null) { + subscription.unsubscribe(); } - itemLoader = new ItemLoader(); - itemLoader.execute(); - } - - private void stopItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); + if (!itemsLoaded && viewCreated) { + setListShown(false); } + subscription = Observable.fromCallable(() -> DBReader.getDownloadedItems()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result != null) { + items = result; + itemsLoaded = true; + if (viewCreated && getActivity() != null) { + onFragmentLoaded(); + } + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } - private class ItemLoader extends AsyncTask<Void, Void, List<FeedItem>> { - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (!itemsLoaded && viewCreated) { - setListShown(false); - } - } - - @Override - protected void onPostExecute(List<FeedItem> results) { - super.onPostExecute(results); - if (results != null) { - items = results; - itemsLoaded = true; - if (viewCreated && getActivity() != null) { - onFragmentLoaded(); - } - } - } - - @Override - protected List<FeedItem> doInBackground(Void... params) { - Context context = getActivity(); - if (context != null) { - return DBReader.getDownloadedItems(context); - } - return Collections.emptyList(); - } - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index 3076f8136..931d14924 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -1,7 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; @@ -9,29 +7,30 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; -import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.AudioplayerActivity; import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.util.playback.Playable; /** * Displays the cover and the title of a FeedItem. */ -public class CoverFragment extends Fragment implements - AudioplayerContentFragment { +public class CoverFragment extends Fragment implements AudioplayerContentFragment { + private static final String TAG = "CoverFragment"; private static final String ARG_PLAYABLE = "arg.playable"; private Playable media; + private View root; + private TextView txtvPodcastTitle; + private TextView txtvEpisodeTitle; private ImageView imgvCover; - private boolean viewCreated = false; - public static CoverFragment newInstance(Playable item) { CoverFragment f = new CoverFragment(); if (item != null) { @@ -45,7 +44,6 @@ public class CoverFragment extends Fragment implements @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setRetainInstance(true); Bundle args = getArguments(); if (args != null) { media = args.getParcelable(ARG_PLAYABLE); @@ -57,35 +55,28 @@ public class CoverFragment extends Fragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.cover_fragment, container, false); + root = inflater.inflate(R.layout.cover_fragment, container, false); + txtvPodcastTitle = (TextView) root.findViewById(R.id.txtvPodcastTitle); + txtvEpisodeTitle = (TextView) root.findViewById(R.id.txtvEpisodeTitle); imgvCover = (ImageView) root.findViewById(R.id.imgvCover); - imgvCover.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Activity activity = getActivity(); - if (activity != null && activity instanceof AudioplayerActivity) { - ((AudioplayerActivity)activity).switchToLastFragment(); - } - } - }); - viewCreated = true; return root; } private void loadMediaInfo() { + if(imgvCover == null) { + return; + } if (media != null) { - imgvCover.post(new Runnable() { - - @Override - public void run() { - Context c = getActivity(); - if (c != null) { - Picasso.with(c) - .load(media.getImageUri()) - .into(imgvCover); - } - } - }); + Log.d(TAG, "feed title: " + media.getFeedTitle()); + Log.d(TAG, "episode title: " + media.getEpisodeTitle()); + txtvPodcastTitle.setText(media.getFeedTitle()); + txtvEpisodeTitle.setText(media.getEpisodeTitle()); + Glide.with(this) + .load(media.getImageUri()) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .dontAnimate() + .fitCenter() + .into(imgvCover); } else { Log.w(TAG, "loadMediaInfo was called while media was null"); } @@ -93,12 +84,10 @@ public class CoverFragment extends Fragment implements @Override public void onStart() { - if (BuildConfig.DEBUG) - Log.d(TAG, "On Start"); + Log.d(TAG, "On Start"); super.onStart(); if (media != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading media info"); + Log.d(TAG, "Loading media info"); loadMediaInfo(); } else { Log.w(TAG, "Unable to load media info: media was null"); @@ -106,12 +95,19 @@ public class CoverFragment extends Fragment implements } @Override - public void onDataSetChanged(Playable media) { - this.media = media; - if (viewCreated) { - loadMediaInfo(); - } + public void onDestroy() { + super.onDestroy(); + // prevent memory leaks + root = null; + } + @Override + public void onMediaChanged(Playable media) { + if(this.media == media) { + return; + } + this.media = media; + loadMediaInfo(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index 074a87ea0..b470d379a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -1,11 +1,10 @@ package de.danoeh.antennapod.fragment; -import android.content.Context; import android.content.res.TypedArray; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.support.v4.view.MenuItemCompat; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -20,6 +19,10 @@ import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Shows the download log @@ -34,19 +37,23 @@ public class DownloadLogFragment extends ListFragment { private boolean viewsCreated = false; private boolean itemsLoaded = false; + private Subscription subscription; + @Override public void onStart() { super.onStart(); setHasOptionsMenu(true); EventDistributor.getInstance().register(contentUpdate); - startItemLoader(); + loadItems(); } @Override public void onStop() { super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); - stopItemLoader(); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override @@ -84,7 +91,11 @@ public class DownloadLogFragment extends ListFragment { @Override public DownloadStatus getItem(int position) { - return (downloadLog != null) ? downloadLog.get(position) : null; + if (downloadLog != null && 0 <= position && position < downloadLog.size()) { + return downloadLog.get(position); + } else { + return null; + } } }; @@ -93,29 +104,16 @@ public class DownloadLogFragment extends ListFragment { @Override public void update(EventDistributor eventDistributor, Integer arg) { if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) { - startItemLoader(); + loadItems(); } } }; - private ItemLoader itemLoader; - - private void startItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); - } - itemLoader = new ItemLoader(); - itemLoader.execute(); - } - - private void stopItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); - } - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + 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); @@ -142,7 +140,7 @@ public class DownloadLogFragment extends ListFragment { if (!super.onOptionsItemSelected(item)) { switch (item.getItemId()) { case R.id.clear_history_item: - DBWriter.clearDownloadLog(getActivity()); + DBWriter.clearDownloadLog(); return true; default: return false; @@ -152,27 +150,24 @@ public class DownloadLogFragment extends ListFragment { } } - private class ItemLoader extends AsyncTask<Void, Void, List<DownloadStatus>> { - - @Override - protected void onPostExecute(List<DownloadStatus> downloadStatuses) { - super.onPostExecute(downloadStatuses); - if (downloadStatuses != null) { - downloadLog = downloadStatuses; - itemsLoaded = true; - if (viewsCreated) { - onFragmentLoaded(); - } - } - } - - @Override - protected List<DownloadStatus> doInBackground(Void... params) { - Context context = getActivity(); - if (context != null) { - return DBReader.getDownloadLog(context); - } - return null; + private void loadItems() { + if(subscription != null) { + subscription.unsubscribe(); } + subscription = Observable.fromCallable(() -> DBReader.getDownloadLog()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result != null) { + downloadLog = result; + itemsLoaded = true; + if (viewsCreated) { + onFragmentLoaded(); + } + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java index 1ce379cf8..52a38ccb9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java @@ -1,7 +1,10 @@ package de.danoeh.antennapod.fragment; +import android.content.Context; +import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; +import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; @@ -25,15 +28,24 @@ public class DownloadsFragment extends Fragment { public static final int POS_COMPLETED = 1; public static final int POS_LOG = 2; - private ViewPager pager; + private static final String PREF_LAST_TAB_POSITION = "tab_position"; + + private ViewPager viewPager; + private TabLayout tabLayout; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View root = inflater.inflate(R.layout.pager_fragment, container, false); - pager = (ViewPager) root.findViewById(R.id.pager); + + viewPager = (ViewPager)root.findViewById(R.id.viewpager); DownloadsPagerAdapter pagerAdapter = new DownloadsPagerAdapter(getChildFragmentManager(), getResources()); - pager.setAdapter(pagerAdapter); + viewPager.setAdapter(pagerAdapter); + + // Give the TabLayout the ViewPager + tabLayout = (TabLayout) root.findViewById(R.id.sliding_tabs); + tabLayout.setupWithViewPager(viewPager); + return root; } @@ -42,10 +54,30 @@ public class DownloadsFragment extends Fragment { super.onViewCreated(view, savedInstanceState); if (getArguments() != null) { int tab = getArguments().getInt(ARG_SELECTED_TAB); - pager.setCurrentItem(tab, false); + viewPager.setCurrentItem(tab, false); } } + @Override + public void onPause() { + super.onPause(); + // save our tab selection + SharedPreferences prefs = getActivity().getSharedPreferences(TAG, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PREF_LAST_TAB_POSITION, tabLayout.getSelectedTabPosition()); + editor.apply(); + } + + @Override + public void onStart() { + super.onStart(); + + // restore our last position + SharedPreferences prefs = getActivity().getSharedPreferences(TAG, Context.MODE_PRIVATE); + int lastPosition = prefs.getInt(PREF_LAST_TAB_POSITION, 0); + viewPager.setCurrentItem(lastPosition); + } + public class DownloadsPagerAdapter extends FragmentPagerAdapter { Resources resources; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java new file mode 100644 index 000000000..f23981935 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java @@ -0,0 +1,120 @@ +package de.danoeh.antennapod.fragment; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; + +public class EpisodesFragment extends Fragment { + + public static final String TAG = "EpisodesFragment"; + private static final String PREF_LAST_TAB_POSITION = "tab_position"; + + public static final int POS_NEW_EPISODES = 0; + public static final int POS_ALL_EPISODES = 1; + public static final int POS_FAV_EPISODES = 2; + public static final int TOTAL_COUNT = 3; + + + private TabLayout tabLayout; + private ViewPager viewPager; + + //Mandatory Constructor + public EpisodesFragment() { + } + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + setHasOptionsMenu(true); + ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.episodes_label); + + View rootView = inflater.inflate(R.layout.pager_fragment, container, false); + viewPager = (ViewPager)rootView.findViewById(R.id.viewpager); + viewPager.setAdapter(new EpisodesPagerAdapter(getChildFragmentManager(), getResources())); + + // Give the TabLayout the ViewPager + tabLayout = (TabLayout) rootView.findViewById(R.id.sliding_tabs); + tabLayout.setupWithViewPager(viewPager); + + return rootView; + } + + @Override + public void onPause() { + super.onPause(); + // save our tab selection + SharedPreferences prefs = getActivity().getSharedPreferences(TAG, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PREF_LAST_TAB_POSITION, tabLayout.getSelectedTabPosition()); + editor.apply(); + } + + @Override + public void onStart() { + super.onStart(); + + // restore our last position + SharedPreferences prefs = getActivity().getSharedPreferences(TAG, Context.MODE_PRIVATE); + int lastPosition = prefs.getInt(PREF_LAST_TAB_POSITION, 0); + viewPager.setCurrentItem(lastPosition); + } + + public static class EpisodesPagerAdapter extends FragmentPagerAdapter { + + private final Resources resources; + + public EpisodesPagerAdapter(FragmentManager fm, Resources resources) { + super(fm); + this.resources = resources; + } + + @Override + public Fragment getItem(int position) { + switch (position) { + case POS_ALL_EPISODES: + return new AllEpisodesFragment(); + case POS_NEW_EPISODES: + return new NewEpisodesFragment(); + case POS_FAV_EPISODES: + return new FavoriteEpisodesFragment(); + } + return null; + } + + @Override + public int getCount() { + return TOTAL_COUNT; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case POS_ALL_EPISODES: + return resources.getString(R.string.all_episodes_short_label); + case POS_NEW_EPISODES: + return resources.getString(R.string.new_label); + case POS_FAV_EPISODES: + return resources.getString(R.string.favorite_episodes_label); + default: + return super.getPageTitle(position); + } + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index fdb128f03..80a9bf0b3 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -9,11 +9,13 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.ProgressBar; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.playback.Playable; @@ -24,13 +26,14 @@ import de.danoeh.antennapod.core.util.playback.PlaybackController; * if the PlaybackService is running */ public class ExternalPlayerFragment extends Fragment { - private static final String TAG = "ExternalPlayerFragment"; + public static final String TAG = "ExternalPlayerFragment"; private ViewGroup fragmentLayout; private ImageView imgvCover; - private ViewGroup layoutInfo; private TextView txtvTitle; private ImageButton butPlay; + private TextView mFeedName; + private ProgressBar mProgressBar; private PlaybackController controller; @@ -45,17 +48,18 @@ public class ExternalPlayerFragment extends Fragment { container, false); fragmentLayout = (ViewGroup) root.findViewById(R.id.fragmentLayout); imgvCover = (ImageView) root.findViewById(R.id.imgvCover); - layoutInfo = (ViewGroup) root.findViewById(R.id.layoutInfo); txtvTitle = (TextView) root.findViewById(R.id.txtvTitle); butPlay = (ImageButton) root.findViewById(R.id.butPlay); + mFeedName = (TextView) root.findViewById(R.id.txtvAuthor); + mProgressBar = (ProgressBar) root.findViewById(R.id.episodeProgress); - layoutInfo.setOnClickListener(new OnClickListener() { + fragmentLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d(TAG, "layoutInfo was clicked"); - if (controller.getMedia() != null) { + if (controller != null && controller.getMedia() != null) { startActivity(PlaybackService.getPlayerActivityIntent( getActivity(), controller.getMedia())); } @@ -75,35 +79,8 @@ public class ExternalPlayerFragment extends Fragment { return new PlaybackController(getActivity(), true) { @Override - public void setupGUI() { - } - - @Override public void onPositionObserverUpdate() { - } - - @Override - public void onReloadNotification(int code) { - } - - @Override - public void onBufferStart() { - } - - @Override - public void onBufferEnd() { - } - - @Override - public void onBufferUpdate(float progress) { - } - - @Override - public void onSleepTimerUpdate() { - } - - @Override - public void handleError(int code) { + ExternalPlayerFragment.this.onPositionObserverUpdate(); } @Override @@ -111,13 +88,6 @@ public class ExternalPlayerFragment extends Fragment { return butPlay; } - @Override - public void postStatusMsg(int msg) { - } - - @Override - public void clearStatusMsg() { - } @Override public boolean loadMediaInfo() { @@ -130,39 +100,13 @@ public class ExternalPlayerFragment extends Fragment { } @Override - public void onAwaitingVideoSurface() { - } - - @Override - public void onServiceQueried() { - } - - @Override public void onShutdownNotification() { - if (fragmentLayout != null) { - fragmentLayout.setVisibility(View.GONE); - } - controller = setupPlaybackController(); - if (butPlay != null) { - butPlay.setOnClickListener(controller - .newOnPlayButtonClickListener()); - } + playbackDone(); } @Override public void onPlaybackEnd() { - if (fragmentLayout != null) { - fragmentLayout.setVisibility(View.GONE); - } - controller = setupPlaybackController(); - if (butPlay != null) { - butPlay.setOnClickListener(controller - .newOnPlayButtonClickListener()); - } - } - - @Override - public void onPlaybackSpeedChange() { + playbackDone(); } }; } @@ -171,6 +115,8 @@ public class ExternalPlayerFragment extends Fragment { public void onResume() { super.onResume(); controller.init(); + mProgressBar.setProgress((int) + ((double) controller.getPosition() / controller.getDuration() * 100)); } @Override @@ -190,16 +136,38 @@ public class ExternalPlayerFragment extends Fragment { } } + private void playbackDone() { + if (fragmentLayout != null) { + fragmentLayout.setVisibility(View.GONE); + } + if (controller != null) { + controller.release(); + } + controller = setupPlaybackController(); + if (butPlay != null) { + butPlay.setOnClickListener(controller + .newOnPlayButtonClickListener()); + } + controller.init(); + } + private boolean loadMediaInfo() { Log.d(TAG, "Loading media info"); - if (controller.serviceAvailable()) { + if (controller != null && controller.serviceAvailable()) { Playable media = controller.getMedia(); if (media != null) { txtvTitle.setText(media.getEpisodeTitle()); + mFeedName.setText(media.getFeedTitle()); + mProgressBar.setProgress((int) + ((double) controller.getPosition() / controller.getDuration() * 100)); - Picasso.with(getActivity()) + Glide.with(getActivity()) .load(media.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(imgvCover); fragmentLayout.setVisibility(View.VISIBLE); @@ -223,4 +191,13 @@ public class ExternalPlayerFragment extends Fragment { return Converter.getDurationStringLong(position) + " / " + Converter.getDurationStringLong(duration); } + + public PlaybackController getPlaybackControllerTestingOnly() { + return controller; + } + + public void onPositionObserverUpdate() { + mProgressBar.setProgress((int) + ((double) controller.getPosition() / controller.getDuration() * 100)); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java new file mode 100644 index 000000000..65305df3d --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -0,0 +1,91 @@ +package de.danoeh.antennapod.fragment; + +import android.os.Bundle; +import android.support.design.widget.Snackbar; +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 java.util.List; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; +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; + + +/** + * Like 'EpisodesFragment' except that it only shows favorite episodes and + * supports swiping to remove from favorites. + */ + +public class FavoriteEpisodesFragment extends AllEpisodesFragment { + + public static final String TAG = "FavoriteEpisodesFrag"; + + private static final String PREF_NAME = "PrefFavoriteEpisodesFragment"; + + @Override + protected boolean showOnlyNewEpisodes() { return true; } + + @Override + protected String getPrefName() { return PREF_NAME; } + + public void onEvent(FavoritesEvent event) { + Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); + loadItems(); + } + + @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); + + ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder)viewHolder; + Log.d(TAG, "remove(" + holder.getItemId() + ")"); + + if (subscription != null) { + subscription.unsubscribe(); + } + FeedItem item = holder.getFeedItem(); + if (item != null) { + DBWriter.removeFavoriteItem(item); + + 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(); + } + } + }; + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); + itemTouchHelper.attachToRecyclerView(recyclerView); + return root; + } + + @Override + protected List<FeedItem> loadData() { + return DBReader.getFavoriteItemsList(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index a7c6d62e6..4c723e5ff 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -7,8 +7,9 @@ import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.graphics.Color; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -20,27 +21,33 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.webkit.WebSettings.LayoutAlgorithm; +import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; -import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.AudioplayerActivity; +import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.ShownotesProvider; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Displays the description of a Playable object in a Webview. */ -public class ItemDescriptionFragment extends Fragment { +public class ItemDescriptionFragment extends Fragment implements AudioplayerContentFragment { private static final String TAG = "ItemDescriptionFragment"; @@ -55,12 +62,12 @@ public class ItemDescriptionFragment extends Fragment { private static final String ARG_HIGHLIGHT_TIMECODES = "arg.highlightTimecodes"; private WebView webvDescription; + private String webvData; private ShownotesProvider shownotesProvider; private Playable media; - - private AsyncTask<Void, Void, Void> webViewLoader; + private Subscription webViewLoader; /** * URL that was selected via long-press. @@ -104,20 +111,19 @@ public class ItemDescriptionFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating view"); + Log.d(TAG, "Creating view"); webvDescription = new WebView(getActivity()); - if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { - if (Build.VERSION.SDK_INT >= 11 - && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - webvDescription.setBackgroundColor(getResources().getColor( - R.color.black)); + if (Build.VERSION.SDK_INT >= 11) { + webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } + TypedArray ta = getActivity().getTheme().obtainStyledAttributes(new int[] + {android.R.attr.colorBackground}); + int backgroundColor = ta.getColor(0, UserPreferences.getTheme() == + R.style.Theme_AntennaPod_Dark ? Color.BLACK : Color.WHITE); + ta.recycle(); + webvDescription.setBackgroundColor(backgroundColor); webvDescription.getSettings().setUseWideViewPort(false); - webvDescription.getSettings().setLayoutAlgorithm( - LayoutAlgorithm.NARROW_COLUMNS); + webvDescription.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); webvDescription.getSettings().setLoadWithOverviewMode(true); webvDescription.setOnLongClickListener(webViewLongClickListener); webvDescription.setWebViewClient(new WebViewClient() { @@ -141,17 +147,9 @@ public class ItemDescriptionFragment extends Fragment { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); - if (BuildConfig.DEBUG) - Log.d(TAG, "Page finished"); + Log.d(TAG, "Page finished"); // Restoring the scroll position might not always work - view.postDelayed(new Runnable() { - - @Override - public void run() { - restoreFromPreference(); - } - - }, 50); + view.postDelayed(() -> restoreFromPreference(), 50); } }); @@ -161,29 +159,11 @@ public class ItemDescriptionFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - if (BuildConfig.DEBUG) - Log.d(TAG, "Fragment attached"); - } - - @Override - public void onDetach() { - super.onDetach(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Fragment detached"); - if (webViewLoader != null) { - webViewLoader.cancel(true); - } - } - - @Override public void onDestroy() { super.onDestroy(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Fragment destroyed"); + Log.d(TAG, "Fragment destroyed"); if (webViewLoader != null) { - webViewLoader.cancel(true); + webViewLoader.unsubscribe(); } if (webvDescription != null) { webvDescription.removeAllViews(); @@ -195,12 +175,10 @@ public class ItemDescriptionFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating fragment"); + Log.d(TAG, "Creating fragment"); Bundle args = getArguments(); saveState = args.getBoolean(ARG_SAVE_STATE, false); highlightTimecodes = args.getBoolean(ARG_HIGHLIGHT_TIMECODES, false); - } @Override @@ -210,46 +188,21 @@ public class ItemDescriptionFragment extends Fragment { if (args.containsKey(ARG_PLAYABLE)) { media = args.getParcelable(ARG_PLAYABLE); shownotesProvider = media; - startLoader(); + load(); } else if (args.containsKey(ARG_FEEDITEM_ID)) { - AsyncTask<Void, Void, FeedItem> itemLoadTask = new AsyncTask<Void, Void, FeedItem>() { - - @Override - protected FeedItem doInBackground(Void... voids) { - return DBReader.getFeedItem(getActivity(), getArguments().getLong(ARG_FEEDITEM_ID)); - } - - @Override - protected void onPostExecute(FeedItem feedItem) { - super.onPostExecute(feedItem); - shownotesProvider = feedItem; - startLoader(); - } - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - itemLoadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - itemLoadTask.execute(); - } + long id = getArguments().getLong(ARG_FEEDITEM_ID); + Observable.defer(() -> Observable.just(DBReader.getFeedItem(id))) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(feedItem -> { + shownotesProvider = feedItem; + load(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } - - - } - - @Override - public void onResume() { - super.onResume(); } - @SuppressLint("NewApi") - private void startLoader() { - webViewLoader = createLoader(); - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - webViewLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - webViewLoader.execute(); - } - } private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { @@ -258,11 +211,7 @@ public class ItemDescriptionFragment extends Fragment { WebView.HitTestResult r = webvDescription.getHitTestResult(); if (r != null && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Link of webview was long-pressed. Extra: " - + r.getExtra() - ); + Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); selectedURL = r.getExtra(); webvDescription.showContextMenu(); return true; @@ -281,8 +230,10 @@ public class ItemDescriptionFragment extends Fragment { switch (item.getItemId()) { case R.id.open_in_browser_item: Uri uri = Uri.parse(selectedURL); - getActivity() - .startActivity(new Intent(Intent.ACTION_VIEW, uri)); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(getActivity(), intent)) { + getActivity().startActivity(intent); + } break; case R.id.share_url_item: ShareUtils.shareLink(getActivity(), selectedURL); @@ -331,8 +282,12 @@ public class ItemDescriptionFragment extends Fragment { R.string.go_to_position_label); menu.setHeaderTitle(Converter.getDurationStringLong(Timeline.getTimecodeLinkTime(selectedURL))); } else { - menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, - R.string.open_in_browser_label); + Uri uri = Uri.parse(selectedURL); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(getActivity(), intent)) { + menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, + R.string.open_in_browser_label); + } menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, R.string.copy_url_label); menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, @@ -342,51 +297,31 @@ public class ItemDescriptionFragment extends Fragment { } } - private AsyncTask<Void, Void, Void> createLoader() { - return new AsyncTask<Void, Void, Void>() { - @Override - protected void onCancelled() { - super.onCancelled(); - webViewLoader = null; - } - - String data; - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - // /webvDescription.loadData(url, "text/html", "utf-8"); - webvDescription.loadDataWithBaseURL(null, data, "text/html", - "utf-8", "about:blank"); - if (BuildConfig.DEBUG) + private void load() { + Log.d(TAG, "load()"); + if(webViewLoader != null) { + webViewLoader.unsubscribe(); + } + if(shownotesProvider == null) { + return; + } + webViewLoader = Observable.defer(() -> Observable.just(loadData())) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(data -> { + webvData = data; + webvDescription.loadDataWithBaseURL(null, data, "text/html", + "utf-8", "about:blank"); Log.d(TAG, "Webview loaded"); - webViewLoader = null; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected Void doInBackground(Void... params) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading Webview"); - try { - Activity activity = getActivity(); - if (activity != null) { - Timeline timeline = new Timeline(activity, shownotesProvider); - data = timeline.processShownotes(highlightTimecodes); - } else { - cancel(true); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } - }; + private String loadData() { + Timeline timeline = new Timeline(getActivity(), shownotesProvider); + String data = timeline.processShownotes(highlightTimecodes); + return data; } @Override @@ -397,24 +332,17 @@ public class ItemDescriptionFragment extends Fragment { private void savePreference() { if (saveState) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Saving preferences"); + Log.d(TAG, "Saving preferences"); SharedPreferences prefs = getActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); if (media != null && webvDescription != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Saving scroll position: " - + webvDescription.getScrollY() - ); + Log.d(TAG, "Saving scroll position: " + webvDescription.getScrollY()); editor.putInt(PREF_SCROLL_Y, webvDescription.getScrollY()); editor.putString(PREF_PLAYABLE_ID, media.getIdentifier() .toString()); } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "savePreferences was called while media or webview was null"); + Log.d(TAG, "savePreferences was called while media or webview was null"); editor.putInt(PREF_SCROLL_Y, -1); editor.putString(PREF_PLAYABLE_ID, ""); } @@ -423,9 +351,8 @@ public class ItemDescriptionFragment extends Fragment { } private boolean restoreFromPreference() { - if (saveState) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Restoring from preferences"); + if (!saveState) { + Log.d(TAG, "Restoring from preferences"); Activity activity = getActivity(); if (activity != null) { SharedPreferences prefs = activity.getSharedPreferences( @@ -435,8 +362,7 @@ public class ItemDescriptionFragment extends Fragment { if (scrollY != -1 && media != null && id.equals(media.getIdentifier().toString()) && webvDescription != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Restored scroll Position: " + scrollY); + Log.d(TAG, "Restored scroll Position: " + scrollY); webvDescription.scrollTo(webvDescription.getScrollX(), scrollY); return true; @@ -448,15 +374,22 @@ public class ItemDescriptionFragment extends Fragment { private void onTimecodeLinkSelected(String link) { int time = Timeline.getTimecodeLinkTime(link); - if (getActivity() != null && getActivity() instanceof ItemDescriptionFragmentCallback) { - PlaybackController pc = ((ItemDescriptionFragmentCallback) getActivity()).getPlaybackController(); + if (getActivity() != null && getActivity() instanceof AudioplayerActivity) { + PlaybackController pc = ((AudioplayerActivity) getActivity()).getPlaybackController(); if (pc != null) { pc.seekTo(time); } } } - public interface ItemDescriptionFragmentCallback { - public PlaybackController getPlaybackController(); + @Override + public void onMediaChanged(Playable media) { + if(this.media == media) { + return; + } + this.media = media; + this.shownotesProvider = media; + load(); } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index 51a1e2252..ce80dc827 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -1,50 +1,51 @@ package de.danoeh.antennapod.fragment; import android.annotation.TargetApi; -import android.content.ActivityNotFoundException; +import android.content.ClipData; +import android.content.Context; import android.content.Intent; -import android.content.res.TypedArray; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.support.v4.util.Pair; -import android.support.v7.widget.PopupMenu; -import android.support.v7.widget.Toolbar; import android.text.TextUtils; -import android.text.format.DateUtils; import android.util.Log; +import android.view.ContextMenu; 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.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; -import android.widget.Button; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.joanzapata.iconify.Iconify; +import com.joanzapata.iconify.widget.IconButton; + +import org.apache.commons.lang3.ArrayUtils; import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; -import de.danoeh.antennapod.core.asynctask.DBTaskLoader; -import de.danoeh.antennapod.core.asynctask.DownloadObserver; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; +import de.danoeh.antennapod.core.event.FavoritesEvent; +import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.QueueEvent; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; @@ -53,21 +54,26 @@ import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.LongList; +import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.playback.Timeline; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Displays information about a FeedItem and actions. */ -public class ItemFragment extends Fragment implements LoaderManager.LoaderCallbacks<Pair<FeedItem, LongList>> { +public class ItemFragment extends Fragment { private static final String TAG = "ItemFragment"; - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | - EventDistributor.DOWNLOAD_QUEUED | - EventDistributor.UNREAD_ITEMS_UPDATE; + private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE; private static final String ARG_FEEDITEM = "feeditem"; @@ -89,12 +95,11 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba private long itemID; private FeedItem item; private LongList queue; + private LongList favorites; private String webviewData; - private DownloadObserver downloadObserver; private List<Downloader> downloaderList; private ViewGroup root; - private View header; private WebView webvDescription; private TextView txtvTitle; private TextView txtvDuration; @@ -102,209 +107,172 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba private ImageView imgvCover; private ProgressBar progbarDownload; private ProgressBar progbarLoading; - private Button butAction1; - private Button butAction2; - private ImageButton butMore; - private PopupMenu popupMenu; + private IconButton butAction1; + private IconButton butAction2; + private Menu popupMenu; + + private Subscription subscription; + + /** + * URL that was selected via long-press. + */ + private String selectedURL; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); - setHasOptionsMenu(false); + setHasOptionsMenu(true); itemID = getArguments().getLong(ARG_FEEDITEM, -1); } - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - getLoaderManager().initLoader(0, null, this); - Toolbar toolbar = ((MainActivity) getActivity()).getToolbar(); - toolbar.addView(header); - } - - @Override - public void onStart() { - super.onStart(); - EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().register(this); - if (downloadObserver != null) { - downloadObserver.setActivity(getActivity()); - downloadObserver.onResume(); - } - if (itemsLoaded) { - onFragmentLoaded(); - } - - } - - @Override - public void onStop() { - super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); - EventBus.getDefault().unregister(this); - } - - private void resetViewState() { - if (downloadObserver != null) { - downloadObserver.onPause(); - } - Toolbar toolbar = ((MainActivity) getActivity()).getToolbar(); - toolbar.removeView(header); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - resetViewState(); - if (webvDescription != null && root != null) { - root.removeView(webvDescription); - webvDescription.destroy(); - } - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); - ((MainActivity) getActivity()).getSupportActionBar().setTitle(""); - Toolbar toolbar = ((MainActivity) getActivity()).getToolbar(); View layout = inflater.inflate(R.layout.feeditem_fragment, container, false); - header = inflater.inflate(R.layout.feeditem_fragment_header, toolbar, false); root = (ViewGroup) layout.findViewById(R.id.content_root); - txtvTitle = (TextView) header.findViewById(R.id.txtvTitle); - txtvDuration = (TextView) header.findViewById(R.id.txtvDuration); - txtvPublished = (TextView) header.findViewById(R.id.txtvPublished); + txtvTitle = (TextView) layout.findViewById(R.id.txtvTitle); + txtvDuration = (TextView) layout.findViewById(R.id.txtvDuration); + txtvPublished = (TextView) layout.findViewById(R.id.txtvPublished); if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448 txtvTitle.setEllipsize(TextUtils.TruncateAt.END); } webvDescription = (WebView) layout.findViewById(R.id.webvDescription); if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { if (Build.VERSION.SDK_INT >= 11 - && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } webvDescription.setBackgroundColor(getResources().getColor( - R.color.black)); + R.color.black)); } webvDescription.getSettings().setUseWideViewPort(false); webvDescription.getSettings().setLayoutAlgorithm( - WebSettings.LayoutAlgorithm.NARROW_COLUMNS); + WebSettings.LayoutAlgorithm.NARROW_COLUMNS); webvDescription.getSettings().setLoadWithOverviewMode(true); + webvDescription.setOnLongClickListener(webViewLongClickListener); webvDescription.setWebViewClient(new WebViewClient() { - @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - try { + if(IntentUtils.isCallable(getActivity(), intent)) { startActivity(intent); - } catch (ActivityNotFoundException e) { - e.printStackTrace(); - return true; } return true; } }); + registerForContextMenu(webvDescription); - imgvCover = (ImageView) header.findViewById(R.id.imgvCover); - progbarDownload = (ProgressBar) header.findViewById(R.id.progbarDownload); + imgvCover = (ImageView) layout.findViewById(R.id.imgvCover); + progbarDownload = (ProgressBar) layout.findViewById(R.id.progbarDownload); progbarLoading = (ProgressBar) layout.findViewById(R.id.progbarLoading); - butAction1 = (Button) header.findViewById(R.id.butAction1); - butAction2 = (Button) header.findViewById(R.id.butAction2); - butMore = (ImageButton) header.findViewById(R.id.butMoreActions); - popupMenu = new PopupMenu(getActivity(), butMore); - - butAction1.setOnClickListener(new View.OnClickListener() { - DefaultActionButtonCallback actionButtonCallback = new DefaultActionButtonCallback(getActivity()); - - @Override - - public void onClick(View v) { - if (item == null) { - return; - } - actionButtonCallback.onActionButtonPressed(item); - FeedMedia media = item.getMedia(); - if (media != null && media.isDownloaded()) { - // playback was started, dialog should close itself - ((MainActivity) getActivity()).dismissChildFragment(); - } - } - - - } - ); - - butAction2.setOnClickListener(new View.OnClickListener() - - { - @Override - public void onClick(View v) { - if (item == null) { - return; - } - - if (item.hasMedia()) { - FeedMedia media = item.getMedia(); - if (!media.isDownloaded()) { - DBTasks.playMedia(getActivity(), media, true, true, true); - ((MainActivity) getActivity()).dismissChildFragment(); - } else { - DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId()); - } - } else if (item.getLink() != null) { - Uri uri = Uri.parse(item.getLink()); - getActivity().startActivity(new Intent(Intent.ACTION_VIEW, uri)); - } - } - } - ); - - butMore.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (item == null) { - return; - } - popupMenu.getMenu().clear(); - popupMenu.inflate(R.menu.feeditem_options); - if (item.hasMedia()) { - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue); - } else { - // these are already available via button1 and button2 - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue, - R.id.mark_read_item, R.id.visit_website_item); - } - popupMenu.show(); - } - } - ); - - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - - try { - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); - return true; - } - } - } - ); + butAction1 = (IconButton) layout.findViewById(R.id.butAction1); + butAction2 = (IconButton) layout.findViewById(R.id.butAction2); + + butAction1.setOnClickListener(v -> { + if (item == null) { + return; + } + DefaultActionButtonCallback actionButtonCallback = new DefaultActionButtonCallback(getActivity()); + actionButtonCallback.onActionButtonPressed(item); + FeedMedia media = item.getMedia(); + if (media != null && media.isDownloaded()) { + // playback was started, dialog should close itself + ((MainActivity) getActivity()).dismissChildFragment(); + } + }); + + butAction2.setOnClickListener(v -> { + if (item == null) { + return; + } + + if (item.hasMedia()) { + FeedMedia media = item.getMedia(); + if (!media.isDownloaded()) { + DBTasks.playMedia(getActivity(), media, true, true, true); + ((MainActivity) getActivity()).dismissChildFragment(); + } else { + DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId()); + } + } else if (item.getLink() != null) { + Uri uri = Uri.parse(item.getLink()); + getActivity().startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } + }); return layout; } + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + load(); + } + + @Override + public void onResume() { + super.onResume(); + EventDistributor.getInstance().register(contentUpdate); + EventBus.getDefault().registerSticky(this); + if(itemsLoaded) { + updateAppearance(); + } + } + + @Override + public void onPause() { + super.onPause(); + EventDistributor.getInstance().unregister(contentUpdate); + EventBus.getDefault().unregister(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if(subscription != null) { + subscription.unsubscribe(); + } + if (webvDescription != null && root != null) { + root.removeView(webvDescription); + webvDescription.destroy(); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if(!isAdded() || item == null) { + return; + } + inflater.inflate(R.menu.feeditem_options, menu); + popupMenu = menu; + if (item.hasMedia()) { + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue, favorites); + } else { + // these are already available via button1 and button2 + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue, favorites, + R.id.mark_read_item, R.id.visit_website_item); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + try { + return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); + return true; + } + } + private final FeedItemMenuHandler.MenuInterface popupMenuInterface = new FeedItemMenuHandler.MenuInterface() { @Override public void setItemVisibility(int id, boolean visible) { - MenuItem item = popupMenu.getMenu().findItem(id); + MenuItem item = popupMenu.findItem(id); if (item != null) { item.setVisible(visible); } @@ -313,23 +281,36 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba private void onFragmentLoaded() { - progbarLoading.setVisibility(View.GONE); + progbarLoading.setVisibility(View.INVISIBLE); if (webviewData != null) { webvDescription.loadDataWithBaseURL(null, webviewData, "text/html", "utf-8", "about:blank"); } updateAppearance(); - downloadObserver = new DownloadObserver(getActivity(), new Handler(), downloadObserverCallback); - downloadObserver.onResume(); } private void updateAppearance() { + if (item == null) { + Log.d(TAG, "updateAppearance item is null"); + return; + } + getActivity().supportInvalidateOptionsMenu(); txtvTitle.setText(item.getTitle()); - txtvPublished.setText(DateUtils.formatDateTime(getActivity(), item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL)); - Picasso.with(getActivity()).load(item.getImageUri()) - .fit() + if (item.getPubDate() != null) { + String pubDateStr = DateUtils.formatAbbrev(getActivity(), item.getPubDate()); + txtvPublished.setText(pubDateStr); + } + + Glide.with(getActivity()) + .load(item.getImageUri()) + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(imgvCover); + progbarDownload.setVisibility(View.GONE); if (item.hasMedia() && downloaderList != null) { for (Downloader downloader : downloaderList) { @@ -342,121 +323,223 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba } FeedMedia media = item.getMedia(); + String butAction1Icon = null; + int butAction1Text = 0; + String butAction2Icon = null; + int butAction2Text = 0; if (media == null) { - TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.navigation_accept, - R.attr.location_web_site}); - - if (!item.isRead()) { - butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(0), null, null, null); - butAction1.setText(getActivity().getString(R.string.mark_read_label)); - butAction1.setVisibility(View.VISIBLE); - } else { - butAction1.setVisibility(View.INVISIBLE); + if (!item.isPlayed()) { + butAction1Icon = "{fa-check 24sp}"; + butAction1Text = R.string.mark_read_label; } - if (item.getLink() != null) { - butAction2.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(1), null, null, null); - butAction2.setText(getActivity().getString(R.string.visit_website_label)); - } else { - butAction2.setEnabled(false); + butAction2Icon = "{md-web 24sp}"; + butAction2Text = R.string.visit_website_label; } - - drawables.recycle(); - } else {if(media.getDuration() > 0) { + } else { + if(media.getDuration() > 0) { txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); } - boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); - TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.av_play, - R.attr.av_download, R.attr.action_stream, R.attr.content_discard, R.attr.navigation_cancel}); - if (!media.isDownloaded()) { - butAction2.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(2), null, null, null); - butAction2.setText(getActivity().getString(R.string.stream_label)); + butAction2Icon = "{md-settings-input-antenna 24sp}"; + butAction2Text = R.string.stream_label; } else { - butAction2.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(3), null, null, null); - butAction2.setText(getActivity().getString(R.string.remove_episode_lable)); + butAction2Icon = "{md-delete 24sp}"; + butAction2Text = R.string.remove_label; } - if (isDownloading) { - butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(4), null, null, null); - butAction1.setText(getActivity().getString(R.string.cancel_download_label)); + butAction1Icon = "{md-cancel 24sp}"; + butAction1Text = R.string.cancel_label; } else if (media.isDownloaded()) { - butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(0), null, null, null); - butAction1.setText(getActivity().getString(R.string.play_label)); + butAction1Icon = "{md-play-arrow 24sp}"; + butAction1Text = R.string.play_label; } else { - butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(1), null, null, null); - butAction1.setText(getActivity().getString(R.string.download_label)); + butAction1Icon = "{md-file-download 24sp}"; + butAction1Text = R.string.download_label; } - - drawables.recycle(); + } + if(butAction1Icon != null && butAction1Text != 0) { + butAction1.setText(butAction1Icon +"\u0020\u0020" + getActivity().getString(butAction1Text)); + Iconify.addIcons(butAction1); + butAction1.setVisibility(View.VISIBLE); + } else { + butAction1.setVisibility(View.INVISIBLE); + } + if(butAction2Icon != null && butAction2Text != 0) { + butAction2.setText(butAction2Icon +"\u0020\u0020" + getActivity().getString(butAction2Text)); + Iconify.addIcons(butAction2); + butAction2.setVisibility(View.VISIBLE); + } else { + butAction2.setVisibility(View.INVISIBLE); } } - public void onEvent(QueueEvent event) { - Log.d(TAG, "onEvent(" + event + ")"); - getLoaderManager().restartLoader(0, null, ItemFragment.this); + private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + WebView.HitTestResult r = webvDescription.getHitTestResult(); + if (r != null + && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { + Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); + selectedURL = r.getExtra(); + webvDescription.showContextMenu(); + return true; + } + selectedURL = null; + return false; + } + }; + + @Override + public boolean onContextItemSelected(MenuItem item) { + boolean handled = selectedURL != null; + if (selectedURL != null) { + switch (item.getItemId()) { + case R.id.open_in_browser_item: + Uri uri = Uri.parse(selectedURL); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(getActivity(), intent)) { + getActivity().startActivity(intent); + } + break; + case R.id.share_url_item: + ShareUtils.shareLink(getActivity(), selectedURL); + break; + case R.id.copy_url_item: + if (android.os.Build.VERSION.SDK_INT >= 11) { + ClipData clipData = ClipData.newPlainText(selectedURL, + selectedURL); + android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(clipData); + } else { + android.text.ClipboardManager cm = (android.text.ClipboardManager) getActivity() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(selectedURL); + } + Toast t = Toast.makeText(getActivity(), + R.string.copied_url_msg, Toast.LENGTH_SHORT); + t.show(); + break; + default: + handled = false; + break; + + } + selectedURL = null; + } + return handled; } @Override - public Loader<Pair<FeedItem,LongList>> onCreateLoader(int id, Bundle args) { - return new DBTaskLoader<Pair<FeedItem,LongList>>(getActivity()) { - @Override - public Pair<FeedItem,LongList> loadInBackground() { - FeedItem data1 = DBReader.getFeedItem(getContext(), itemID); - if (data1 != null) { - Timeline t = new Timeline(getActivity(), data1); - webviewData = t.processShownotes(false); + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenu.ContextMenuInfo menuInfo) { + if (selectedURL != null) { + super.onCreateContextMenu(menu, v, menuInfo); + Uri uri = Uri.parse(selectedURL); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(getActivity(), intent)) { + menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, + R.string.open_in_browser_label); } - LongList data2 = DBReader.getQueueIDList(getContext()); - return Pair.create(data1, data2); + menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, + R.string.copy_url_label); + menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, + R.string.share_url_label); + menu.setHeaderTitle(selectedURL); + } + } + + public void onEventMainThread(QueueEvent event) { + if(event.contains(itemID)) { + load(); + } + } + + public void onEventMainThread(FavoritesEvent event) { + if(event.item.getId() == itemID) { + load(); + } + } + + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + for(FeedItem item : event.items) { + if(itemID == item.getId()) { + load(); + return; } - }; + } } - @Override - public void onLoadFinished(Loader<Pair<FeedItem,LongList>> loader, Pair<FeedItem,LongList> data) { - - if (data != null) { - item = data.first; - queue = data.second; - if (!itemsLoaded) { - itemsLoaded = true; - onFragmentLoaded(); - } else { + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + DownloaderUpdate update = event.update; + downloaderList = update.downloaders; + if(item == null || item.getMedia() == null) { + return; + } + long mediaId = item.getMedia().getId(); + if(ArrayUtils.contains(update.mediaIds, mediaId)) { + if (itemsLoaded && getActivity() != null) { updateAppearance(); } } } - @Override - public void onLoaderReset(Loader<Pair<FeedItem,LongList>> loader) { - } private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override public void update(EventDistributor eventDistributor, Integer arg) { if ((arg & EVENTS) != 0) { - getLoaderManager().restartLoader(0, null, ItemFragment.this); + load(); } } }; - private final DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { - - @Override - public void onContentChanged() { - if (itemsLoaded && getActivity() != null) { - updateAppearance(); - } + private void load() { + if(subscription != null) { + subscription.unsubscribe(); } + subscription = Observable.fromCallable(() -> loadInBackground()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + item = (FeedItem) result[0]; + queue = (LongList) result[1]; + favorites = (LongList) result[2]; + if (!itemsLoaded) { + itemsLoaded = true; + onFragmentLoaded(); + } else { + updateAppearance(); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } - @Override - public void onDownloadDataAvailable(List<Downloader> downloaderList) { - ItemFragment.this.downloaderList = downloaderList; - if (itemsLoaded && getActivity() != null) { - updateAppearance(); - } + private Object[] loadInBackground() { + FeedItem feedItem = DBReader.getFeedItem(itemID); + if (feedItem != null) { + Timeline t = new Timeline(getActivity(), feedItem); + webviewData = t.processShownotes(false); } - }; + LongList queue; + if(feedItem.isTagged(FeedItem.TAG_QUEUE)) { + queue = LongList.of(feedItem.getId()); + } else { + queue = new LongList(0); + } + LongList favorites; + if(feedItem.isTagged(FeedItem.TAG_FAVORITE)) { + favorites = LongList.of(feedItem.getId()); + } else { + favorites = new LongList(0); + } + return new Object[] { feedItem, queue, favorites }; + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java index a9cbe8291..7a9b73982 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -4,14 +4,13 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.os.AsyncTask; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.LightingColorFilter; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.support.v4.app.ListFragment; import android.support.v4.view.MenuItemCompat; - -import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.SearchView; import android.util.Log; import android.view.ContextMenu; @@ -21,7 +20,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; -import android.widget.IconTextView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListAdapter; @@ -29,8 +27,11 @@ import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; -import com.joanzapata.android.iconify.Iconify; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.joanzapata.iconify.IconDrawable; +import com.joanzapata.iconify.Iconify; +import com.joanzapata.iconify.fonts.FontAwesomeIcons; +import com.joanzapata.iconify.widget.IconTextView; import org.apache.commons.lang3.Validate; @@ -41,30 +42,41 @@ import de.danoeh.antennapod.activity.FeedInfoActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; import de.danoeh.antennapod.adapter.FeedItemlistAdapter; -import de.danoeh.antennapod.core.asynctask.DownloadObserver; import de.danoeh.antennapod.core.asynctask.FeedRemover; -import de.danoeh.antennapod.core.asynctask.PicassoProvider; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; +import de.danoeh.antennapod.core.event.FavoritesEvent; +import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedItemFilter; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.QueueEvent; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.glide.FastBlurTransformation; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil; +import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Displays a list of FeedItems. @@ -73,9 +85,8 @@ import de.greenrobot.event.EventBus; public class ItemlistFragment extends ListFragment { private static final String TAG = "ItemlistFragment"; - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED - | EventDistributor.DOWNLOAD_QUEUED - | EventDistributor.UNREAD_ITEMS_UPDATE + private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE + | EventDistributor.FEED_LIST_UPDATE | EventDistributor.PLAYER_STATUS_UPDATE; public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem"; @@ -83,17 +94,16 @@ public class ItemlistFragment extends ListFragment { protected FeedItemlistAdapter adapter; private ContextMenu contextMenu; + private AdapterView.AdapterContextMenuInfo lastMenuInfo = null; private long feedID; private Feed feed; private LongList queuedItemsIds; - private LongList newItemsIds; - + private LongList favoritedItemsId; private boolean itemsLoaded = false; private boolean viewsCreated = false; - private DownloadObserver downloadObserver; private List<Downloader> downloaderList; private MoreContentListFooterUtil listFooter; @@ -104,6 +114,8 @@ public class ItemlistFragment extends ListFragment { private TextView txtvInformation; + private Subscription subscription; + /** * Creates new ItemlistFragment which shows the Feeditems of a specific * feed. Sets 'showFeedtitle' to false @@ -133,36 +145,29 @@ public class ItemlistFragment extends ListFragment { @Override public void onStart() { super.onStart(); - EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().register(this); - if (downloadObserver != null) { - downloadObserver.setActivity(getActivity()); - downloadObserver.onResume(); - } if (viewsCreated && itemsLoaded) { onFragmentLoaded(); } } @Override - public void onStop() { - super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); - EventBus.getDefault().unregister(this); - stopItemLoader(); - } - - @Override public void onResume() { super.onResume(); + EventDistributor.getInstance().register(contentUpdate); + EventBus.getDefault().registerSticky(this); + ((MainActivity)getActivity()).getSupportActionBar().setTitle(""); updateProgressBarVisibility(); - startItemLoader(); + loadItems(); } @Override - public void onDetach() { - super.onDetach(); - stopItemLoader(); + public void onPause() { + super.onPause(); + EventDistributor.getInstance().unregister(contentUpdate); + EventBus.getDefault().unregister(this); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override @@ -175,9 +180,6 @@ public class ItemlistFragment extends ListFragment { adapter = null; viewsCreated = false; listFooter = null; - if (downloadObserver != null) { - downloadObserver.onPause(); - } } private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() { @@ -193,6 +195,9 @@ public class ItemlistFragment extends ListFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if(!isAdded()) { + return; + } super.onCreateOptionsMenu(menu, inflater); if (itemsLoaded) { @@ -217,6 +222,18 @@ public class ItemlistFragment extends ListFragment { 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); + } + int[] attrs = { R.attr.action_bar_icon_color }; + TypedArray ta = getActivity().obtainStyledAttributes(UserPreferences.getTheme(), attrs); + int textColor = ta.getColor(0, Color.GRAY); + ta.recycle(); + + menu.findItem(R.id.episode_actions).setIcon(new IconDrawable(getActivity(), + FontAwesomeIcons.fa_gears).color(textColor).actionBarSize()); + isUpdatingFeed = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker); } } @@ -234,13 +251,18 @@ public class ItemlistFragment extends ListFragment { try { if (!FeedMenuHandler.onOptionsItemClicked(getActivity(), item, feed)) { switch (item.getItemId()) { + case R.id.episode_actions: + EpisodesApplyActionFragment fragment = new EpisodesApplyActionFragment(); + fragment.setEpisodes(feed.getItems()); + ((MainActivity)getActivity()).loadChildFragment(fragment); + return true; case R.id.remove_item: final FeedRemover remover = new FeedRemover( getActivity(), feed) { @Override protected void onPostExecute(Void result) { super.onPostExecute(result); - ((MainActivity) getActivity()).loadFragment(NewEpisodesFragment.TAG, null); + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); } }; ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(), @@ -302,12 +324,17 @@ public class ItemlistFragment extends ListFragment { } contextMenu = menu; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds); + lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds, + favoritedItemsId); } @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); + if(menuInfo == null) { + menuInfo = lastMenuInfo; + } // because of addHeaderView(), positions are increased by 1! FeedItem selectedItem = itemAccess.getItem(menuInfo.position-1); @@ -337,7 +364,6 @@ public class ItemlistFragment extends ListFragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(""); registerForContextMenu(getListView()); @@ -349,21 +375,58 @@ public class ItemlistFragment extends ListFragment { @Override public void onListItemClick(ListView l, View v, int position, long id) { + if(adapter == null) { + return; + } FeedItem selection = adapter.getItem(position - l.getHeaderViewsCount()); if (selection != null) { - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(selection.getId())); + MainActivity activity = (MainActivity) getActivity(); + activity.loadChildFragment(ItemFragment.newInstance(selection.getId())); + activity.getSupportActionBar().setTitle(feed.getTitle()); } } public void onEvent(QueueEvent event) { - Log.d(TAG, "onEvent(" + event + ")"); - startItemLoader(); + Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); + loadItems(); + } + + public void onEvent(FavoritesEvent event) { + Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); + loadItems(); } public void onEvent(FeedEvent event) { - Log.d(TAG, "onEvent(" + event + ")"); + Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); if(event.feedId == feedID) { - startItemLoader(); + loadItems(); + } + } + + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + boolean queueChanged = false; + if(feed == null || feed.getItems() == null || adapter == null) { + return; + } + for(FeedItem item : event.items) { + int pos = FeedItemUtil.indexOfItemWithId(feed.getItems(), item.getId()); + if(pos >= 0) { + loadItems(); + return; + } + } + } + + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + DownloaderUpdate update = event.update; + downloaderList = update.downloaders; + if (isUpdatingFeed != event.update.feedIds.length > 0) { + updateProgressBarVisibility(); + } + if(adapter != null && update.mediaIds.length > 0) { + adapter.notifyDataSetChanged(); } } @@ -373,12 +436,8 @@ public class ItemlistFragment extends ListFragment { public void update(EventDistributor eventDistributor, Integer arg) { if ((EVENTS & arg) != 0) { Log.d(TAG, "Received contentUpdate Intent. arg " + arg); - if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) { - updateProgressBarVisibility(); - } else { - startItemLoader(); - updateProgressBarVisibility(); - } + loadItems(); + updateProgressBarVisibility(); } } }; @@ -396,15 +455,16 @@ public class ItemlistFragment extends ListFragment { private boolean insideOnFragmentLoaded = false; private void onFragmentLoaded() { + if(!isVisible()) { + return; + } insideOnFragmentLoaded = true; if (adapter == null) { setListAdapter(null); setupHeaderView(); setupFooterView(); - adapter = new FeedItemlistAdapter(getActivity(), itemAccess, new DefaultActionButtonCallback(getActivity()), false); + adapter = new FeedItemlistAdapter(getActivity(), itemAccess, new DefaultActionButtonCallback(getActivity()), false, true); setListAdapter(adapter); - downloadObserver = new DownloadObserver(getActivity(), new Handler(), downloadObserverCallback); - downloadObserver.onResume(); } refreshHeaderView(); setListShown(true); @@ -421,6 +481,10 @@ public class ItemlistFragment extends ListFragment { } private void refreshHeaderView() { + if (getListView() == null || feed == null) { + Log.e(TAG, "Unable to setup listview: recyclerView = null or feed = null"); + return; + } if(feed.hasLastUpdateFailed()) { txtvFailure.setVisibility(View.VISIBLE); } else { @@ -440,32 +504,13 @@ public class ItemlistFragment extends ListFragment { txtvInformation.setVisibility(View.GONE); } } else { - txtvInformation.setVisibility(View.GONE); } } - - private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { - @Override - public void onContentChanged() { - if (adapter != null) { - adapter.notifyDataSetChanged(); - } - } - - @Override - public void onDownloadDataAvailable(List<Downloader> downloaderList) { - ItemlistFragment.this.downloaderList = downloaderList; - if (adapter != null) { - adapter.notifyDataSetChanged(); - } - } - }; - private void setupHeaderView() { if (getListView() == null || feed == null) { - Log.e(TAG, "Unable to setup listview: listView = null or feed = null"); + Log.e(TAG, "Unable to setup listview: recyclerView = null or feed = null"); return; } ListView lv = getListView(); @@ -485,28 +530,34 @@ public class ItemlistFragment extends ListFragment { txtvTitle.setText(feed.getTitle()); txtvAuthor.setText(feed.getAuthor()); - Picasso.with(getActivity()) + + // https://github.com/bumptech/glide/issues/529 + imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); + + Glide.with(getActivity()) .load(feed.getImageUri()) .placeholder(R.color.image_readability_tint) .error(R.color.image_readability_tint) - .transform(PicassoProvider.blurTransformation) - .resize(PicassoProvider.BLUR_IMAGE_SIZE, PicassoProvider.BLUR_IMAGE_SIZE) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .transform(new FastBlurTransformation(getActivity())) + .dontAnimate() .into(imgvBackground); - Picasso.with(getActivity()) + Glide.with(getActivity()) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() .into(imgvCover); - butShowInfo.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (viewsCreated && itemsLoaded) { - Intent startIntent = new Intent(getActivity(), FeedInfoActivity.class); - startIntent.putExtra(FeedInfoActivity.EXTRA_FEED_ID, - feed.getId()); - startActivity(startIntent); - } + butShowInfo.setOnClickListener(v -> { + if (viewsCreated && itemsLoaded) { + Intent startIntent = new Intent(getActivity(), FeedInfoActivity.class); + startIntent.putExtra(FeedInfoActivity.EXTRA_FEED_ID, + feed.getId()); + startActivity(startIntent); } }); } @@ -514,7 +565,7 @@ public class ItemlistFragment extends ListFragment { private void setupFooterView() { if (getListView() == null || feed == null) { - Log.e(TAG, "Unable to setup listview: listView = null or feed = null"); + Log.e(TAG, "Unable to setup listview: recyclerView = null or feed = null"); return; } if (feed.isPaged() && feed.getNextPageLink() != null) { @@ -524,16 +575,13 @@ public class ItemlistFragment extends ListFragment { View header = inflater.inflate(R.layout.more_content_list_footer, lv, false); lv.addFooterView(header); listFooter = new MoreContentListFooterUtil(header); - listFooter.setClickListener(new MoreContentListFooterUtil.Listener() { - @Override - public void onClick() { - if (feed != null) { - try { - DBTasks.loadNextPageOfFeed(getActivity(), feed, false); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); - } + listFooter.setClickListener(() -> { + if (feed != null) { + try { + DBTasks.loadNextPageOfFeed(getActivity(), feed, false); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); } } }); @@ -544,7 +592,11 @@ public class ItemlistFragment extends ListFragment { @Override public FeedItem getItem(int position) { - return (feed != null) ? feed.getItemAtIndex(position) : null; + if (feed != null && 0 <= position && position < feed.getNumOfItems()) { + return feed.getItemAtIndex(position); + } else { + return null; + } } @Override @@ -558,11 +610,6 @@ public class ItemlistFragment extends ListFragment { } @Override - public boolean isNew(FeedItem item) { - return (newItemsIds != null) && newItemsIds.contains(item.getId()); - } - - @Override public int getItemDownloadProgressPercent(FeedItem item) { if (downloaderList != null) { for (Downloader downloader : downloaderList) { @@ -576,53 +623,38 @@ public class ItemlistFragment extends ListFragment { } }; - private ItemLoader itemLoader; - private void startItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); - } - itemLoader = new ItemLoader(); - itemLoader.execute(feedID); + private void loadItems() { + if(subscription != null) { + subscription.unsubscribe(); + } + subscription = Observable.fromCallable(() -> loadData()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result != null) { + feed = (Feed) result[0]; + queuedItemsIds = (LongList) result[1]; + favoritedItemsId = (LongList) result[2]; + itemsLoaded = true; + if (viewsCreated) { + onFragmentLoaded(); + } + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } - private void stopItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); + private Object[] loadData() { + Feed feed = DBReader.getFeed(feedID); + if(feed != null && feed.getItemFilter() != null) { + FeedItemFilter filter = feed.getItemFilter(); + feed.setItems(filter.filter(feed.getItems())); } + LongList queuedItemsIds = DBReader.getQueueIDList(); + LongList favoritedItemsId = DBReader.getFavoriteIDList(); + return new Object[] { feed, queuedItemsIds, favoritedItemsId }; } - private class ItemLoader extends AsyncTask<Long, Void, Object[]> { - @Override - protected Object[] doInBackground(Long... params) { - long feedID = params[0]; - Context context = getActivity(); - if (context != null) { - Feed feed = DBReader.getFeed(context, feedID); - if(feed.getItemFilter() != null) { - FeedItemFilter filter = feed.getItemFilter(); - feed.setItems(filter.filter(context, feed.getItems())); - } - LongList queuedItemsIds = DBReader.getQueueIDList(context); - LongList newItemsIds = DBReader.getNewItemIds(context); - return new Object[] { feed, queuedItemsIds, newItemsIds }; - } else { - return null; - } - } - - @Override - protected void onPostExecute(Object[] res) { - super.onPostExecute(res); - if (res != null) { - feed = (Feed) res[0]; - queuedItemsIds = (LongList) res[1]; - newItemsIds = res[2] == null ? null : (LongList) res[2]; - itemsLoaded = true; - if (viewsCreated) { - onFragmentLoaded(); - } - } - } - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java index 16789d694..eb947dc2b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java @@ -1,55 +1,75 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; -import android.content.res.Resources; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.view.MenuItemCompat; import android.support.v7.widget.SearchView; +import 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.AdapterView; +import android.widget.Button; import android.widget.GridView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.util.EntityUtils; 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; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.itunes.ItunesAdapter; -import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.ClientConfig; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; -import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.*; +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 { - final String TAG = "ItunesSearchFragment"; - /** - * Search input field - */ - private SearchView searchView; + + 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 */ 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<Podcast> searchResults; + private List<Podcast> topList; + private Subscription subscription; /** * Replace adapter data with provided search results from SearchTask. @@ -58,13 +78,17 @@ public class ItunesSearchFragment extends Fragment { void updateData(List<Podcast> result) { this.searchResults = result; adapter.clear(); - - //ArrayAdapter.addAll() requires minsdk > 10 - for(Podcast p: result) { - adapter.add(p); + if (result != null && result.size() > 0) { + gridView.setVisibility(View.VISIBLE); + txtvEmpty.setVisibility(View.GONE); + for (Podcast p : result) { + adapter.add(p); + } + adapter.notifyDataSetInvalidated(); + } else { + gridView.setVisibility(View.GONE); + txtvEmpty.setVisibility(View.VISIBLE); } - - adapter.notifyDataSetInvalidated(); } /** @@ -77,47 +101,105 @@ public class ItunesSearchFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - adapter = new ItunesAdapter(getActivity(), new ArrayList<Podcast>()); - + setHasOptionsMenu(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_itunes_search, container, false); - GridView gridView = (GridView) view.findViewById(R.id.gridView); + View root = inflater.inflate(R.layout.fragment_itunes_search, container, false); + gridView = (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(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Intent intent = new Intent(getActivity(), - DefaultOnlineFeedViewActivity.class); - - //Tell the OnlineFeedViewActivity where to go - String url = searchResults.get(position).feedUrl; - intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, url); - - intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, "iTunes"); + gridView.setOnItemClickListener((parent, view1, position, id) -> { + Podcast podcast = searchResults.get(position); + 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); + rx.Observable.create((Observable.OnSubscribe<String>) subscriber -> { + 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"); + subscriber.onNext(feedUrl); + } else { + String prefix = getString(R.string.error_msg_prefix); + subscriber.onError(new IOException(prefix + response)); + } + } catch (IOException | JSONException e) { + subscriber.onError(e); + } + subscriber.onCompleted(); + }) + .subscribeOn(Schedulers.newThread()) + .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(); + }); } }); + progressBar = (ProgressBar) root.findViewById(R.id.progressBar); + txtvError = (TextView) root.findViewById(R.id.txtvError); + butRetry = (Button) root.findViewById(R.id.butRetry); + txtvEmpty = (TextView) root.findViewById(android.R.id.empty); + + loadToplist(); - //Configure search input view to be expanded by default with a visible submit button - searchView = (SearchView) view.findViewById(R.id.itunes_search_view); - searchView.setIconifiedByDefault(false); - searchView.setIconified(false); - searchView.setSubmitButtonEnabled(true); - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + return root; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (subscription != null) { + subscription.unsubscribe(); + } + 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_itunes_label)); + sv.setOnQueryTextListener(new android.support.v7.widget.SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { - //This prevents onQueryTextSubmit() from being called twice when keyboard is used - //to submit the query. - searchView.clearFocus(); - new SearchTask(s).execute(); - return false; + sv.clearFocus(); + search(s); + return true; } @Override @@ -125,78 +207,153 @@ public class ItunesSearchFragment extends Fragment { return false; } }); + MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return true; + } - SearchView.SearchAutoComplete textField = (SearchView.SearchAutoComplete) searchView.findViewById(de.danoeh.antennapod.R.id.search_src_text); - if(UserPreferences.getTheme() == de.danoeh.antennapod.R.style.Theme_AntennaPod_Dark) { - textField.setTextColor(Resources.getSystem().getColor(android.R.color.white)); - } else { - textField.setTextColor(Resources.getSystem().getColor(android.R.color.black)); - } - - return view; - } - - /** - * Search the iTunes store for podcasts using the given query - */ - class SearchTask extends AsyncTask<Void,Void,Void> { - /** - * Incomplete iTunes API search URL - */ - final String apiUrl = "https://itunes.apple.com/search?media=podcast&term=%s"; - - /** - * Search terms - */ - final String query; - - /** - * Search result - */ - final List<Podcast> taskData = new ArrayList<>(); - - /** - * Constructor - * - * @param query Search string - */ - public SearchTask(String query){ - this.query = query; - } - - //Get the podcast data - @Override - protected Void doInBackground(Void... params) { - - //Spaces in the query need to be replaced with '+' character. - String formattedUrl = String.format(apiUrl, query).replace(' ', '+'); - - HttpClient client = new DefaultHttpClient(); - HttpGet get = new HttpGet(formattedUrl); - - try { - HttpResponse response = client.execute(get); - String resultString = EntityUtils.toString(response.getEntity()); - 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 = new Podcast(podcastJson); - taskData.add(podcast); + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + if(searchResults != null) { + searchResults = null; + updateData(topList); } - - } catch (IOException | JSONException e) { - e.printStackTrace(); + return true; } - return null; + }); + } + + private void loadToplist() { + if (subscription != null) { + subscription.unsubscribe(); } + gridView.setVisibility(View.GONE); + txtvError.setVisibility(View.GONE); + butRetry.setVisibility(View.GONE); + txtvEmpty.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + subscription = rx.Observable.create((Observable.OnSubscribe<List<Podcast>>) subscriber -> { + String lang = Locale.getDefault().getLanguage(); + String url = "https://itunes.apple.com/" + lang + "/rss/toppodcasts/limit=25/explicit=true/json"; + OkHttpClient client = AntennapodHttpClient.getHttpClient(); + Request.Builder httpReq = new Request.Builder() + .url(url) + .header("User-Agent", ClientConfig.USER_AGENT); + List<Podcast> results = new ArrayList<>(); + try { + Response response = client.newCall(httpReq.build()).execute(); + if(!response.isSuccessful()) { + // toplist for language does not exist, fall back to united states + url = "https://itunes.apple.com/us/rss/toppodcasts/limit=25/explicit=true/json"; + httpReq = new Request.Builder() + .url(url) + .header("User-Agent", ClientConfig.USER_AGENT); + response = client.newCall(httpReq.build()).execute(); + } + if(response.isSuccessful()) { + String resultString = response.body().string(); + JSONObject result = new JSONObject(resultString); + JSONObject feed = result.getJSONObject("feed"); + JSONArray entries = feed.getJSONArray("entry"); + + for(int i=0; i < entries.length(); i++) { + JSONObject json = entries.getJSONObject(i); + Podcast podcast = Podcast.fromToplist(json); + results.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.onNext(results); + subscriber.onCompleted(); + }) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(podcasts -> { + progressBar.setVisibility(View.GONE); + topList = podcasts; + updateData(topList); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressBar.setVisibility(View.GONE); + txtvError.setText(error.toString()); + txtvError.setVisibility(View.VISIBLE); + butRetry.setOnClickListener(v -> loadToplist()); + butRetry.setVisibility(View.VISIBLE); + }); + } - //Save the data and update the list - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - updateData(taskData); + private void search(String query) { + if (subscription != null) { + subscription.unsubscribe(); } + gridView.setVisibility(View.GONE); + txtvError.setVisibility(View.GONE); + butRetry.setVisibility(View.GONE); + txtvEmpty.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + subscription = rx.Observable.create((Observable.OnSubscribe<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 + } + + //Spaces in the query need to be replaced with '+' character. + String formattedUrl = String.format(API_URL, query).replace(' ', '+'); + + 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.onNext(podcasts); + subscriber.onCompleted(); + }) + .subscribeOn(Schedulers.newThread()) + .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); + }); } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index 4bce3c7ba..b996e1cb3 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -1,24 +1,25 @@ package de.danoeh.antennapod.fragment; -import android.content.Context; import android.os.Bundle; +import android.os.Handler; +import android.support.design.widget.Snackbar; +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 com.mobeta.android.dslv.DragSortListView; +import java.util.List; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; +import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.QueueEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken; -import de.danoeh.antennapod.core.util.gui.UndoBarController; -import de.greenrobot.event.EventBus; /** @@ -32,78 +33,101 @@ public class NewEpisodesFragment extends AllEpisodesFragment { private static final String PREF_NAME = "PrefNewEpisodesFragment"; - private UndoBarController undoBarController; - - public NewEpisodesFragment() { - super(true, PREF_NAME); - } - - public void onEvent(QueueEvent event) { - Log.d(TAG, "onEvent(" + event + ")"); - startItemLoader(); - } - @Override - public void onStart() { - super.onStart(); - EventBus.getDefault().register(this); - } + protected boolean showOnlyNewEpisodes() { return true; } @Override - public void onStop() { - super.onStop(); - EventBus.getDefault().unregister(this); + protected String getPrefName() { return PREF_NAME; } + + public void onEvent(QueueEvent event) { + Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); + loadItems(); } @Override protected void resetViewState() { super.resetViewState(); - undoBarController = null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = super.onCreateViewHelper(inflater, container, savedInstanceState, - R.layout.new_episodes_fragment, R.string.new_episodes_label); + R.layout.all_episodes_fragment); - listView.setRemoveListener(new DragSortListView.RemoveListener() { + ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override - public void remove(int which) { - Log.d(TAG, "remove(" + which + ")"); - stopItemLoader(); - FeedItem item = (FeedItem) listView.getAdapter().getItem(which); - DBWriter.markItemRead(getActivity(), item.getId(), true); - undoBarController.showUndoBar(false, - getString(R.string.marked_as_read_label), new FeedItemUndoToken(item, - which) - ); + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; } - }); - undoBarController = new UndoBarController<FeedItemUndoToken>(root.findViewById(R.id.undobar), new UndoBarController.UndoListener<FeedItemUndoToken>() { + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder)viewHolder; + + Log.d(TAG, "remove(" + holder.getItemId() + ")"); + if (subscription != null) { + subscription.unsubscribe(); + } + FeedItem item = holder.getFeedItem(); + // we're marking it as unplayed since the user didn't actually play it + // but they don't want it considered 'NEW' anymore + DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId()); - private final Context context = getActivity(); + final Handler h = new Handler(getActivity().getMainLooper()); + final Runnable r = () -> { + FeedMedia media = item.getMedia(); + if (media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) { + DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId()); + } + }; + + Snackbar snackbar = Snackbar.make(root, getString(R.string.marked_as_read_label), + Snackbar.LENGTH_LONG); + snackbar.setAction(getString(R.string.undo), v -> { + DBWriter.markItemPlayed(FeedItem.NEW, item.getId()); + // don't forget to cancel the thing that's going to remove the media + h.removeCallbacks(r); + }); + snackbar.show(); + h.postDelayed(r, (int)Math.ceil(snackbar.getDuration() * 1.05f)); + } @Override - public void onUndo(FeedItemUndoToken token) { - if (token != null) { - long itemId = token.getFeedItemId(); - DBWriter.markItemRead(context, itemId, false); + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, + int actionState) { + // We only want the active item + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + if (viewHolder instanceof AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) { + AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder itemViewHolder = + (AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } } + + super.onSelectedChanged(viewHolder, actionState); } @Override - public void onHide(FeedItemUndoToken token) { - if (token != null && context != null) { - long itemId = token.getFeedItemId(); - FeedItem item = DBReader.getFeedItem(context, itemId); - FeedMedia media = item.getMedia(); - if(media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) { - DBWriter.deleteFeedMediaOfItem(context, media.getId()); - } + public void clearView(RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + if (viewHolder instanceof AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) { + AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder itemViewHolder = + (AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); } } - }); + }; + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); + itemTouchHelper.attachToRecyclerView(recyclerView); + return root; } + @Override + protected List<FeedItem> loadData() { + return DBReader.getNewItemsList(); + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index 9099829d8..c5b77fae2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -1,11 +1,8 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; -import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; import android.support.v4.app.ListFragment; import android.support.v4.util.Pair; import android.support.v4.view.MenuItemCompat; @@ -17,22 +14,26 @@ import android.view.View; import android.widget.ListView; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; import de.danoeh.antennapod.adapter.FeedItemlistAdapter; -import de.danoeh.antennapod.core.asynctask.DownloadObserver; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; +import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.QueueEvent; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.LongList; import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; public class PlaybackHistoryFragment extends ListFragment { @@ -48,11 +49,18 @@ public class PlaybackHistoryFragment extends ListFragment { private boolean itemsLoaded = false; private boolean viewsCreated = false; - private AtomicReference<Activity> activity = new AtomicReference<Activity>(); - - private DownloadObserver downloadObserver; private List<Downloader> downloaderList; + private Subscription subscription; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (viewsCreated && itemsLoaded) { + onFragmentLoaded(); + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -61,43 +69,55 @@ public class PlaybackHistoryFragment extends ListFragment { } @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); + + viewsCreated = true; + if (itemsLoaded) { + onFragmentLoaded(); + } + } + + + @Override public void onResume() { super.onResume(); - startItemLoader(); + EventBus.getDefault().registerSticky(this); + loadItems(); } @Override public void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().register(this); + } + + @Override + public void onPause() { + super.onPause(); + EventBus.getDefault().unregister(this); } @Override public void onStop() { super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); - EventBus.getDefault().unregister(this); - stopItemLoader(); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override public void onDetach() { super.onDetach(); - stopItemLoader(); - activity.set(null); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.activity.set(activity); - if (downloadObserver != null) { - downloadObserver.setActivity(activity); - downloadObserver.onResume(); - } - if (viewsCreated && itemsLoaded) { - onFragmentLoaded(); + if(subscription != null) { + subscription.unsubscribe(); } } @@ -106,24 +126,14 @@ public class PlaybackHistoryFragment extends ListFragment { super.onDestroyView(); adapter = null; viewsCreated = false; - if (downloadObserver != null) { - downloadObserver.onPause(); - } } - @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); - - viewsCreated = true; - if (itemsLoaded) { - onFragmentLoaded(); + 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(); } } @@ -138,6 +148,9 @@ public class PlaybackHistoryFragment extends ListFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + 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); @@ -164,7 +177,7 @@ public class PlaybackHistoryFragment extends ListFragment { if (!super.onOptionsItemSelected(item)) { switch (item.getItemId()) { case R.id.clear_history_item: - DBWriter.clearPlaybackHistory(getActivity()); + DBWriter.clearPlaybackHistory(); return true; default: return false; @@ -176,7 +189,7 @@ public class PlaybackHistoryFragment extends ListFragment { public void onEvent(QueueEvent event) { Log.d(TAG, "onEvent(" + event + ")"); - startItemLoader(); + loadItems(); } private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @@ -184,7 +197,7 @@ public class PlaybackHistoryFragment extends ListFragment { @Override public void update(EventDistributor eventDistributor, Integer arg) { if ((arg & EVENTS) != 0) { - startItemLoader(); + loadItems(); getActivity().supportInvalidateOptionsMenu(); } } @@ -192,33 +205,18 @@ public class PlaybackHistoryFragment extends ListFragment { private void onFragmentLoaded() { if (adapter == null) { - adapter = new FeedItemlistAdapter(getActivity(), itemAccess, new DefaultActionButtonCallback(activity.get()), true); + // 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, + new DefaultActionButtonCallback(getActivity()), true, false); setListAdapter(adapter); - downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback); - downloadObserver.onResume(); } setListShown(true); adapter.notifyDataSetChanged(); getActivity().supportInvalidateOptionsMenu(); } - private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { - @Override - public void onContentChanged() { - if (adapter != null) { - adapter.notifyDataSetChanged(); - } - } - - @Override - public void onDownloadDataAvailable(List<Downloader> downloaderList) { - PlaybackHistoryFragment.this.downloaderList = downloaderList; - if (adapter != null) { - adapter.notifyDataSetChanged(); - } - } - }; - private FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { @Override public boolean isInQueue(FeedItem item) { @@ -226,11 +224,6 @@ public class PlaybackHistoryFragment extends ListFragment { } @Override - public boolean isNew(FeedItem item) { - return false; - } - - @Override public int getItemDownloadProgressPercent(FeedItem item) { if (downloaderList != null) { for (Downloader downloader : downloaderList) { @@ -250,52 +243,40 @@ public class PlaybackHistoryFragment extends ListFragment { @Override public FeedItem getItem(int position) { - return (playbackHistory != null) ? playbackHistory.get(position) : null; + if (playbackHistory != null && 0 <= position && position < playbackHistory.size()) { + return playbackHistory.get(position); + } else { + return null; + } } }; - private ItemLoader itemLoader; - - private void startItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); + private void loadItems() { + if(subscription != null) { + subscription.unsubscribe(); } - itemLoader = new ItemLoader(); - itemLoader.execute(); + subscription = Observable.fromCallable(() -> loadData()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result != null) { + playbackHistory = result.first; + queue = result.second; + itemsLoaded = true; + if (viewsCreated) { + onFragmentLoaded(); + } + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } - private void stopItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); - } + private Pair<List<FeedItem>, LongList> loadData() { + List<FeedItem> history = DBReader.getPlaybackHistory(); + LongList queue = DBReader.getQueueIDList(); + DBReader.loadAdditionalFeedItemListData(history); + return Pair.create(history, queue); } - private class ItemLoader extends AsyncTask<Void, Void, Pair<List<FeedItem>,LongList>> { - - @Override - protected Pair<List<FeedItem>,LongList> doInBackground(Void... params) { - Context context = activity.get(); - if (context != null) { - List<FeedItem> history = DBReader.getPlaybackHistory(context); - LongList queue = DBReader.getQueueIDList(context); - DBReader.loadFeedDataOfFeedItemlist(context, history); - return Pair.create(history, queue); - } else { - return null; - } - } - - @Override - protected void onPostExecute(Pair<List<FeedItem>,LongList> res) { - super.onPostExecute(res); - if (res != null) { - playbackHistory = res.first; - queue = res.second; - itemsLoaded = true; - if (viewsCreated) { - onFragmentLoaded(); - } - } - } - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index d82c7b8f7..b3f6c3534 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -1,44 +1,45 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; -import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; +import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; +import android.support.v7.widget.SimpleItemAnimator; +import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; -import android.view.ContextMenu; 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.AdapterView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.mobeta.android.dslv.DragSortListView; +import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; -import de.danoeh.antennapod.adapter.QueueListAdapter; -import de.danoeh.antennapod.core.asynctask.DownloadObserver; +import de.danoeh.antennapod.adapter.QueueRecyclerAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; +import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.QueueEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; @@ -47,13 +48,17 @@ import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.QueueSorter; -import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken; -import de.danoeh.antennapod.core.util.gui.UndoBarController; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Shows all items in the queue @@ -63,37 +68,27 @@ public class QueueFragment extends Fragment { public static final String TAG = "QueueFragment"; private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | - EventDistributor.DOWNLOAD_QUEUED | + EventDistributor.UNREAD_ITEMS_UPDATE | // sent when playback position is reset EventDistributor.PLAYER_STATUS_UPDATE; - private DragSortListView listView; - private QueueListAdapter listAdapter; + private TextView infoBar; + private RecyclerView recyclerView; + private QueueRecyclerAdapter recyclerAdapter; private TextView txtvEmpty; private ProgressBar progLoading; - private ContextMenu contextMenu; - - private UndoBarController<FeedItemUndoToken> undoBarController; - private List<FeedItem> queue; private List<Downloader> downloaderList; - private boolean itemsLoaded = false; - private boolean viewsCreated = false; private boolean isUpdatingFeeds = false; private static final String PREFS = "QueueFragment"; - private static final String PREF_KEY_LIST_TOP = "list_top"; - private static final String PREF_KEY_LIST_SELECTION = "list_selection"; - - private AtomicReference<Activity> activity = new AtomicReference<Activity>(); + private static final String PREF_SCROLL_POSITION = "scroll_position"; + private static final String PREF_SCROLL_OFFSET = "scroll_offset"; - private DownloadObserver downloadObserver = null; - - /** - * Download observer updates won't result in an upate of the list adapter if this is true. - */ - private boolean blockDownloadObserverUpdate = false; + private Subscription subscription; + private LinearLayoutManager layoutManager; + private ItemTouchHelper itemTouchHelper; @Override @@ -104,92 +99,129 @@ public class QueueFragment extends Fragment { } @Override - public void onResume() { - super.onResume(); - startItemLoader(); + public void onStart() { + super.onStart(); + if (queue != null) { + onFragmentLoaded(true); + } } @Override - public void onStart() { - super.onStart(); + public void onResume() { + super.onResume(); + recyclerView.setAdapter(recyclerAdapter); + loadItems(true); EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().register(this); - this.activity.set((MainActivity) getActivity()); - if (downloadObserver != null) { - downloadObserver.setActivity(getActivity()); - downloadObserver.onResume(); - } - if (viewsCreated && itemsLoaded) { - onFragmentLoaded(); - } + EventBus.getDefault().registerSticky(this); } @Override public void onPause() { super.onPause(); saveScrollPosition(); - } - - @Override - public void onStop() { - super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); EventBus.getDefault().unregister(this); - stopItemLoader(); - if(undoBarController.isShowing()) { - undoBarController.close(); + if(subscription != null) { + subscription.unsubscribe(); } } - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.activity.set((MainActivity) activity); + public void onEventMainThread(QueueEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if(queue == null || recyclerAdapter == null) { + return; + } + switch(event.action) { + case ADDED: + queue.add(event.position, event.item); + recyclerAdapter.notifyItemInserted(event.position); + break; + case SET_QUEUE: + queue = event.items; + recyclerAdapter.notifyDataSetChanged(); + break; + case REMOVED: + case IRREVERSIBLE_REMOVED: + int position = FeedItemUtil.indexOfItemWithId(queue, event.item.getId()); + queue.remove(position); + recyclerAdapter.notifyItemRemoved(position); + break; + case CLEARED: + queue.clear(); + recyclerAdapter.notifyDataSetChanged(); + break; + case SORTED: + queue = event.items; + recyclerAdapter.notifyDataSetChanged(); + break; + case MOVED: + return; + } + saveScrollPosition(); + onFragmentLoaded(false); } - public void onEventMainThread(QueueEvent event) { - Log.d(TAG, "onEvent(" + event + ")"); - if(event.action == QueueEvent.Action.REMOVED) { - undoBarController.showUndoBar(false, getString(R.string.removed_from_queue), - new FeedItemUndoToken(event.item, event.position)); + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if(queue == null || recyclerAdapter == null) { + return; + } + for(int i=0, size = event.items.size(); i < size; i++) { + FeedItem item = event.items.get(i); + int pos = FeedItemUtil.indexOfItemWithId(queue, item.getId()); + if(pos >= 0) { + queue.remove(pos); + queue.add(pos, item); + recyclerAdapter.notifyItemChanged(pos); + } + } + } + + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + DownloaderUpdate update = event.update; + downloaderList = update.downloaders; + if (isUpdatingFeeds != update.feedIds.length > 0) { + getActivity().supportInvalidateOptionsMenu(); + } + if (recyclerAdapter != null && update.mediaIds.length > 0) { + for (long mediaId : update.mediaIds) { + int pos = FeedItemUtil.indexOfItemWithMediaId(queue, mediaId); + if (pos >= 0) { + recyclerAdapter.notifyItemChanged(pos); + } + } } - startItemLoader(); } private void saveScrollPosition() { + int firstItem = layoutManager.findFirstVisibleItemPosition(); + View firstItemView = layoutManager.findViewByPosition(firstItem); + float topOffset; + if(firstItemView == null) { + topOffset = 0; + } else { + topOffset = firstItemView.getTop(); + } + SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - View v = listView.getChildAt(0); - int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); - editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition()); - editor.putInt(PREF_KEY_LIST_TOP, top); + editor.putInt(PREF_SCROLL_POSITION, firstItem); + editor.putFloat(PREF_SCROLL_OFFSET, topOffset); editor.commit(); } private void restoreScrollPosition() { SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); - int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0); - int top = prefs.getInt(PREF_KEY_LIST_TOP, 0); - if(listSelection > 0 || top > 0) { - listView.setSelectionFromTop(listSelection, top); - // restore once, then forget - SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(PREF_KEY_LIST_SELECTION, 0); - editor.putInt(PREF_KEY_LIST_TOP, 0); - editor.commit(); + int position = prefs.getInt(PREF_SCROLL_POSITION, 0); + float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f); + if (position > 0 || offset > 0) { + layoutManager.scrollToPositionWithOffset(position, (int) offset); } } private void resetViewState() { - unregisterForContextMenu(listView); - listAdapter = null; - activity.set(null); - undoBarController = null; - viewsCreated = false; - blockDownloadObserverUpdate = false; - if (downloadObserver != null) { - downloadObserver.onPause(); - } + recyclerAdapter = null; } @Override @@ -198,17 +230,17 @@ public class QueueFragment extends Fragment { resetViewState(); } - private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() { - @Override - public boolean isRefreshing() { - return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); - } + private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = () -> { + return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); }; @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if(!isAdded()) { + return; + } super.onCreateOptionsMenu(menu, inflater); - if (itemsLoaded) { + if (queue != null) { inflater.inflate(R.menu.queue, menu); MenuItem searchItem = menu.findItem(R.id.action_search); @@ -240,15 +272,17 @@ public class QueueFragment extends Fragment { if (!super.onOptionsItemSelected(item)) { switch (item.getItemId()) { case R.id.queue_lock: - boolean locked = !UserPreferences.isQueueLocked(); - if(locked) { - listView.setDragEnabled(false); + boolean newLockState = !UserPreferences.isQueueLocked(); + UserPreferences.setQueueLocked(newLockState); + getActivity().supportInvalidateOptionsMenu(); + recyclerAdapter.setLocked(newLockState); + if (newLockState) { + Snackbar.make(getActivity().findViewById(R.id.content), R.string + .queue_locked, Snackbar.LENGTH_SHORT).show(); } else { - listView.setDragEnabled(true); + Snackbar.make(getActivity().findViewById(R.id.content), R.string + .queue_unlocked, Snackbar.LENGTH_SHORT).show(); } - UserPreferences.setQueueLocked(locked); - getActivity().supportInvalidateOptionsMenu(); - listAdapter.setLocked(locked); return true; case R.id.refresh_item: List<Feed> feeds = ((MainActivity) getActivity()).getFeeds(); @@ -266,7 +300,7 @@ public class QueueFragment extends Fragment { public void onConfirmButtonPressed( DialogInterface dialog) { dialog.dismiss(); - DBWriter.clearQueue(getActivity()); + DBWriter.clearQueue(); } }; conDialog.createNewDialog().show(); @@ -295,59 +329,42 @@ public class QueueFragment extends Fragment { } else { return true; } - } - private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() { - @Override - public void setItemVisibility(int id, boolean visible) { - if(contextMenu == null) { - return; - } - MenuItem item = contextMenu.findItem(id); - if (item != null) { - item.setVisible(visible); - } - } - }; - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - FeedItem item = itemAccess.getItem(adapterInfo.position); - - MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.queue_context, menu); - - if (item != null) { - menu.setHeaderTitle(item.getTitle()); - } - - contextMenu = menu; - LongList queueIds = new LongList(queue.size()); - for(FeedItem queueItem : queue) { - queueIds.add(queueItem.getId()); - } - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queueIds); - } @Override public boolean onContextItemSelected(MenuItem item) { - AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - FeedItem selectedItem = itemAccess.getItem(menuInfo.position); - + Log.d(TAG, "onContextItemSelected() called with: " + "item = [" + item + "]"); + if(!isVisible()) { + return false; + } + FeedItem selectedItem = recyclerAdapter.getSelectedItem(); if (selectedItem == null) { - Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection"); + Log.i(TAG, "Selected item was null, ignoring selection"); return super.onContextItemSelected(item); } - try { - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); - return true; + switch(item.getItemId()) { + case R.id.move_to_top_item: + int position = FeedItemUtil.indexOfItemWithId(queue, selectedItem.getId()); + queue.add(0, queue.remove(position)); + recyclerAdapter.notifyItemMoved(position, 0); + DBWriter.moveQueueItemToTop(selectedItem.getId(), true); + return true; + case R.id.move_to_bottom_item: + position = FeedItemUtil.indexOfItemWithId(queue, selectedItem.getId()); + queue.add(queue.size()-1, queue.remove(position)); + recyclerAdapter.notifyItemMoved(position, queue.size()-1); + DBWriter.moveQueueItemToBottom(selectedItem.getId(), true); + return true; + default: + try { + return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); + return true; + } } } @@ -358,140 +375,153 @@ public class QueueFragment extends Fragment { ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.queue_label); View root = inflater.inflate(R.layout.queue_fragment, container, false); - listView = (DragSortListView) root.findViewById(android.R.id.list); - txtvEmpty = (TextView) root.findViewById(android.R.id.empty); - progLoading = (ProgressBar) root.findViewById(R.id.progLoading); - listView.setEmptyView(txtvEmpty); - - if(UserPreferences.isQueueLocked()) { - listView.setDragEnabled(false); - } else { - listView.setDragEnabled(true); + infoBar = (TextView) root.findViewById(R.id.info_bar); + recyclerView = (RecyclerView) root.findViewById(R.id.recyclerView); + RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator(); + if (animator instanceof SimpleItemAnimator) { + ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); } + layoutManager = new LinearLayoutManager(getActivity()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); + recyclerView.setHasFixedSize(true); + registerForContextMenu(recyclerView); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount()); - if (item != null) { - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId())); - } - } - }); + itemTouchHelper = new ItemTouchHelper( + new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT) { - listView.setDragSortListener(new DragSortListView.DragSortListener() { - @Override - public void drag(int from, int to) { - Log.d(TAG, "drag"); - blockDownloadObserverUpdate = true; - } + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + int from = viewHolder.getAdapterPosition(); + int to = target.getAdapterPosition(); + Log.d(TAG, "move(" + from + ", " + to + ")"); + queue.add(to, queue.remove(from)); + recyclerAdapter.notifyItemMoved(from, to); + DBWriter.moveQueueItem(from, to, true); + return true; + } - @Override - public void drop(int from, int to) { - Log.d(TAG, "drop"); - blockDownloadObserverUpdate = false; - stopItemLoader(); - final FeedItem item = queue.remove(from); - queue.add(to, item); - listAdapter.notifyDataSetChanged(); - DBWriter.moveQueueItem(getActivity(), from, to, true); - } + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + if(subscription != null) { + subscription.unsubscribe(); + } + final int position = viewHolder.getAdapterPosition(); + Log.d(TAG, "remove(" + position + ")"); + final FeedItem item = queue.get(position); + final boolean isRead = item.isPlayed(); + DBWriter.markItemPlayed(FeedItem.PLAYED, item.getId()); + DBWriter.removeQueueItem(getActivity(), item, true); + Snackbar snackbar = Snackbar.make(root, getString(R.string.marked_as_read_label), Snackbar.LENGTH_LONG); + snackbar.setAction(getString(R.string.undo), v -> { + DBWriter.addQueueItemAt(getActivity(), item.getId(), position, false); + if(false == isRead) { + DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId()); + } + }); + snackbar.show(); + } - @Override - public void remove(int which) { - Log.d(TAG, "remove(" + which + ")"); - stopItemLoader(); - FeedItem item = (FeedItem) listView.getAdapter().getItem(which); - DBWriter.removeQueueItem(getActivity(), item, true); - } - }); + @Override + public boolean isLongPressDragEnabled() { + return false == UserPreferences.isQueueLocked(); + } - undoBarController = new UndoBarController<FeedItemUndoToken>(root.findViewById(R.id.undobar), - new UndoBarController.UndoListener<FeedItemUndoToken>() { + @Override + public boolean isItemViewSwipeEnabled() { + return false == UserPreferences.isQueueLocked(); + } - private final Context context = getActivity(); + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, + int actionState) { + // We only want the active item + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) { + QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder = + (QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } + } - @Override - public void onUndo(FeedItemUndoToken token) { - if (token != null) { - long itemId = token.getFeedItemId(); - int position = token.getPosition(); - DBWriter.addQueueItemAt(context, itemId, position, false); + super.onSelectedChanged(viewHolder, actionState); } - } - - @Override - public void onHide(FeedItemUndoToken token) { - if (token != null && context != null) { - long itemId = token.getFeedItemId(); - FeedItem item = DBReader.getFeedItem(context, itemId); - FeedMedia media = item.getMedia(); - if(media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) { - DBWriter.deleteFeedMediaOfItem(context, media.getId()); + @Override + public void clearView(RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) { + QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder = + (QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); } } } + ); + itemTouchHelper.attachToRecyclerView(recyclerView); - }); - - registerForContextMenu(listView); - - if (!itemsLoaded) { - progLoading.setVisibility(View.VISIBLE); - txtvEmpty.setVisibility(View.GONE); - } - - viewsCreated = true; - - if (itemsLoaded && activity.get() != null) { - onFragmentLoaded(); - } + txtvEmpty = (TextView) root.findViewById(android.R.id.empty); + txtvEmpty.setVisibility(View.GONE); + progLoading = (ProgressBar) root.findViewById(R.id.progLoading); + progLoading.setVisibility(View.VISIBLE); return root; } - private void onFragmentLoaded() { - if (listAdapter == null) { - listAdapter = new QueueListAdapter(activity.get(), itemAccess, new DefaultActionButtonCallback(activity.get())); - listView.setAdapter(listAdapter); - downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback); - downloadObserver.onResume(); + private void onFragmentLoaded(final boolean restoreScrollPosition) { + if (recyclerAdapter == null) { + MainActivity activity = (MainActivity) getActivity(); + recyclerAdapter = new QueueRecyclerAdapter(activity, itemAccess, + new DefaultActionButtonCallback(activity), itemTouchHelper); + recyclerView.setAdapter(recyclerAdapter); + } + if(queue == null || queue.size() == 0) { + recyclerView.setVisibility(View.GONE); + txtvEmpty.setVisibility(View.VISIBLE); + } else { + txtvEmpty.setVisibility(View.GONE); + recyclerView.setVisibility(View.VISIBLE); } - listAdapter.notifyDataSetChanged(); - restoreScrollPosition(); + if (restoreScrollPosition) { + restoreScrollPosition(); + } // we need to refresh the options menu because it sometimes // needs data that may have just been loaded. getActivity().supportInvalidateOptionsMenu(); - } - private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { - @Override - public void onContentChanged() { - if (listAdapter != null && !blockDownloadObserverUpdate) { - listAdapter.notifyDataSetChanged(); - } - } + refreshInfoBar(); + } - @Override - public void onDownloadDataAvailable(List<Downloader> downloaderList) { - QueueFragment.this.downloaderList = downloaderList; - if (listAdapter != null && !blockDownloadObserverUpdate) { - listAdapter.notifyDataSetChanged(); + private void refreshInfoBar() { + String info = queue.size() + getString(R.string.episodes_suffix); + if(queue.size() > 0) { + long duration = 0; + for(FeedItem item : queue) { + if(item.getMedia() != null) { + duration += item.getMedia().getDuration(); + } } + info += " \u2022 "; + info += Converter.getDurationStringLocalized(getActivity(), duration); } - }; + infoBar.setText(info); + } - private QueueListAdapter.ItemAccess itemAccess = new QueueListAdapter.ItemAccess() { + private QueueRecyclerAdapter.ItemAccess itemAccess = new QueueRecyclerAdapter.ItemAccess() { @Override public int getCount() { - return (itemsLoaded) ? queue.size() : 0; + return queue != null ? queue.size() : 0; } @Override public FeedItem getItem(int position) { - return (itemsLoaded) ? queue.get(position) : null; + if (queue != null && 0 <= position && position < queue.size()) { + return queue.get(position); + } + return null; } @Override @@ -533,13 +563,33 @@ public class QueueFragment extends Fragment { } return 0; } + + @Override + public LongList getQueueIds() { + return queue != null ? LongList.of(FeedItemUtil.getIds(queue)) : new LongList(0); + } + + @Override + public LongList getFavoritesIds() { + LongList favoritesIds = new LongList(); + if(queue == null) { + return favoritesIds; + } + for(FeedItem item : queue) { + if(item.isTagged(FeedItem.TAG_FAVORITE)) { + favoritesIds.add(item.getId()); + } + } + return favoritesIds; + } }; private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override public void update(EventDistributor eventDistributor, Integer arg) { if ((arg & EVENTS) != 0) { - startItemLoader(); + Log.d(TAG, "arg: " + arg); + loadItems(false); if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { getActivity().supportInvalidateOptionsMenu(); } @@ -547,55 +597,31 @@ public class QueueFragment extends Fragment { } }; - private ItemLoader itemLoader; - - private void startItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); + private void loadItems(final boolean restoreScrollPosition) { + Log.d(TAG, "loadItems()"); + if(subscription != null) { + subscription.unsubscribe(); } - itemLoader = new ItemLoader(); - itemLoader.execute(); - } - - private void stopItemLoader() { - if (itemLoader != null) { - itemLoader.cancel(true); + if (queue == null) { + recyclerView.setVisibility(View.GONE); + txtvEmpty.setVisibility(View.GONE); + progLoading.setVisibility(View.VISIBLE); } + subscription = Observable.fromCallable(() -> DBReader.getQueue()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(items -> { + if(items != null) { + progLoading.setVisibility(View.GONE); + queue = items; + onFragmentLoaded(restoreScrollPosition); + if(recyclerAdapter != null) { + recyclerAdapter.notifyDataSetChanged(); + } + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } - private class ItemLoader extends AsyncTask<Void, Void, List<FeedItem>> { - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (viewsCreated && !itemsLoaded) { - listView.setVisibility(View.GONE); - txtvEmpty.setVisibility(View.GONE); - progLoading.setVisibility(View.VISIBLE); - } - } - - @Override - protected void onPostExecute(List<FeedItem> feedItems) { - super.onPostExecute(feedItems); - listView.setVisibility(View.VISIBLE); - progLoading.setVisibility(View.GONE); - - if (feedItems != null) { - queue = feedItems; - itemsLoaded = true; - if (viewsCreated && activity.get() != null) { - onFragmentLoaded(); - } - } - } - - @Override - protected List<FeedItem> doInBackground(Void... params) { - Context context = activity.get(); - if (context != null) { - return DBReader.getQueue(context); - } - return null; - } - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java index b1b61f74b..ba526edb3 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; -import android.os.Handler; import android.support.v4.app.ListFragment; +import android.util.Log; import android.view.View; import android.widget.ListView; import android.widget.Toast; @@ -11,7 +11,8 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DownloadlistAdapter; -import de.danoeh.antennapod.core.asynctask.DownloadObserver; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadRequest; @@ -19,24 +20,17 @@ import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.greenrobot.event.EventBus; /** * Displays all running downloads and provides actions to cancel them */ public class RunningDownloadsFragment extends ListFragment { - private static final String TAG = "RunningDownloadsFragment"; - - private DownloadObserver downloadObserver; - private List<Downloader> downloaderList; + private static final String TAG = "RunningDownloadsFrag"; - @Override - public void onDetach() { - super.onDetach(); - if (downloadObserver != null) { - downloadObserver.onPause(); - } - } + private DownloadlistAdapter adapter; + private List<Downloader> downloaderList; @Override public void onViewCreated(View view, Bundle savedInstanceState) { @@ -48,24 +42,39 @@ public class RunningDownloadsFragment extends ListFragment { final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); lv.setPadding(0, vertPadding, 0, vertPadding); - final DownloadlistAdapter downloadlistAdapter = new DownloadlistAdapter(getActivity(), itemAccess); - setListAdapter(downloadlistAdapter); + adapter = new DownloadlistAdapter(getActivity(), itemAccess); + setListAdapter(adapter); + } - downloadObserver = new DownloadObserver(getActivity(), new Handler(), new DownloadObserver.Callback() { - @Override - public void onContentChanged() { - downloadlistAdapter.notifyDataSetChanged(); - } + @Override + public void onResume() { + super.onResume(); + EventBus.getDefault().registerSticky(this); + } - @Override - public void onDownloadDataAvailable(List<Downloader> downloaderList) { - RunningDownloadsFragment.this.downloaderList = downloaderList; - downloadlistAdapter.notifyDataSetChanged(); - } - }); - downloadObserver.onResume(); + @Override + public void onPause() { + super.onPause(); + EventBus.getDefault().unregister(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + setListAdapter(null); + adapter = null; + } + + 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(); + } } + private DownloadlistAdapter.ItemAccess itemAccess = new DownloadlistAdapter.ItemAccess() { @Override public int getCount() { @@ -74,7 +83,11 @@ public class RunningDownloadsFragment extends ListFragment { @Override public Downloader getItem(int position) { - return (downloaderList != null) ? downloaderList.get(position) : null; + if (downloaderList != null && 0 <= position && position < downloaderList.size()) { + return downloaderList.get(position); + } else { + return null; + } } @Override @@ -84,8 +97,8 @@ public class RunningDownloadsFragment extends ListFragment { if(downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA && UserPreferences.isEnableAutodownload()) { - FeedMedia media = DBReader.getFeedMedia(getActivity(), downloadRequest.getFeedfileId()); - DBWriter.setFeedItemAutoDownload(getActivity(), media.getItem(), false); + FeedMedia media = DBReader.getFeedMedia(downloadRequest.getFeedfileId()); + DBWriter.setFeedItemAutoDownload(media.getItem(), false); Toast.makeText(getActivity(), R.string.download_canceled_autodownload_enabled_msg, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getActivity(), R.string.download_canceled_msg, Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index fc6225409..dbd18163c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -1,19 +1,18 @@ package de.danoeh.antennapod.fragment; import android.content.Context; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ListView; -import java.util.Collections; import java.util.List; import de.danoeh.antennapod.R; @@ -25,6 +24,10 @@ import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.SearchResult; import de.danoeh.antennapod.core.storage.FeedSearcher; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Performs a search operation on all feeds or one specific feed and displays the search result. @@ -41,6 +44,8 @@ public class SearchFragment extends ListFragment { private boolean viewCreated = false; private boolean itemsLoaded = false; + private Subscription subscription; + /** * Create a new SearchFragment that searches all feeds. */ @@ -68,7 +73,7 @@ public class SearchFragment extends ListFragment { super.onCreate(savedInstanceState); setRetainInstance(true); setHasOptionsMenu(true); - startSearchTask(); + search(); } @Override @@ -80,14 +85,18 @@ public class SearchFragment extends ListFragment { @Override public void onStop() { super.onStop(); - stopSearchTask(); + if(subscription != null) { + subscription.unsubscribe(); + } EventDistributor.getInstance().unregister(contentUpdate); } @Override public void onDetach() { super.onDetach(); - stopSearchTask(); + if(subscription != null) { + subscription.unsubscribe(); + } } @Override @@ -107,7 +116,7 @@ public class SearchFragment extends ListFragment { final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); lv.setPadding(0, vertPadding, 0, vertPadding); - ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(R.string.search_label); + ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.search_label); viewCreated = true; if (itemsLoaded) { onFragmentLoaded(); @@ -120,7 +129,7 @@ public class SearchFragment extends ListFragment { SearchResult result = (SearchResult) l.getAdapter().getItem(position); FeedComponent comp = result.getComponent(); if (comp.getClass() == Feed.class) { - ((MainActivity) getActivity()).loadFeedFragmentById(comp.getId()); + ((MainActivity) getActivity()).loadFeedFragmentById(comp.getId(), null); } else { if (comp.getClass() == FeedItem.class) { FeedItem item = (FeedItem) comp; @@ -143,7 +152,7 @@ public class SearchFragment extends ListFragment { public boolean onQueryTextSubmit(String s) { getArguments().putString(ARG_QUERY, s); itemsLoaded = false; - startSearchTask(); + search(); return true; } @@ -161,7 +170,7 @@ public class SearchFragment extends ListFragment { public void update(EventDistributor eventDistributor, Integer arg) { if ((arg & (EventDistributor.UNREAD_ITEMS_UPDATE | EventDistributor.DOWNLOAD_HANDLED)) != 0) { - startSearchTask(); + search(); } } }; @@ -183,57 +192,44 @@ public class SearchFragment extends ListFragment { @Override public SearchResult getItem(int position) { - return (searchResults != null) ? searchResults.get(position) : null; + if (searchResults != null && 0 <= position && position < searchResults.size()) { + return searchResults.get(position); + } else { + return null; + } } }; - private SearchTask searchTask; - private void startSearchTask() { - if (searchTask != null) { - searchTask.cancel(true); + private void search() { + if(subscription != null) { + subscription.unsubscribe(); } - searchTask = new SearchTask(); - searchTask.execute(getArguments()); - } - - private void stopSearchTask() { - if (searchTask != null) { - searchTask.cancel(true); + if (viewCreated && !itemsLoaded) { + setListShown(false); } + subscription = Observable.fromCallable(() -> performSearch()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result != null) { + itemsLoaded = true; + searchResults = result; + if (viewCreated) { + onFragmentLoaded(); + } + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); } - private class SearchTask extends AsyncTask<Bundle, Void, List<SearchResult>> { - @Override - protected List<SearchResult> doInBackground(Bundle... params) { - String query = params[0].getString(ARG_QUERY); - long feed = params[0].getLong(ARG_FEED); - Context context = getActivity(); - if (context != null) { - return FeedSearcher.performSearch(context, query, feed); - } else { - return Collections.emptyList(); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (viewCreated && !itemsLoaded) { - setListShown(false); - } - } - - @Override - protected void onPostExecute(List<SearchResult> results) { - super.onPostExecute(results); - if (results != null) { - itemsLoaded = true; - searchResults = results; - if (viewCreated) { - onFragmentLoaded(); - } - } - } + private List<SearchResult> performSearch() { + Bundle args = getArguments(); + String query = args.getString(ARG_QUERY); + long feed = args.getLong(ARG_FEED); + Context context = getActivity(); + return FeedSearcher.performSearch(context, query, feed); } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index 7f37ea680..286212891 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.fragment; 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; @@ -15,12 +16,12 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.NavListAdapter; import de.danoeh.antennapod.adapter.SubscriptionsAdapter; -import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.storage.DBReader; import de.greenrobot.event.EventBus; import rx.Observable; -import rx.functions.Action1; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Fragment for displaying feed subscriptions @@ -65,7 +66,20 @@ public class SubscriptionFragment extends Fragment { mSubscriptionAdapter = new SubscriptionsAdapter(getActivity(), mItemAccess); mSubscriptionGridLayout.setAdapter(mSubscriptionAdapter); - refreshSubscriptionList(); + + Observable.fromCallable(() -> loadData()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + mDrawerData = result; + mSubscriptionList = mDrawerData.feeds; + mSubscriptionAdapter.setItemAccess(mItemAccess); + mSubscriptionAdapter.notifyDataSetChanged(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + + mSubscriptionGridLayout.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { @@ -79,38 +93,9 @@ public class SubscriptionFragment extends Fragment { } - private void refreshSubscriptionList() { - Observable.just(loadData()).subscribe(new Action1<DBReader.NavDrawerData>() { - @Override - public void call(DBReader.NavDrawerData navDrawerData) { - mDrawerData = navDrawerData; - mSubscriptionList = mDrawerData.feeds; - mSubscriptionAdapter.setItemAccess(mItemAccess); - mSubscriptionAdapter.notifyDataSetChanged(); - } - }); - } - - EventDistributor.EventListener updateListener = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) { - refreshSubscriptionList(); - } - } - }; - - - @Override - public void onStop() { - super.onStop(); - EventDistributor.getInstance().unregister(updateListener); - } - @Override public void onResume() { super.onResume(); - EventDistributor.getInstance().register(updateListener); } public class SubscriptionEvent { @@ -123,6 +108,6 @@ public class SubscriptionFragment extends Fragment { private DBReader.NavDrawerData loadData() { - return DBReader.getNavDrawerData(getActivity()); + return DBReader.getNavDrawerData(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java index 55d4b940f..aff5069c6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java @@ -1,7 +1,10 @@ package de.danoeh.antennapod.fragment.gpodnet; +import android.content.Context; +import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; +import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; @@ -17,16 +20,49 @@ import de.danoeh.antennapod.R; */ public class GpodnetMainFragment extends Fragment { + public static final String TAG = "GpodnetMainFragment"; + + private static final String PREF_LAST_TAB_POSITION = "tab_position"; + private TabLayout tabLayout; + private ViewPager viewPager; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View root = inflater.inflate(R.layout.pager_fragment, container, false); - ViewPager pager = (ViewPager) root.findViewById(R.id.pager); + + viewPager = (ViewPager)root.findViewById(R.id.viewpager); GpodnetPagerAdapter pagerAdapter = new GpodnetPagerAdapter(getChildFragmentManager(), getResources()); - pager.setAdapter(pagerAdapter); + viewPager.setAdapter(pagerAdapter); + + // Give the TabLayout the ViewPager + tabLayout = (TabLayout) root.findViewById(R.id.sliding_tabs); + tabLayout.setupWithViewPager(viewPager); + return root; } + + @Override + public void onPause() { + super.onPause(); + // save our tab selection + SharedPreferences prefs = getActivity().getSharedPreferences(TAG, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PREF_LAST_TAB_POSITION, tabLayout.getSelectedTabPosition()); + editor.apply(); + } + + @Override + public void onStart() { + super.onStart(); + + // restore our last position + SharedPreferences prefs = getActivity().getSharedPreferences(TAG, Context.MODE_PRIVATE); + int lastPosition = prefs.getInt(PREF_LAST_TAB_POSITION, 0); + viewPager.setCurrentItem(lastPosition); + } + public class GpodnetPagerAdapter extends FragmentPagerAdapter { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index 6139a4901..204f36956 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -23,7 +23,6 @@ import android.widget.TextView; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter; @@ -62,7 +61,10 @@ public abstract class PodcastListFragment extends Fragment { @Override public boolean onQueryTextSubmit(String s) { sv.clearFocus(); - ((MainActivity) getActivity()).loadChildFragment(SearchListFragment.newInstance(s)); + MainActivity activity = (MainActivity)getActivity(); + if (activity != null) { + activity.loadChildFragment(SearchListFragment.newInstance(s)); + } return true; } @@ -101,9 +103,9 @@ public abstract class PodcastListFragment extends Fragment { protected void onPodcastSelected(GpodnetPodcast selection) { Log.d(TAG, "Selected podcast: " + selection.toString()); - Intent intent = new Intent(getActivity(), DefaultOnlineFeedViewActivity.class); + Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, selection.getUrl()); - intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, getString(R.string.gpodnet_main_label)); + intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.gpodnet_main_label)); startActivity(intent); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java index e2450f03d..d39829260 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java @@ -45,7 +45,7 @@ public class TagFragment extends PodcastListFragment { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - ((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(tag.getTitle()); + ((MainActivity) getActivity()).getSupportActionBar().setTitle(tag.getTitle()); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index 5bd567a2f..338f02e61 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -80,7 +80,7 @@ public class TagListFragment extends ListFragment { @Override public void onResume() { super.onResume(); - ((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(R.string.add_feed_label); + ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.add_feed_label); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index fe1a09149..58fe8afbf 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.util.Log; +import android.widget.Toast; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; @@ -16,7 +17,7 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.ShareUtils; @@ -56,26 +57,29 @@ public class FeedItemMenuHandler { * @param queueAccess Used for testing if the queue contains the selected item * @return Returns true if selectedItem is not null. */ - public static boolean onPrepareMenu(MenuInterface mi, FeedItem selectedItem, - boolean showExtendedMenu, LongList queueAccess) { + public static boolean onPrepareMenu(MenuInterface mi, + FeedItem selectedItem, + boolean showExtendedMenu, + LongList queueAccess, + LongList favorites) { if (selectedItem == null) { return false; } boolean hasMedia = selectedItem.getMedia() != null; - boolean isPlaying = hasMedia - && selectedItem.getState() == FeedItem.State.PLAYING; - - FeedItem.State state = selectedItem.getState(); + boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING; if (!isPlaying) { mi.setItemVisibility(R.id.skip_episode_item, false); } - boolean isInQueue = queueAccess.contains(selectedItem.getId()); - if(queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId()) { + boolean isInQueue = false; + if(queueAccess != null) { + isInQueue = queueAccess.contains(selectedItem.getId()); + } + if(queueAccess == null || queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId()) { mi.setItemVisibility(R.id.move_to_top_item, false); } - if(queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == selectedItem.getId()) { + if(queueAccess == null || queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == selectedItem.getId()) { mi.setItemVisibility(R.id.move_to_bottom_item, false); } if (!isInQueue || isPlaying) { @@ -84,14 +88,24 @@ public class FeedItemMenuHandler { if (!(!isInQueue && selectedItem.getMedia() != null)) { mi.setItemVisibility(R.id.add_to_queue_item, false); } + if (!showExtendedMenu || selectedItem.getLink() == null) { + mi.setItemVisibility(R.id.visit_website_item, false); mi.setItemVisibility(R.id.share_link_item, false); + mi.setItemVisibility(R.id.share_link_with_position_item, false); + } + if (!showExtendedMenu || !hasMedia || selectedItem.getMedia().getDownload_url() == null) { + mi.setItemVisibility(R.id.share_download_url_item, false); + mi.setItemVisibility(R.id.share_download_url_with_position_item, false); + } + if(false == hasMedia || selectedItem.getMedia().getPosition() <= 0) { + mi.setItemVisibility(R.id.share_link_with_position_item, false); + mi.setItemVisibility(R.id.share_download_url_with_position_item, false); } - if (!(state == FeedItem.State.UNREAD || state == FeedItem.State.IN_PROGRESS)) { + if (selectedItem.isPlayed()) { mi.setItemVisibility(R.id.mark_read_item, false); - } - if (!(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) { + } else { mi.setItemVisibility(R.id.mark_unread_item, false); } @@ -108,13 +122,14 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.deactivate_auto_download, false); } - if (!showExtendedMenu || selectedItem.getLink() == null) { - mi.setItemVisibility(R.id.visit_website_item, false); - } - if (selectedItem.getPaymentLink() == null || !selectedItem.getFlattrStatus().flattrable()) { mi.setItemVisibility(R.id.support_item, false); } + + boolean isFavorite = favorites != null && favorites.contains(selectedItem.getId()); + mi.setItemVisibility(R.id.add_to_favorites_item, !isFavorite); + mi.setItemVisibility(R.id.remove_from_favorites_item, isFavorite); + return true; } @@ -126,20 +141,22 @@ public class FeedItemMenuHandler { * @return true if selectedItem is not null. */ public static boolean onPrepareMenu(MenuInterface mi, - FeedItem selectedItem, boolean showExtendedMenu, LongList queueAccess, int... excludeIds) { - boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess); + FeedItem selectedItem, + boolean showExtendedMenu, + LongList queueAccess, + LongList favorites, + int... excludeIds) { + boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess, favorites); if (rc && excludeIds != null) { for (int id : excludeIds) { mi.setItemVisibility(id, false); } } - return rc; } public static boolean onMenuItemClicked(Context context, int menuItemId, FeedItem selectedItem) throws DownloadRequestException { - DownloadRequester requester = DownloadRequester.getInstance(); switch (menuItemId) { case R.id.skip_episode_item: context.sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); @@ -148,24 +165,27 @@ public class FeedItemMenuHandler { DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId()); break; case R.id.mark_read_item: - selectedItem.setRead(true); - DBWriter.markItemRead(context, selectedItem, true, false); + selectedItem.setPlayed(true); + DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, false); if(GpodnetPreferences.loggedIn()) { FeedMedia media = selectedItem.getMedia(); - GpodnetEpisodeAction actionPlay = new GpodnetEpisodeAction.Builder(selectedItem, Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(media.getDuration() / 1000) - .position(media.getDuration() / 1000) - .total(media.getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(actionPlay); + // not all items have media, Gpodder only cares about those that do + if (media != null) { + GpodnetEpisodeAction actionPlay = new GpodnetEpisodeAction.Builder(selectedItem, Action.PLAY) + .currentDeviceId() + .currentTimestamp() + .started(media.getDuration() / 1000) + .position(media.getDuration() / 1000) + .total(media.getDuration() / 1000) + .build(); + GpodnetPreferences.enqueueEpisodeAction(actionPlay); + } } break; case R.id.mark_unread_item: - selectedItem.setRead(false); - DBWriter.markItemRead(context, selectedItem, false, false); - if(GpodnetPreferences.loggedIn()) { + selectedItem.setPlayed(false); + DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false); + if(GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) { GpodnetEpisodeAction actionNew = new GpodnetEpisodeAction.Builder(selectedItem, Action.NEW) .currentDeviceId() .currentTimestamp() @@ -173,32 +193,39 @@ public class FeedItemMenuHandler { GpodnetPreferences.enqueueEpisodeAction(actionNew); } break; - case R.id.move_to_top_item: - DBWriter.moveQueueItemToTop(context, selectedItem.getId(), true); - return true; - case R.id.move_to_bottom_item: - DBWriter.moveQueueItemToBottom(context, selectedItem.getId(), true); case R.id.add_to_queue_item: - DBWriter.addQueueItem(context, selectedItem.getId()); + DBWriter.addQueueItem(context, selectedItem); break; case R.id.remove_from_queue_item: DBWriter.removeQueueItem(context, selectedItem, true); break; + case R.id.add_to_favorites_item: + DBWriter.addFavoriteItem(selectedItem); + break; + case R.id.remove_from_favorites_item: + DBWriter.removeFavoriteItem(selectedItem); + break; case R.id.reset_position: selectedItem.getMedia().setPosition(0); - DBWriter.markItemRead(context, selectedItem, false, true); + DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, true); break; case R.id.activate_auto_download: selectedItem.setAutoDownload(true); - DBWriter.setFeedItemAutoDownload(context, selectedItem, true); + DBWriter.setFeedItemAutoDownload(selectedItem, true); break; case R.id.deactivate_auto_download: selectedItem.setAutoDownload(false); - DBWriter.setFeedItemAutoDownload(context, selectedItem, false); + DBWriter.setFeedItemAutoDownload(selectedItem, false); break; case R.id.visit_website_item: Uri uri = Uri.parse(selectedItem.getLink()); - context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(context, intent)) { + context.startActivity(intent); + } else { + Toast.makeText(context, context.getString(R.string.download_error_malformed_url), + Toast.LENGTH_SHORT); + } break; case R.id.support_item: DBTasks.flattrItemIfLoggedIn(context, selectedItem); @@ -206,6 +233,15 @@ public class FeedItemMenuHandler { case R.id.share_link_item: ShareUtils.shareFeedItemLink(context, selectedItem); break; + case R.id.share_download_url_item: + ShareUtils.shareFeedItemDownloadLink(context, selectedItem); + break; + case R.id.share_link_with_position_item: + ShareUtils.shareFeedItemLink(context, selectedItem, true); + break; + case R.id.share_download_url_with_position_item: + ShareUtils.shareFeedItemDownloadLink(context, selectedItem, true); + break; default: Log.d(TAG, "Unknown menuItemId: " + menuItemId); return false; diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java index 7bd8fedc9..830e9d419 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -1,18 +1,21 @@ package de.danoeh.antennapod.menuhandler; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.widget.Toast; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; @@ -20,6 +23,7 @@ import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ShareUtils; /** @@ -39,11 +43,11 @@ public class FeedMenuHandler { } Log.d(TAG, "Preparing options menu"); - menu.findItem(R.id.mark_all_read_item).setVisible(selectedFeed.hasNewItems()); - if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) + if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) { menu.findItem(R.id.support_item).setVisible(true); - else + } else { menu.findItem(R.id.support_item).setVisible(false); + } menu.findItem(R.id.refresh_complete_item).setVisible(selectedFeed.isPaged()); @@ -64,8 +68,8 @@ public class FeedMenuHandler { case R.id.refresh_complete_item: DBTasks.refreshCompleteFeed(context, selectedFeed); break; - case R.id.hide_items: - showHideDialog(context, selectedFeed); + case R.id.filter_items: + showFilterDialog(context, selectedFeed); break; case R.id.mark_all_read_item: ConfirmationDialog conDialog = new ConfirmationDialog(context, @@ -76,14 +80,20 @@ public class FeedMenuHandler { public void onConfirmButtonPressed( DialogInterface dialog) { dialog.dismiss(); - DBWriter.markFeedRead(context, selectedFeed.getId()); + DBWriter.markFeedRead(selectedFeed.getId()); } }; conDialog.createNewDialog().show(); break; case R.id.visit_website_item: Uri uri = Uri.parse(selectedFeed.getLink()); - context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(context, intent)) { + context.startActivity(intent); + } else { + Toast.makeText(context, context.getString(R.string.download_error_malformed_url), + Toast.LENGTH_SHORT); + } break; case R.id.support_item: DBTasks.flattrFeedIfLoggedIn(context, selectedFeed); @@ -91,7 +101,7 @@ public class FeedMenuHandler { case R.id.share_link_item: ShareUtils.shareFeedlink(context, selectedFeed); break; - case R.id.share_source_item: + case R.id.share_download_url_item: ShareUtils.shareFeedDownloadLink(context, selectedFeed); break; default: @@ -100,38 +110,38 @@ public class FeedMenuHandler { return true; } - private static void showHideDialog(final Context context, final Feed feed) { - - final String[] items = context.getResources().getStringArray(R.array.episode_hide_options); - final String[] values = context.getResources().getStringArray(R.array.episode_hide_values); + private static void showFilterDialog(final Context context, final Feed feed) { + final String[] items = context.getResources().getStringArray(R.array.episode_filter_options); + final String[] values = context.getResources().getStringArray(R.array.episode_filter_values); final boolean[] checkedItems = new boolean[items.length]; - final List<String> hidden = new ArrayList<String>(Arrays.asList(feed.getItemFilter().getValues())); + final Set<String> filter = new HashSet<>(Arrays.asList(feed.getItemFilter().getValues())); + Iterator<String> it = filter.iterator(); + while(it.hasNext()) { + // make sure we have no empty strings in the filter list + if(TextUtils.isEmpty(it.next())) { + it.remove(); + } + } for(int i=0; i < values.length; i++) { String value = values[i]; - if(hidden.contains(value)) { + if(filter.contains(value)) { checkedItems[i] = true; } } AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.hide_episodes_title); - builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - if (isChecked) { - hidden.add(values[which]); - } else { - hidden.remove(values[which]); - } + builder.setTitle(R.string.filter); + builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> { + if (isChecked) { + filter.add(values[which]); + } else { + filter.remove(values[which]); } }); - builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - feed.setHiddenItemProperties(hidden.toArray(new String[hidden.size()])); - DBWriter.setFeedItemsFilter(context, feed.getId(), hidden); - } + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + feed.setItemFilter(filter.toArray(new String[filter.size()])); + DBWriter.setFeedItemsFilter(feed.getId(), filter); }); builder.setNegativeButton(R.string.cancel_label, null); builder.create().show(); diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java index cfc540fd6..0d2ff8a75 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java @@ -34,10 +34,10 @@ public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuIte TypedArray ta = context.obtainStyledAttributes(lockIcons); if (UserPreferences.isQueueLocked()) { queueLock.setTitle(de.danoeh.antennapod.R.string.unlock_queue); - queueLock.setIcon(ta.getDrawable(1)); + queueLock.setIcon(ta.getDrawable(0)); } else { queueLock.setTitle(de.danoeh.antennapod.R.string.lock_queue); - queueLock.setIcon(ta.getDrawable(0)); + queueLock.setIcon(ta.getDrawable(1)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/CustomEditTextPreference.java b/app/src/main/java/de/danoeh/antennapod/preferences/CustomEditTextPreference.java deleted file mode 100644 index 898a56004..000000000 --- a/app/src/main/java/de/danoeh/antennapod/preferences/CustomEditTextPreference.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.danoeh.antennapod.preferences; - -import android.app.AlertDialog; -import android.content.Context; -import android.os.Build; -import android.preference.EditTextPreference; -import android.util.AttributeSet; - -import de.danoeh.antennapod.R; - -public class CustomEditTextPreference extends EditTextPreference { - - public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public CustomEditTextPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CustomEditTextPreference(Context context) { - super(context); - } - - @Override - protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - builder.setInverseBackgroundForced(true); - getEditText().setTextColor(getContext().getResources().getColor(R.color.black)); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java index f387b7524..785944768 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -1,11 +1,14 @@ package de.danoeh.antennapod.preferences; +import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; +import android.app.TimePickerDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Resources; +import android.net.Uri; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Build; @@ -13,19 +16,31 @@ import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceManager; import android.preference.PreferenceScreen; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; import android.text.Editable; +import android.text.Html; import android.text.TextWatcher; +import android.text.format.DateFormat; import android.util.Log; import android.widget.EditText; import android.widget.Toast; +import com.afollestad.materialdialogs.MaterialDialog; + +import org.apache.commons.lang3.ArrayUtils; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.List; +import java.util.concurrent.TimeUnit; -import de.danoeh.antennapod.BuildConfig; +import de.danoeh.antennapod.CrashReportWriter; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.AboutActivity; import de.danoeh.antennapod.activity.DirectoryChooserActivity; @@ -33,12 +48,11 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.activity.PreferenceActivityGingerbread; import de.danoeh.antennapod.asynctask.OpmlExportWorker; -import de.danoeh.antennapod.core.asynctask.FlattrClickWorker; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.flattr.FlattrStatus; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import de.danoeh.antennapod.core.util.flattr.SimpleFlattrThing; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.AutoFlattrPreferenceDialog; import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; @@ -47,9 +61,11 @@ import de.danoeh.antennapod.dialog.VariableSpeedDialog; /** * Sets up a preference UI that lets the user change user preferences. */ -public class PreferenceController { + +public class PreferenceController implements SharedPreferences.OnSharedPreferenceChangeListener { + private static final String TAG = "PreferenceController"; - public static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp"; + public static final String PREF_FLATTR_SETTINGS = "prefFlattrSettings"; public static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate"; public static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; @@ -71,6 +87,18 @@ public class PreferenceController { public PreferenceController(PreferenceUI ui) { this.ui = ui; + PreferenceManager.getDefaultSharedPreferences(ui.getActivity().getApplicationContext()) + .registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if(key.equals(UserPreferences.PREF_SONIC)) { + CheckBoxPreference prefSonic = (CheckBoxPreference) ui.findPreference(UserPreferences.PREF_SONIC); + if(prefSonic != null) { + prefSonic.setChecked(sharedPreferences.getBoolean(UserPreferences.PREF_SONIC, false)); + } + } } /** @@ -105,23 +133,6 @@ public class PreferenceController { ); } - ui.findPreference(PreferenceController.PREF_FLATTR_THIS_APP).setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() { - - @Override - public boolean onPreferenceClick(Preference preference) { - new FlattrClickWorker(activity, - new SimpleFlattrThing(activity.getString(R.string.app_name), - FlattrUtils.APP_URL, - new FlattrStatus(FlattrStatus.STATUS_QUEUE) - ) - ).executeAsync(); - - return true; - } - } - ); - ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setOnPreferenceClickListener( new Preference.OnPreferenceClickListener() { @@ -160,21 +171,22 @@ public class PreferenceController { } } ); - - ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() { - - @Override - public boolean onPreferenceClick(Preference preference) { - activity.startActivityForResult( - new Intent(activity, - DirectoryChooserActivity.class), - DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED - ); - return true; - } - } - ); + ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR) + .setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + if (Build.VERSION.SDK_INT >= 19) { + showChooseDataFolderDialog(); + } else { + Intent intent = new Intent(activity, DirectoryChooserActivity.class); + activity.startActivityForResult(intent, + DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); + } + return true; + } + } + ); ui.findPreference(UserPreferences.PREF_THEME) .setOnPreferenceChangeListener( new Preference.OnPreferenceChangeListener() { @@ -200,18 +212,26 @@ public class PreferenceController { } }); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL) - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL) + .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (newValue instanceof Boolean) { - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled((Boolean) newValue); - setSelectedNetworksEnabled((Boolean) newValue && UserPreferences.isEnableAutodownloadWifiFilter()); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled((Boolean) newValue); - } + public boolean onPreferenceClick(Preference preference) { + showUpdateIntervalTimePreferencesDialog(); return true; } }); + + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL).setOnPreferenceChangeListener( + (preference, newValue) -> { + if (newValue instanceof Boolean) { + boolean enabled = (Boolean) newValue; + ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setEnabled(enabled); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled(enabled); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled(enabled); + setSelectedNetworksEnabled(enabled && UserPreferences.isEnableAutodownloadWifiFilter()); + } + return true; + }); ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER) .setOnPreferenceChangeListener( new Preference.OnPreferenceChangeListener() { @@ -240,7 +260,7 @@ public class PreferenceController { setParallelDownloadsText(value); return true; } - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { return false; } } @@ -249,17 +269,19 @@ public class PreferenceController { } ); // validate and set correct value: number of downloads between 1 and 50 (inclusive) - final EditText ev = ((EditTextPreference)ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)).getEditText(); + final EditText ev = ((EditTextPreference) ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)).getEditText(); ev.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) {} + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { - if(s.length() > 0) { + if (s.length() > 0) { try { int value = Integer.valueOf(s.toString()); if (value <= 0) { @@ -267,7 +289,7 @@ public class PreferenceController { } else if (value > 50) { ev.setText("50"); } - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { ev.setText("6"); } ev.setSelection(ev.getText().length()); @@ -345,38 +367,91 @@ public class PreferenceController { @Override public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue) { - UserPreferences.setAutoFlattrSettings(activity, autoFlattrEnabled, autoFlattrValue); + UserPreferences.setAutoFlattrSettings(autoFlattrEnabled, autoFlattrValue); checkItemVisibility(); } }); return true; } }); - buildUpdateIntervalPreference(); + ui.findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE) + .setOnPreferenceChangeListener( + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object o) { + if (o instanceof String) { + int newValue = Integer.valueOf((String) o) * 1024 * 1024; + if (newValue != UserPreferences.getImageCacheSize()) { + AlertDialog.Builder dialog = new AlertDialog.Builder(ui.getActivity()); + dialog.setTitle(android.R.string.dialog_alert_title); + dialog.setMessage(R.string.pref_restart_required); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.show(); + } + return true; + } + return false; + } + } + ); + ui.findPreference("prefSendCrashReport").setOnPreferenceClickListener(preference -> { + Intent emailIntent = new Intent(Intent.ACTION_SEND); + emailIntent.setType("text/plain"); + emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"Martin.Fietz@gmail.com"}); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "AntennaPod Crash Report"); + emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe what you were doing when the app crashed"); + // the attachment + emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(CrashReportWriter.getFile())); + String intentTitle = ui.getActivity().getString(R.string.send_email); + ui.getActivity().startActivity(Intent.createChooser(emailIntent, intentTitle)); + return true; + }); + buildEpisodeCleanupPreference(); buildSmartMarkAsPlayedPreference(); buildAutodownloadSelectedNetworsPreference(); - setSelectedNetworksEnabled(UserPreferences - .isEnableAutodownloadWifiFilter()); + setSelectedNetworksEnabled(UserPreferences.isEnableAutodownloadWifiFilter()); } public void onResume() { checkItemVisibility(); + setUpdateIntervalText(); setParallelDownloadsText(UserPreferences.getParallelDownloads()); setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize()); setDataFolderText(); updateGpodnetPreferenceScreen(); } + @SuppressLint("NewApi") public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { - String dir = data - .getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting data folder"); - UserPreferences.setDataFolder(dir); + if (resultCode == Activity.RESULT_OK && + requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { + String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); + + File path = new File(dir); + String message = null; + final Context context= ui.getActivity().getApplicationContext(); + if(!path.exists()) { + message = String.format(context.getString(R.string.folder_does_not_exist_error), dir); + } else if(!path.canRead()) { + message = String.format(context.getString(R.string.folder_not_readable_error), dir); + } else if(!path.canWrite()) { + message = String.format(context.getString(R.string.folder_not_writable_error), dir); + } + + if(message == null) { + Log.d(TAG, "Setting data folder: " + dir); + UserPreferences.setDataFolder(dir); + setDataFolderText(); + } else { + AlertDialog.Builder ab = new AlertDialog.Builder(ui.getActivity()); + ab.setMessage(message); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } } } + private void updateGpodnetPreferenceScreen() { final boolean loggedIn = GpodnetPreferences.loggedIn(); ui.findPreference(PreferenceController.PREF_GPODNET_LOGIN).setEnabled(!loggedIn); @@ -385,12 +460,8 @@ public class PreferenceController { ui.findPreference(PreferenceController.PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname()); } - private void buildUpdateIntervalPreference() { + private String[] getUpdateIntervalEntries(final String[] values) { final Resources res = ui.getActivity().getResources(); - - ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL); - String[] values = res.getStringArray( - R.array.update_intervall_values); String[] entries = new String[values.length]; for (int x = 0; x < values.length; x++) { Integer v = Integer.parseInt(values[x]); @@ -399,34 +470,56 @@ public class PreferenceController { entries[x] = res.getString(R.string.pref_update_interval_hours_manual); break; case 1: - entries[x] = v - + " " - + res.getString(R.string.pref_update_interval_hours_singular); + entries[x] = v + " " + res.getString(R.string.pref_update_interval_hours_singular); break; default: - entries[x] = v + " " - + res.getString(R.string.pref_update_interval_hours_plural); + entries[x] = v + " " + res.getString(R.string.pref_update_interval_hours_plural); break; } } - pref.setEntries(entries); + return entries; + } + private void buildEpisodeCleanupPreference() { + final Resources res = ui.getActivity().getResources(); + + ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_EPISODE_CLEANUP); + String[] values = res.getStringArray( + R.array.episode_cleanup_values); + String[] entries = new String[values.length]; + for (int x = 0; x < values.length; x++) { + int v = Integer.parseInt(values[x]); + if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) { + entries[x] = res.getString(R.string.episode_cleanup_queue_removal); + } else if (v == UserPreferences.EPISODE_CLEANUP_NULL){ + entries[x] = res.getString(R.string.episode_cleanup_never); + } else if (v == 0) { + entries[x] = res.getString(R.string.episode_cleanup_after_listening); + } else { + entries[x] = res.getQuantityString(R.plurals.episode_cleanup_days_after_listening, v, v); + } + } + pref.setEntries(entries); } private void buildSmartMarkAsPlayedPreference() { final Resources res = ui.getActivity().getResources(); ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS); - String[] values = res.getStringArray( - R.array.smart_mark_as_played_values); + String[] values = res.getStringArray(R.array.smart_mark_as_played_values); String[] entries = new String[values.length]; for (int x = 0; x < values.length; x++) { if(x == 0) { entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled); } else { Integer v = Integer.parseInt(values[x]); - entries[x] = v + " " + res.getString(R.string.time_unit_seconds); + if(v < 60) { + entries[x] = res.getQuantityString(R.plurals.time_seconds_quantified, v, v); + } else { + v /= 60; + entries[x] = res.getQuantityString(R.plurals.time_minutes_quantified, v, v); + } } } pref.setEntries(entries); @@ -442,21 +535,52 @@ public class PreferenceController { @SuppressWarnings("deprecation") private void checkItemVisibility() { - boolean hasFlattrToken = FlattrUtils.hasToken(); - ui.findPreference(PreferenceController.PREF_FLATTR_SETTINGS).setEnabled(FlattrUtils.hasAPICredentials()); ui.findPreference(PreferenceController.PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken); ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken); ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS).setEnabled(hasFlattrToken); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER) - .setEnabled(UserPreferences.isEnableAutodownload()); - setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload() - && UserPreferences.isEnableAutodownloadWifiFilter()); + boolean autoDownload = UserPreferences.isEnableAutodownload(); + ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setEnabled(autoDownload); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled(autoDownload); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled(autoDownload); + setSelectedNetworksEnabled(autoDownload && UserPreferences.isEnableAutodownloadWifiFilter()); + + ui.findPreference("prefSendCrashReport").setEnabled(CrashReportWriter.getFile().exists()); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY) - .setEnabled(UserPreferences.isEnableAutodownload()); + if (Build.VERSION.SDK_INT >= 16) { + ui.findPreference(UserPreferences.PREF_SONIC).setEnabled(true); + } else { + Preference prefSonic = ui.findPreference(UserPreferences.PREF_SONIC); + prefSonic.setSummary("[Android 4.1+]\n" + prefSonic.getSummary()); + } + } + + private void setUpdateIntervalText() { + Context context = ui.getActivity().getApplicationContext(); + String val; + long interval = UserPreferences.getUpdateInterval(); + if(interval > 0) { + int hours = (int) TimeUnit.MILLISECONDS.toHours(interval); + String hoursStr = context.getResources().getQuantityString(R.plurals.time_hours_quantified, hours, hours); + val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_every), hoursStr); + } else { + int[] timeOfDay = UserPreferences.getUpdateTimeOfDay(); + if(timeOfDay.length == 2) { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, timeOfDay[0]); + cal.set(Calendar.MINUTE, timeOfDay[1]); + String timeOfDayStr = DateFormat.getTimeFormat(context).format(cal.getTime()); + val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_at), + timeOfDayStr); + } else { + val = context.getString(R.string.pref_smart_mark_as_played_disabled); + } + } + String summary = context.getString(R.string.pref_autoUpdateIntervallOrTime_sum) + "\n" + + String.format(context.getString(R.string.pref_current_value), val); + ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL).setSummary(summary); } private void setParallelDownloadsText(int downloads) { @@ -482,7 +606,7 @@ public class PreferenceController { } private void setDataFolderText() { - File f = UserPreferences.getDataFolder(ui.getActivity(), null); + File f = UserPreferences.getDataFolder(null); if (f != null) { ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR) .setSummary(f.getAbsolutePath()); @@ -516,9 +640,7 @@ public class PreferenceController { ); boolean newValue = ((CheckBoxPreference) preference) .isChecked(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Selected network " + key - + ". New state: " + newValue); + Log.d(TAG, "Selected network " + key + ". New state: " + newValue); int index = prefValuesList.indexOf(key); if (index >= 0 && newValue == false) { @@ -529,9 +651,7 @@ public class PreferenceController { } UserPreferences.setAutodownloadSelectedNetworks( - activity, prefValuesList - .toArray(new String[prefValuesList - .size()]) + prefValuesList.toArray(new String[prefValuesList.size()]) ); return true; } else { @@ -586,36 +706,156 @@ public class PreferenceController { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.drawer_preferences); - builder.setMultiChoiceItems(navTitles, checked, new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - if (isChecked) { - hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); - } else { - hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); - } + builder.setMultiChoiceItems(navTitles, checked, (dialog, which, isChecked) -> { + if (isChecked) { + hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); + } else { + hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); } }); builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - UserPreferences.setHiddenDrawerItems(context, hiddenDrawerItems); + UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); } }); builder.setNegativeButton(R.string.cancel_label, null); builder.create().show(); } + private void showChooseDataFolderDialog() { + Context context = ui.getActivity(); + File dataFolder = UserPreferences.getDataFolder(null); + if(dataFolder == null) { + new MaterialDialog.Builder(ui.getActivity()) + .title(R.string.error_label) + .content(R.string.external_storage_error_msg) + .neutralText(android.R.string.ok) + .show(); + return; + } + String dataFolderPath = dataFolder.getAbsolutePath(); + int selectedIndex = -1; + File[] mediaDirs = ContextCompat.getExternalFilesDirs(context, null); + List<String> folders = new ArrayList<>(mediaDirs.length); + List<CharSequence> choices = new ArrayList<>(mediaDirs.length); + for(int i=0; i < mediaDirs.length; i++) { + if(mediaDirs[i] == null) { + continue; + } + String path = mediaDirs[i].getAbsolutePath(); + folders.add(path); + if(dataFolderPath.equals(path)) { + selectedIndex = i; + } + int index = path.indexOf("Android"); + String choice; + if(index >= 0) { + choice = path.substring(0, index); + } else { + choice = path; + } + long bytes = StorageUtils.getFreeSpaceAvailable(path); + String freeSpace = String.format(context.getString(R.string.free_space_label), + Converter.byteToString(bytes)); + choices.add(Html.fromHtml("<html><small>" + choice + + " [" + freeSpace + "]" + "</small></html>")); + } + if(choices.size() == 0) { + new MaterialDialog.Builder(ui.getActivity()) + .title(R.string.error_label) + .content(R.string.external_storage_error_msg) + .neutralText(android.R.string.ok) + .show(); + return; + } + MaterialDialog dialog = new MaterialDialog.Builder(ui.getActivity()) + .title(R.string.choose_data_directory) + .content(R.string.choose_data_directory_message) + .items(choices.toArray(new CharSequence[choices.size()])) + .itemsCallbackSingleChoice(selectedIndex, (dialog1, itemView, which, text) -> { + String folder = folders.get(which); + Log.d(TAG, "data folder: " + folder); + UserPreferences.setDataFolder(folder); + setDataFolderText(); + return true; + }) + .negativeText(R.string.cancel_label) + .cancelable(true) + .build(); + dialog.show(); + } + + private void showUpdateIntervalTimePreferencesDialog() { + final Context context = ui.getActivity(); + MaterialDialog.Builder builder = new MaterialDialog.Builder(context); + builder.title(R.string.pref_autoUpdateIntervallOrTime_title); + builder.content(R.string.pref_autoUpdateIntervallOrTime_message); + builder.positiveText(R.string.pref_autoUpdateIntervallOrTime_Interval); + builder.negativeText(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay); + builder.neutralText(R.string.pref_autoUpdateIntervallOrTime_Disable); + builder.callback(new MaterialDialog.ButtonCallback() { + @Override + public void onPositive(MaterialDialog dialog) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_Interval)); + final String[] values = context.getResources().getStringArray(R.array.update_intervall_values); + final String[] entries = getUpdateIntervalEntries(values); + long currInterval = UserPreferences.getUpdateInterval(); + int checkedItem = -1; + if(currInterval > 0) { + String currIntervalStr = String.valueOf(TimeUnit.MILLISECONDS.toHours(currInterval)); + checkedItem = ArrayUtils.indexOf(values, currIntervalStr); + } + builder.setSingleChoiceItems(entries, checkedItem, (dialog1, which) -> { + int hours = Integer.valueOf(values[which]); + UserPreferences.setUpdateInterval(hours); + dialog1.dismiss(); + setUpdateIntervalText(); + }); + builder.setNegativeButton(context.getString(R.string.cancel_label), null); + builder.show(); + } + + @Override + public void onNegative(MaterialDialog dialog) { + int hourOfDay = 7, minute = 0; + int[] updateTime = UserPreferences.getUpdateTimeOfDay(); + if (updateTime.length == 2) { + hourOfDay = updateTime[0]; + minute = updateTime[1]; + } + TimePickerDialog timePickerDialog = new TimePickerDialog(context, + (view, selectedHourOfDay, selectedMinute) -> { + if (view.getTag() == null) { // onTimeSet() may get called twice! + view.setTag("TAGGED"); + UserPreferences.setUpdateTimeOfDay(selectedHourOfDay, selectedMinute); + setUpdateIntervalText(); + } + }, hourOfDay, minute, DateFormat.is24HourFormat(context)); + timePickerDialog.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay)); + timePickerDialog.show(); + } + + @Override + public void onNeutral(MaterialDialog dialog) { + UserPreferences.setUpdateInterval(0); + setUpdateIntervalText(); + } + }); + builder.forceStacking(true); + builder.show(); + } - public static interface PreferenceUI { + public interface PreferenceUI { /** * Finds a preference based on its key. */ - public Preference findPreference(CharSequence key); + Preference findPreference(CharSequence key); - public Activity getActivity(); + Activity getActivity(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/SwitchCompatPreference.java b/app/src/main/java/de/danoeh/antennapod/preferences/SwitchCompatPreference.java new file mode 100644 index 000000000..10c11b88e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/preferences/SwitchCompatPreference.java @@ -0,0 +1,37 @@ +package de.danoeh.antennapod.preferences; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.preference.CheckBoxPreference; +import android.util.AttributeSet; + +import de.danoeh.antennapod.R; + +public class SwitchCompatPreference extends CheckBoxPreference { + + public SwitchCompatPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SwitchCompatPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + public SwitchCompatPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public SwitchCompatPreference(Context context) { + super(context); + init(); + } + + private void init() { + setWidgetLayoutResource(R.layout.preference_switch_layout); + } +}
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java index f55a7603f..665ddc3b5 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java @@ -5,28 +5,23 @@ import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.text.TextUtils; import android.util.Log; -import org.apache.commons.lang3.StringUtils; - -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.NetworkUtils; public class ConnectivityActionReceiver extends BroadcastReceiver { - private static final String TAG = "ConnectivityActionReceiver"; + private static final String TAG = "ConnectivityActionRecvr"; @Override public void onReceive(final Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), ConnectivityManager.CONNECTIVITY_ACTION)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received intent"); + if (TextUtils.equals(intent.getAction(), ConnectivityManager.CONNECTIVITY_ACTION)) { + Log.d(TAG, "Received intent"); - if (NetworkUtils.autodownloadNetworkAvailable(context)) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "auto-dl network available, starting auto-download"); + if (NetworkUtils.autodownloadNetworkAvailable()) { + Log.d(TAG, "auto-dl network available, starting auto-download"); DBTasks.autodownloadUndownloadedItems(context); } else { // if new network is Wi-Fi, finish ongoing downloads, // otherwise cancel all downloads @@ -34,12 +29,9 @@ public class ConnectivityActionReceiver extends BroadcastReceiver { .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = cm.getActiveNetworkInfo(); if (ni == null || ni.getType() != ConnectivityManager.TYPE_WIFI) { - if (BuildConfig.DEBUG) - Log.i(TAG, - "Device is no longer connected to Wi-Fi. Cancelling ongoing downloads"); + Log.i(TAG, "Device is no longer connected to Wi-Fi. Cancelling ongoing downloads"); DownloadRequester.getInstance().cancelAllDownloads(context); } - } } } diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java index 7ab386edf..7000827c6 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java @@ -4,46 +4,75 @@ import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; +import android.text.TextUtils; import android.util.Log; -import org.apache.commons.lang3.StringUtils; - -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.service.PlayerWidgetService; public class PlayerWidget extends AppWidgetProvider { - private static final String TAG = "PlayerWidget"; + private static final String TAG = "PlayerWidget"; + private static final String PREFS_NAME = "PlayerWidgetPrefs"; + private static final String KEY_ENABLED = "WidgetEnabled"; + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "onReceive"); + super.onReceive(context, intent); + // don't do anything if we're not enabled + if (!isEnabled(context)) { + return; + } + + // these come from the PlaybackService when things should get updated + if (TextUtils.equals(intent.getAction(), PlaybackService.FORCE_WIDGET_UPDATE)) { + startUpdate(context); + } else if (TextUtils.equals(intent.getAction(), PlaybackService.STOP_WIDGET_UPDATE)) { + stopUpdate(context); + } + } + + @Override + public void onEnabled(Context context) { + super.onEnabled(context); + Log.d(TAG, "Widget enabled"); + setEnabled(context, true); + startUpdate(context); + } @Override - public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), PlaybackService.FORCE_WIDGET_UPDATE)) { - startUpdate(context); - } else if (StringUtils.equals(intent.getAction(), PlaybackService.STOP_WIDGET_UPDATE)) { - stopUpdate(context); - } - - } - - @Override - public void onEnabled(Context context) { - super.onEnabled(context); - if (BuildConfig.DEBUG) - Log.d(TAG, "Widget enabled"); - } - - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, - int[] appWidgetIds) { - startUpdate(context); - } - - private void startUpdate(Context context) { - context.startService(new Intent(context, PlayerWidgetService.class)); - } - - private void stopUpdate(Context context) { - context.stopService(new Intent(context, PlayerWidgetService.class)); - } + public void onUpdate(Context context, AppWidgetManager appWidgetManager, + int[] appWidgetIds) { + Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + appWidgetIds + "]"); + startUpdate(context); + } + + @Override + public void onDisabled(Context context) { + super.onDisabled(context); + Log.d(TAG, "Widet disabled"); + setEnabled(context, false); + stopUpdate(context); + } + + private void startUpdate(Context context) { + Log.d(TAG, "startUpdate() called with: " + "context = [" + context + "]"); + context.startService(new Intent(context, PlayerWidgetService.class)); + } + + private void stopUpdate(Context context) { + Log.d(TAG, "stopUpdate() called with: " + "context = [" + context + "]"); + context.stopService(new Intent(context, PlayerWidgetService.class)); + } + + private boolean isEnabled(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + return prefs.getBoolean(KEY_ENABLED, false); + } + private void setEnabled(Context context, boolean enabled) { + SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + prefs.edit().putBoolean(KEY_ENABLED, enabled).apply(); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java index d15108bfe..ef6330f82 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java @@ -3,11 +3,10 @@ package de.danoeh.antennapod.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.text.TextUtils; import android.util.Log; import android.widget.Toast; -import org.apache.commons.lang3.StringUtils; - import java.util.Arrays; import java.util.Date; @@ -29,7 +28,7 @@ public class SPAReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), ACTION_SP_APPS_QUERY_FEEDS_REPSONSE)) { + if (TextUtils.equals(intent.getAction(), ACTION_SP_APPS_QUERY_FEEDS_REPSONSE)) { if (BuildConfig.DEBUG) Log.d(TAG, "Received SP_APPS_QUERY_RESPONSE"); if (intent.hasExtra(ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA)) { String[] feedUrls = intent.getStringArrayExtra(ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA); diff --git a/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java b/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java index 1fe9e2cf9..323060f81 100644 --- a/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java +++ b/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java @@ -14,206 +14,229 @@ import android.view.View; import android.widget.RemoteViews; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; 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.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.receiver.PlayerWidget; -/** Updates the state of the player widget */ +/** + * Updates the state of the player widget + */ public class PlayerWidgetService extends Service { - private static final String TAG = "PlayerWidgetService"; + private static final String TAG = "PlayerWidgetService"; - private PlaybackService playbackService; + private PlaybackService playbackService; - /** Controls write access to playbackservice reference */ + /** + * Controls write access to playbackservice reference + */ private Object psLock; - /** True while service is updating the widget */ - private volatile boolean isUpdating; + /** + * True while service is updating the widget + */ + private volatile boolean isUpdating; - public PlayerWidgetService() { - } + public PlayerWidgetService() { + } - @Override - public void onCreate() { - super.onCreate(); - Log.d(TAG, "Service created"); - isUpdating = false; + @Override + public void onCreate() { + super.onCreate(); + Log.d(TAG, "Service created"); + isUpdating = false; psLock = new Object(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.d(TAG, "Service is about to be destroyed"); - if (playbackService != null) { - Playable playable = playbackService.getPlayable(); - if (playable != null && playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - if (media.hasAlmostEnded()) { - Log.d(TAG, "smart mark as read"); - FeedItem item = media.getItem(); - DBWriter.markItemRead(this, item, true, false); - DBWriter.removeQueueItem(this, item, false); - DBWriter.addItemToPlaybackHistory(this, media); - if (UserPreferences.isAutoDelete()) { - Log.d(TAG, "Delete " + media.toString()); - DBWriter.deleteFeedMediaOfItem(this, media.getId()); - } - } - } - } - - try { - unbindService(mConnection); - } catch (IllegalArgumentException e) { - Log.w(TAG, "IllegalArgumentException when trying to unbind service"); - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (!isUpdating) { - if (playbackService == null && PlaybackService.isRunning) { - bindService(new Intent(this, PlaybackService.class), - mConnection, 0); - } else { - startViewUpdaterIfNotRunning(); - } - } else { - Log.d(TAG, "Service was called while updating. Ignoring update request"); - } - return Service.START_NOT_STICKY; - } - - private void updateViews() { - if (playbackService == null) { - return; + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.d(TAG, "Service is about to be destroyed"); + if (playbackService != null) { + Playable playable = playbackService.getPlayable(); + if (playable != null && playable instanceof FeedMedia) { + FeedMedia media = (FeedMedia) playable; + if (media.hasAlmostEnded()) { + Log.d(TAG, "smart mark as read"); + FeedItem item = media.getItem(); + DBWriter.markItemPlayed(item, FeedItem.PLAYED, false); + DBWriter.removeQueueItem(this, item, false); + DBWriter.addItemToPlaybackHistory(media); + if (item.getFeed().getPreferences().getCurrentAutoDelete()) { + Log.d(TAG, "Delete " + media.toString()); + DBWriter.deleteFeedMediaOfItem(this, media.getId()); + } + } + } + } + + try { + unbindService(mConnection); + } catch (IllegalArgumentException e) { + Log.w(TAG, "IllegalArgumentException when trying to unbind service"); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (!isUpdating) { + if (playbackService == null && PlaybackService.isRunning) { + bindService(new Intent(this, PlaybackService.class), + mConnection, 0); + } else { + startViewUpdaterIfNotRunning(); + } + } else { + Log.d(TAG, "Service was called while updating. Ignoring update request"); } - isUpdating = true; - - ComponentName playerWidget = new ComponentName(this, PlayerWidget.class); - AppWidgetManager manager = AppWidgetManager.getInstance(this); - RemoteViews views = new RemoteViews(getPackageName(), - R.layout.player_widget); - PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), 0); - - views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); - final Playable media = playbackService.getPlayable(); - if (playbackService != null && media != null) { - PlayerStatus status = playbackService.getStatus(); - - views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); - - String progressString = getProgressString(media); - if (progressString != null) { - views.setTextViewText(R.id.txtvProgress, progressString); - } - - if (status == PlayerStatus.PLAYING) { - views.setImageViewResource(R.id.butPlay, R.drawable.ic_pause_white_24dp); - if (Build.VERSION.SDK_INT >= 15) { - views.setContentDescription(R.id.butPlay, getString(R.string.pause_label)); + return Service.START_NOT_STICKY; + } + + private void updateViews() { + isUpdating = true; + + ComponentName playerWidget = new ComponentName(this, PlayerWidget.class); + AppWidgetManager manager = AppWidgetManager.getInstance(this); + RemoteViews views = new RemoteViews(getPackageName(), + R.layout.player_widget); + PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0, + PlaybackService.getPlayerActivityIntent(this), 0); + + Intent startApp = new Intent(getBaseContext(), MainActivity.class); + startApp.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startApp.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, QueueFragment.TAG); + PendingIntent startAppPending = PendingIntent.getActivity(getBaseContext(), 0, startApp, PendingIntent.FLAG_UPDATE_CURRENT); + + boolean nothingPlaying = false; + if (playbackService != null) { + final Playable media = playbackService.getPlayable(); + if (media != null) { + PlayerStatus status = playbackService.getStatus(); + views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); + + views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); + + String progressString = getProgressString(); + if (progressString != null) { + views.setViewVisibility(R.id.txtvProgress, View.VISIBLE); + views.setTextViewText(R.id.txtvProgress, progressString); } - } else { - views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp); - if (Build.VERSION.SDK_INT >= 15) { - views.setContentDescription(R.id.butPlay, getString(R.string.play_label)); + + if (status == PlayerStatus.PLAYING) { + views.setImageViewResource(R.id.butPlay, R.drawable.ic_pause_white_24dp); + if (Build.VERSION.SDK_INT >= 15) { + views.setContentDescription(R.id.butPlay, getString(R.string.pause_label)); + } + } else { + views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp); + if (Build.VERSION.SDK_INT >= 15) { + views.setContentDescription(R.id.butPlay, getString(R.string.play_label)); + } } - } - views.setOnClickPendingIntent(R.id.butPlay, - createMediaButtonIntent()); - } else { - views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE); - views.setTextViewText(R.id.txtvTitle, - this.getString(R.string.no_media_playing_label)); - views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp); - - } - - manager.updateAppWidget(playerWidget, views); - isUpdating = false; - } - - /** Creates an intent which fakes a mediabutton press */ - private PendingIntent createMediaButtonIntent() { - KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); - Intent startingIntent = new Intent( - MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER); - startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); - - return PendingIntent.getBroadcast(this, 0, startingIntent, 0); - } - - private String getProgressString(Playable media) { - int position = media.getPosition(); - int duration = media.getDuration(); - if (position > 0 && duration > 0) { - return Converter.getDurationStringLong(position) + " / " - + Converter.getDurationStringLong(duration); - } else { - return null; - } - } - - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - Log.d(TAG, "Connection to service established"); + views.setOnClickPendingIntent(R.id.butPlay, + createMediaButtonIntent()); + } else { + nothingPlaying = true; + } + } else { + nothingPlaying = true; + } + + if (nothingPlaying) { + // start the app if they click anything + views.setOnClickPendingIntent(R.id.layout_left, startAppPending); + views.setOnClickPendingIntent(R.id.butPlay, startAppPending); + views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE); + views.setTextViewText(R.id.txtvTitle, + this.getString(R.string.no_media_playing_label)); + views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp); + } + + manager.updateAppWidget(playerWidget, views); + isUpdating = false; + } + + /** + * Creates an intent which fakes a mediabutton press + */ + private PendingIntent createMediaButtonIntent() { + KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); + Intent startingIntent = new Intent( + MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER); + startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); + + return PendingIntent.getBroadcast(this, 0, startingIntent, 0); + } + + private String getProgressString() { + int position = playbackService.getCurrentPosition(); + int duration = playbackService.getDuration(); + if (position > 0 && duration > 0) { + return Converter.getDurationStringLong(position) + " / " + + Converter.getDurationStringLong(duration); + } else { + return null; + } + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "Connection to service established"); synchronized (psLock) { playbackService = ((PlaybackService.LocalBinder) service) .getService(); startViewUpdaterIfNotRunning(); } - } + } - @Override - public void onServiceDisconnected(ComponentName name) { + @Override + public void onServiceDisconnected(ComponentName name) { synchronized (psLock) { playbackService = null; Log.d(TAG, "Disconnected from service"); } - } + } - }; + }; - private void startViewUpdaterIfNotRunning() { - if (!isUpdating) { - ViewUpdater updateThread = new ViewUpdater(this); - updateThread.start(); - } - } + private void startViewUpdaterIfNotRunning() { + if (!isUpdating) { + ViewUpdater updateThread = new ViewUpdater(this); + updateThread.start(); + } + } - class ViewUpdater extends Thread { - private static final String THREAD_NAME = "ViewUpdater"; - private PlayerWidgetService service; + class ViewUpdater extends Thread { + private static final String THREAD_NAME = "ViewUpdater"; + private PlayerWidgetService service; - public ViewUpdater(PlayerWidgetService service) { - super(); - setName(THREAD_NAME); - this.service = service; + public ViewUpdater(PlayerWidgetService service) { + super(); + setName(THREAD_NAME); + this.service = service; - } + } - @Override - public void run() { + @Override + public void run() { synchronized (psLock) { service.updateViews(); } - } + } - } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/SubscriptionViewItem.java b/app/src/main/java/de/danoeh/antennapod/view/SubscriptionViewItem.java index ee2567364..7703554f9 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/SubscriptionViewItem.java +++ b/app/src/main/java/de/danoeh/antennapod/view/SubscriptionViewItem.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.view; import android.content.Context; +import android.net.Uri; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -8,8 +9,10 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; -import com.squareup.picasso.Callback; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.Feed; @@ -56,24 +59,31 @@ public class SubscriptionViewItem extends RelativeLayout { mUnreadCountText = (TextView) view.findViewById(R.id.unread_count_text); } - public void setFeed(final Feed feed, int unreadCount) { + public void setFeed(Feed feed) { mFeedTitle.setVisibility(VISIBLE); mFeedTitle.setText(feed.getTitle()); + Glide.with(mContext) + .load(feed.getImageUri()) + .listener(new RequestListener<Uri, GlideDrawable>() { + @Override + public boolean onException(Exception e, Uri model, Target<GlideDrawable> target, boolean isFirstResource) { + return false; + } - Picasso.with(mContext).load(feed.getImageUri()).centerCrop().fit().into(mImageView, new Callback() { - @Override - public void onSuccess() { - mFeedTitle.setVisibility(GONE); - } - - @Override - public void onError() { - } - }); - mUnreadCountText.setText(unreadCount + ""); + @Override + public boolean onResourceReady(GlideDrawable resource, Uri model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { + mFeedTitle.setVisibility(INVISIBLE); + return false; + } + }) + .centerCrop() + .into(mImageView); // Removing the updated time. It could be the latest podcast updated time in the future. //mTextTime.setText(TimeUtils.getTimeAgo(feed.getLastUpdate().getTime(), mContext)); mTextTime.setVisibility(GONE); + + // Could be the count of unread/ not played feed items + //mUnreadCountText.setText(String.valueOf(feed.getNumOfItems())); } } |